{"id":"CVE-2026-44266","kind":"vulnerability","aliases":["CVE-2026-44266","GHSA-7w38-ggh5-v542"],"related":[],"title":"Elo Ruby backend has Remote Code Execution via unescaped `#{...}` interpolation in emitted string literals and subtype-constraint labels","description":"### Summary\n\nElo's Ruby backend emits attacker-controlled text into Ruby double-quoted strings without escaping the `#` character. Because `#` begins Ruby's interpolation sequences — `#{expr}`, `#@ivar`, `#@@cvar`, `#$gvar` — and any expression wrapped in `#{...}` may use backticks for shell execution, an Elo program that contains a crafted string literal or a labelled subtype constraint compiles to Ruby that evaluates attacker-chosen Ruby, and through backticks runs attacker-chosen shell commands, at runtime.\n\nThe same Elo source compiled to JavaScript, Python, or SQL is benign, so the bug also breaks the cross-target portability guarantee the rest of the compiler honours.\n\n### Vulnerable sinks\n\nTwo sinks exist in `src/compilers/ruby.ts` at commit `749af7b`:\n\n1. **String-literal emitter — lines 180–184.**\n   The emitter escapes backslash, double quote, and control characters, but not `#`. Any `#{...}` sequence in an Elo string literal flows straight into the emitted Ruby source.\n\n2. **Labelled subtype-constraint branch — lines 405–412.**\n   The error message passed to `p_fail(p, \"...\")` is wrapped in a Ruby double-quoted string with no escape pass at all. Because the parser at `src/parser.ts:1339–1348` accepts single-quoted Elo strings as constraint labels, a label such as `'#{` `` `id` `` `}'` flows unchanged into the emitted Ruby.\n\n### Attack surfaces\n\nAll three surfaces share the same root cause (the missing `#` escape in the Ruby double-quoted-string emission path) and are addressed by the same patch:\n\n| # | Entry point | Threat model |\n|---|---|---|\n| 1 | `/try` playground on elo-lang.org (Ruby target) | 0-click server-side RCE against elo-lang.org infrastructure from a shared PoC URL |\n| 2 | `bin/eloc -t ruby -x` on a CI or DevOps host | RCE on any host that passes Elo strings through the CLI |\n| 3 | Library API — `defaultFormats.elo.parse()` with a Ruby emission step | Ruby RCE for any downstream integrator using Elo as a rule engine, configuration DSL, or form builder DSL (the primary documented use cases) |\n\n### Proof of Concept\n\n**String-literal sink:**\n\n```\n# poc.elo\n'RES=#{`id 2>&1`}'\n```\n\n```\n$ eloc -t ruby -x poc.elo\n# emitted Ruby contains:  \"RES=#{`id 2>&1`}\"\n# running it returns the output of id(1).\n```\n\nReplacing `` `id 2>&1` `` with `` `touch /tmp/pwned` `` produces a trivially observable filesystem side effect.\n\n**Subtype-constraint label sink:**\n\n```\n# poc-subtype.elo\nlet X = Int(i | '#{`touch /tmp/pwned2`}': i > 0) in X(-1)\n```\n\nThe predicate fails for `-1`, causing `p_fail(p, \"#{...}\")` to interpolate and execute the backtick command at the Ruby layer.\n\nBoth flows are automated in the reproduction script submitted with the original report and were verified against Ruby 3.x.\n\n### Root cause\n\nThe Ruby-emitter's double-quoted-string escape routine omitted `#` from its character set, and one emission path (the subtype-constraint label branch) performed no escape pass at all.","summary":"The Elo compiler's Ruby target writes string literals into Ruby double-quoted strings without escaping the # character, so a #{...} sequence in Elo source becomes live Ruby code. An attacker who can supply Elo source compiled to Ruby can run arbitrary shell commands through backticks.","severity":"critical","cvss":9.8,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"@enspirit/elo","fixed":["1.0.1"],"affected":"<= 1.0.0","ecosystem":"npm"}],"ecosystems":["npm"],"tags":["cwe-116","cwe-1336","cwe-94","elo","eloc","npm","package","ruby"],"not_affected_if":["javascript-or-python-or-sql-target-only","trusted-elo-source-only"],"how_to_check":"- Check the installed version of `@enspirit/elo` in your project lockfile or with `npm ls @enspirit/elo`.\n- Find any build, CI or server step that runs the Ruby target (`eloc -t ruby` or the Ruby emission API) on Elo source you do not fully control.","fix":"- `@enspirit/elo` fixed in `1.0.1`; affected versions are `<= 1.0.0`.","status":"open","published_at":"2026-11-03T17:51:30+00:00","first_seen_at":"2026-11-03T17:52:03+00:00","triaged_at":"2026-11-03T17:53:08+00:00","updated_at":"2026-11-03T17:53:08+00:00","category":"package","confidence":0.8,"triage":{"at":"2026-11-03T17:53:08Z","model":"claude-haiku-5-5","output":{"fix":"- `@enspirit/elo` fixed in `1.0.1`; affected versions are `<= 1.0.0`.","reach":"network","title":"Elo Ruby backend: crafted strings run attacker-chosen shell commands via interpolation","impact":"high","summary":"The Elo compiler's Ruby target writes string literals into Ruby double-quoted strings without escaping the # character, so a #{...} sequence in Elo source becomes live Ruby code. An attacker who can supply Elo source compiled to Ruby can run arbitrary shell commands through backticks.","audience":"developers","category":"package","nicknames":[],"who_cares":["npm","elo","ruby","eloc"],"confidence":0.8,"fix_status":"available","needs_auth":"unknown","worst_case":"Attacker runs arbitrary shell commands on the host compiling or running the Ruby output.","analyst_note":"Confirm from the GHSA advisory whether the Ruby target is the only affected output and whether the playground exposure is a separate deployment to check.","how_to_check":"- Check the installed version of `@enspirit/elo` in your project lockfile or with `npm ls @enspirit/elo`.\n- Find any build, CI or server step that runs the Ruby target (`eloc -t ruby` or the Ruby emission API) on Elo source you do not fully control.","plain_action":"","needs_analyst":true,"not_affected_if":["javascript-or-python-or-sql-target-only","trusted-elo-source-only"],"who_is_affected":"Teams that compile Elo to Ruby using the library API or the eloc CLI on Elo source that includes untrusted input, such as a rule engine, configuration DSL or form builder that accepts user-supplied strings. Projects that only compile Elo to JavaScript, Python or SQL, or that only compile trusted Elo source, are not exposed by this flaw.","user_interaction":"no","needs_special_setup":"yes","severity_if_unknown":"critical"},"cost_usd":0.000877,"input_tokens":5364,"output_tokens":681,"severity_before":"critical"},"analyst_status":null,"analyst_note":null,"search":"'-1':465C,470C '-116':33B '-1336':35B '-2026':21A,29A '-44266':22A,30A '-94':37B '/tmp/pwned':441C '/tmp/pwned2':460C '/try':329C '0':335C,462C '1':203C,328C,414C,426C,434C,438C '1339':273C '1348':274C '180':209C '184':210C '2':236C,349C,413C,425C,437C '3':371C,504C '405':243C '412':244C '749af7b':202C '7w38':25A 'accept':275C 'address':319C 'also':179C 'api':373C 'arbitrari':84B 'attack':73B,96C,150C,158C,295C 'attacker-chosen':149C,157C 'attacker-control':95C 'autom':489C 'backend':3A,93C 'backslash':214C 'backtick':88B,126C,155C,480C 'becom':68B 'begin':110C 'benign':175C 'bin/eloc':350C 'branch':241C,533C 'break':180C 'bug':178C 'builder':396C 'case':402C 'caus':304C,471C,507C 'charact':61B,108C,219C,522C 'chosen':151C,159C 'ci':356C 'cli':370C 'click':336C 'code':6A,71B 'command':86B,161C,481C 'commit':201C 'compil':45B,79B,144C,168C,191C 'concept':405C 'configur':392C 'constraint':18A,143C,240C,282C,451C,531C 'contain':134C,422C 'control':97C,218C 'craft':136C 'cross':183C 'cross-target':182C 'cvar':117C 'cve':20A,28A 'cwe':32B,34B,36B 'defaultformats.elo.parse':374C 'devop':358C 'document':400C 'doubl':55B,102C,215C,259C,312C,514C 'double-quot':54B,101C,258C 'double-quoted-str':311C,513C 'downstream':384C 'dsl':393C,397C 'effect':448C 'elo':1A,38B,44B,66B,77B,90C,131C,166C,226C,279C,366C,387C 'elo-lang.org':332C,342C 'eloc':39B,415C 'emiss':315C,378C,526C 'emit':12A,94C,233C,293C,420C 'emitt':207C,212C,511C 'engin':391C 'enspirit/elo':31A 'entri':324C 'error':246C 'escap':59B,106C,213C,264C,307C,517C,536C 'evalu':148C 'execut':7A,129C,478C 'exist':197C 'expr':115C 'express':121C 'fail':251C,468C,473C 'filesystem':446C 'flow':229C,289C,487C 'form':395C 'ggh5':26A 'ghsa':24A 'ghsa-7w38-ggh5-v542':23A 'guarante':186C 'gvar':118C 'honour':192C 'host':359C,363C 'id':288C,412C,424C,433C,436C 'infrastructur':343C 'int':457C 'integr':385C 'interpol':10A,113C,476C 'ivar':116C 'javascript':170C 'label':19A,141C,237C,283C,285C,452C,532C 'layer':485C 'let':455C 'librari':372C 'line':208C,242C 'liter':14A,51B,138C,206C,228C,408C 'live':69B 'may':124C 'messag':247C 'miss':306C 'model':327C 'npm':40B 'observ':445C 'omit':519C 'one':525C 'origin':497C 'output':431C 'p':250C,252C,472C,474C 'packag':41B 'parser':270C 'pass':248C,265C,365C,537C 'patch':323C 'path':316C,527C 'perform':534C 'playground':330C 'poc':347C 'poc-subtype.elo':454C 'poc.elo':410C,419C 'point':325C 'portabl':185C 'predic':467C 'primari':399C 'produc':442C 'program':132C 'proof':403C 'python':171C 'quot':56B,103C,216C,260C,278C,313C,515C 'rce':340C,360C,381C 'remot':5A 'replac':435C 'report':498C 'reproduct':492C 'res':411C,423C 'rest':188C 'return':429C 'root':303C,506C 'routin':518C 'rubi':2A,42B,47B,53B,70B,81B,92C,100C,111C,146C,152C,234C,257C,294C,310C,333C,352C,377C,380C,417C,421C,484C,503C,510C 'ruby-emitt':509C 'rule':390C 'run':83B,156C,427C 'runtim':163C 'script':493C 'sequenc':64B,114C,223C 'server':338C 'server-sid':337C 'set':523C 'share':300C,346C 'shell':85B,128C,160C 'side':339C,447C 'singl':277C 'single-quot':276C 'sink':194C,196C,409C,453C 'sourc':67B,78B,167C,235C 'sql':173C 'src/compilers/ruby.ts':199C 'src/parser.ts':272C 'step':379C 'straight':230C 'string':13A,50B,57B,104C,137C,205C,227C,261C,280C,314C,367C,407C,516C 'string-liter':204C,406C 'submit':494C 'subtyp':17A,142C,239C,450C,530C 'subtype-constraint':16A,238C,449C,529C 'summari':89C 'suppli':76B 'surfac':296C,299C 'target':48B,184C,334C 'text':98C 'threat':326C 'three':298C 'touch':440C,459C 'trivial':444C 'two':195C 'unchang':290C 'unescap':9A 'url':348C 'use':125C,386C,401C 'v542':27A 'verifi':501C 'via':8A 'vulner':193C 'without':58B,105C 'wrap':122C,254C 'write':49B 'x':353C,418C,456C,464C,505C","nicknames":[],"mitigations":[],"who_affected":"Teams that compile Elo to Ruby using the library API or the eloc CLI on Elo source that includes untrusted input, such as a rule engine, configuration DSL or form builder that accepts user-supplied strings. Projects that only compile Elo to JavaScript, Python or SQL, or that only compile trusted Elo source, are not exposed by this flaw.","tier":"B","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"developers","plain_action":null,"pkeys":["elo"],"ptokens":["elo","enspirit"],"labels":["critical","cwe116","cwe1336","cwe94","elo","eloc","npm","package","ruby"],"campaign_type":null,"first_public_at":"2026-10-30T17:23:15+00:00","kev":null,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","cvss_by":"GitHub","poc_count":0,"sources":[{"source":"ghsa","external_id":"GHSA-7w38-ggh5-v542","url":"https://github.com/advisories/GHSA-7w38-ggh5-v542","severity":"critical","published_at":"2026-11-03T17:51:30+00:00","modified_at":"2026-11-03T17:51:30+00:00","first_seen_at":"2026-11-03T17:52:03+00:00","fetched_at":"2026-11-03T17:57:07+00:00","note":null},{"source":"nvd","external_id":"CVE-2026-44266","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-44266","severity":null,"published_at":"2026-11-03T18:18:02+00:00","modified_at":"2026-11-03T18:18:02+00:00","first_seen_at":"2026-11-03T18:22:10+00:00","fetched_at":"2026-11-03T18:22:11+00:00","note":null}],"documents":[{"id":59468,"url":"https://api.github.com/repos/enspirit/elo/security-advisories/GHSA-7w38-ggh5-v542","title":"Maintainer's advisory: api.github.com","kind":"vendor","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: api.github.com"},{"id":59469,"url":"https://github.com/enspirit/elo/security/advisories/GHSA-7w38-ggh5-v542","title":"Maintainer's advisory: github.com","kind":"vendor","source":"ghsa","publisher":"vendor","text":null,"published_at":"2026-10-30T17:23:15+00:00","relation":"advisory","confidence":null,"note":"Maintainer's advisory: github.com"},{"id":59470,"url":"https://github.com/enspirit/elo/commit/51b869963fbe445c18328714a7ccfbd76b21ba38","title":"Patch: github.com","kind":"other","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Patch: github.com"},{"id":59471,"url":"https://github.com/enspirit/elo/commit/d2a89a8db76a244eb740687524a7e8529b7554b5","title":"Patch: github.com","kind":"other","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Patch: github.com"}],"notice":"Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai"}