CVE-2026-44266 is a critical-severity (CVSS 9.8) Code Injection vulnerability in @enspirit/elo. A fix is available for @enspirit/elo — see the affected versions and patch details below.
Elo Ruby backend has Remote Code Execution via unescaped `#{...}` interpolation in emitted string literals and subtype-constraint labels
Exploitation Status
Proof-of-concept exploit code exists
- CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
- CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.
- A successful exploit gives an attacker total control of the affected component, not partial access.
Exploitation and automatability from CISA’s SSVC triage for CVE-2026-44266.
Real-World Exposure
How broadly this vulnerability is actually deployed: weekly install volume shows current usage, and reverse-dependency count shows how many other packages break if it stays unpatched.
@enspirit/elonpmDescription
Summary
Elo'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.
The 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.
Vulnerable sinks
Two sinks exist in src/compilers/ruby.ts at commit 749af7b:
-
String-literal emitter — lines 180–184. 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. -
Labelled subtype-constraint branch — lines 405–412. 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 atsrc/parser.ts:1339–1348accepts single-quoted Elo strings as constraint labels, a label such as'#{`id`}'flows unchanged into the emitted Ruby.
Attack surfaces
All 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:
| # | Entry point | Threat model |
|---|---|---|
| 1 | /try playground on elo-lang.org (Ruby target) | 0-click server-side RCE against elo-lang.org infrastructure from a shared PoC URL |
| 2 | bin/eloc -t ruby -x on a CI or DevOps host | RCE on any host that passes Elo strings through the CLI |
| 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) |
Proof of Concept
String-literal sink:
# poc.elo
'RES=#{`id 2>&1`}'
$ eloc -t ruby -x poc.elo
# emitted Ruby contains: "RES=#{`id 2>&1`}"
# running it returns the output of id(1).
Replacing `id 2>&1` with `touch /tmp/pwned` produces a trivially observable filesystem side effect.
Subtype-constraint label sink:
# poc-subtype.elo
let X = Int(i | '#{`touch /tmp/pwned2`}': i > 0) in X(-1)
The predicate fails for -1, causing p_fail(p, "#{...}") to interpolate and execute the backtick command at the Ruby layer.
Both flows are automated in the reproduction script submitted with the original report and were verified against Ruby 3.x.
Root cause
The 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.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 📦npm | @enspirit/elo | all versions | 1.0.1npm install @enspirit/elo@1.0.1 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for @enspirit/elo, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update @enspirit/elo to 1.0.1 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-44266 is resolved across your whole dependency graph.
Workarounds
Stop passing untrusted input into the interpreter or shell: call the affected binary with an argument array rather than a composed command string, reject anything outside a strict allowlist of expected values, and run the component under an account that cannot reach beyond the work it legitimately does.
Frequently Asked Questions
Is CVE-2026-44266 in your dependencies?
Find it across npm, including transitive dependencies.