Critical advisory for @enspirit/elo
GHSA-7w38-ggh5-v542 · Reported 3 Nov 2026
Full advisory details, affected packages, severity context, and original source.
Affected technology
Advisory overview
### 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`: 1. **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. 2. **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 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. ### 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 and releases
npm / @enspirit/elo
introduced: 0 · fixed: 1.0.1
Also known as
CVE-2026-44266
