Skip to content

JSONata arbitrary-code-execution trio (CVE-2026-77413 / -77414 / -77415)

Summary

Three critical arbitrary code execution flaws in JSONata (npm, ~1.65M downloads/week, MIT-licensed query and transformation language originating at IBM) were published to the GitHub Advisory Database on August 21, 2026 with working public PoCs. A crafted JSONata expression — evaluated against attacker-controlled or attacker-influenced data — executes attacker code in the host Node.js process via child_process.execSync.

All three advisories describe the same attack class (sandbox-escaping through JSONata's host-object bridge) reached through different primitives:

Advisory Root cause Fixed in
CVE-2026-77413 / GHSA-8gq3-vp5j-2grp Missing hasOwnProperty check in the lookup function (src/functions.js, ~L1686–1705) — prototype-chain lookup reaches __lookupSetter__/__defineGetter__ and the object constructor 2.2.0 (PR #794), backported to 1.8.8
CVE-2026-77414 / GHSA-2943-5xfg-gq5f Bypassable hasOwnProperty check in environment.lookup (src/jsonata.js, ~L1863–1871) — $hasOwnProperty := $spread($string); $__proto__ := $constructor; recovers the host Function constructor 2.2.1 (PR #799), backported to 1.8.8
CVE-2026-77415 / GHSA-66mm-25pp-rfff Chain of three flaws: overwriting $clone (mutating objects via evaluateTransformExpression), destructuring JSONata lambdas (e.g. $merge.*), and applyProcedure using proc.arguments.forEach instead of Array.prototype.forEach 2.2.1 (PRs #799, #800, #802), backported to 1.8.8

Affected: npm jsonata < 1.8.8 and >= 2.0.0 < 2.2.1. All three PoCs end in the same primitive — building a host Function constructor and calling it with "return process.getBuiltinModule('child_process').execSync('sh',{stdio:'inherit'})" (Node 22+; on older Node the equivalent is require('child_process') via the recovered constructor). All three advisories list CWE-94 (Code Injection) and critical severity; no in-the-wild exploitation or actor attribution is reported.

Tags

Why this matters

  • JSONata is a data-query language, not a JS sandbox: it is embedded in ETL pipelines, ESB/integration layers, BI and data-catalog tooling, IoT/telemetry decoders, and workflow engines to parse, shape, and transform JSON — often against payloads whose structure is attacker-influenced (device telemetry, API responses, file contents, user-supplied documents). Any of those hosts that also evaluates the expression from user- or pipeline-controlled input is a code-injection surface, not just a data-processing step.
  • The three advisories show the host-object bridge (property lookup, lambda/function objects, transform procedures) is the load-bearing trust boundary in JSONata, and that fixing one bypass (2.2.0, May 14) left the next one (2.2.1, May 19 / July backport, August 21 publication) behind it. The pattern — repeated prototype-access bypasses in a data language's object-lookup path — is the same failure family as vm2's wildcard builtins and isolated-vm's native-binding handoff: a "safe" data/query layer whose glue code reaches into host objects.
  • Public PoCs exist for all three, and the fixes are ordinary version bumps, so this is a patch-now item: no exploit configuration or network position needed, and no in-the-wild window to wait out.

Mechanism

All three chains use JSONata's ability to reach host JavaScript objects from inside an expression and call methods on them:

CVE-2026-77413 (lookup, fixed in 2.2.0 / 1.8.8). The lookup function in src/functions.js read a key off an object without a hasOwnProperty guard, so the expression engine would walk the prototype chain and expose __lookupSetter__('__proto__') and friends:

jsonata(`(
   __lookupSetter__('__proto__')(constructor);
   __defineGetter__('l', constructor("return
process.getBuiltinModule('child_process').execSync('sh',{stdio:'inherit'}).toString()"));
   valueOf().l
)`).evaluate({});

PR #794 (in 2.2.0, ported into 1.8.8) adds the missing own-property check.

CVE-2026-77414 (environment.lookup bypass, fixed in 2.2.1 / 1.8.8). The analogous check in environment.lookup (src/jsonata.js) was bypassable by spreading the $string function to re-bind $hasOwnProperty and capturing $constructor first:

jsonata(`(
     $hasOwnProperty := $spread($string);
     $__proto__ := $constructor;
     $constructor("return
process.getBuiltinModule('child_process').execSync('sh',{stdio:'inherit'})")();
)`).evaluate({});

PR #799 (in 2.2.1, back-ported to 1.8.8) closes it.

CVE-2026-77415 (transform/lambda/procedure chain, fixed in 2.2.1 / 1.8.8). Three stacked flaws: 1. overwriting $clone allows mutation of objects passed through transforms (evaluateTransformExpression); 2. JSONata functions/lambdas can be destructured (e.g. $merge.*), leaking their internal representation; 3. applyProcedure calls proc.arguments.forEach instead of Array.prototype.forEach, so a poisoned arguments object's forEach is invoked with attacker control.

The published PoC chains them to capture a __lookupGetter__ reference on a transform argument, reconstitutes a host function, and executes child_process.execSync the same way. Fixed by PRs #799, #800, and #802 (in 2.2.1, back-ported to 1.8.8).

Affected and patched versions

Product Affected Patched
jsonata (npm) v1 line <= 1.8.7 1.8.8 (dist-tag latest-v1 is now 1.8.9)
jsonata (npm) v2 line >= 2.0.0 < 2.2.1 2.2.1 (dist-tag latest is now 2.2.2)

Release timeline on npm: 2.2.0 (2026-05-14, contains PR #794 for 77413), 2.2.1 (2026-05-19, contains PRs #799/#800/#802 for 77414/77415), 1.8.8 (2026-07-16, "Security Release" — prototype-pollution fixes and v1 backports of the same PRs), 2.2.2 (2026-07-16, maintenance), 1.8.9 (2026-07-30, backports $toMillis fixes). The August 21 advisories retroactively assigned the CVEs to those fixes; 2.2.2 / 1.8.9 are the safe targets.

Upgrade notes

  • Move to jsonata ≥ 2.2.2 (or ≥ 1.8.9 on the v1 line). Verify the resolved version in package.json, lockfiles, and node_modules/jsonata/package.json.
  • No embedder-side configuration mitigates this: the flaw is reachable by evaluating any attacker-influenced expression on vulnerable versions. If upgrading is not immediately possible, stop evaluating expressions (or the data they bind to) from untrusted input until you are on a patched release.
  • Audit where expression strings come from in your pipeline: the risk is expression injection, so a "static" expression evaluated over malicious data is safe, but a template with interpolated user input is not.

Detection

  • Dependency sweep: jsonata < 2.2.1 / < 1.8.8 in any Node host that evaluates expressions over external data (ETL/ELT jobs, integration brokers, BI front-ends, workflow engines).
  • Review expression-provenance code paths: any string built from user/pipeline input and passed to jsonata(...).evaluate() is the exposure to prioritize.
  • Host telemetry: child_process.execSync / shell spawns from a long-running Node data-pipeline process that has no legitimate reason to exec is the observable half-signature of these PoCs.

Assessment limits

  • No in-the-wild exploitation, PoC abuse, or actor linkage is reported as of the August 21, 2026 advisory publication.
  • The CVEs were assigned retroactively to fixes that shipped in May–July 2026 (2.2.0, 2.2.1, 1.8.8); the advisories are a disclosure of those fixes with PoCs, not a new exploit window opened in August.
  • The PoCs target Node 22+ (process.getBuiltinModule); equivalent require('child_process') access is trivially available on earlier Node lines, so the PoC text should not be read as limiting the impact.

Sources