# Sandbox escape via reachable constructor gadgets

Sandboxed code reaching host callables, constructors or coercion hooks that write or eval outside the guard

_Area: Web injection and code execution. Seen in 3 disclosed reports; latest 2026-04-08. Updated 2026-11-02._

## How it works
A sandbox tries to confine untrusted code by blocking dangerous names or guarding assignment. The assumption that breaks: that the only path to a capability is the one being checked. If sandboxed code can still reach a host object, constructor, or coercion hook that internally performs the forbidden action, the check is bypassed. The guard sits at one layer (a name blocklist, an assignment opcode, a member-access rule) while the capability lives at another (dot-notation attribute access, a constructor that copies keys into `this`, an implicit `toString` call).

## Where it shows up
- Two parallel blocklists for the same protection (e.g. direct mode vs subprocess mode) [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-qf73-2hrx-xprp.md)
- A `_safe_getattr`-style hook that only intercepts `getattr()` but not dot notation [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-qf73-2hrx-xprp.md)
- Attribute blocklists that omit frame-traversal names (`__traceback__`, `tb_frame`, `f_back`, `f_builtins`) [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-qf73-2hrx-xprp.md)
- Sandboxes whose only guard is write-time assignment checks [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-2gg9-6p7w-6cpj.md)
- Reachable host constructors, setters, or merge/copy helpers that write properties internally [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-2gg9-6p7w-6cpj.md)
- Expression evaluators that allow member access to `window`/global scope [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-7f2v-3qq3-vvjf.md)
- Global functions whose internal `this.x(...)` calls can be repurposed as `toString`/`valueOf` gadgets [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-7f2v-3qq3-vvjf.md)

## How to review
Identify the exact invariant the sandbox claims: "untrusted code cannot reach capability X." Then enumerate every reachable host callable and every implicit coercion path, and check whether any of them performs X internally without passing the guard. The property that must hold: no reachable object, constructor, setter, or coercion hook can perform a guarded action on behalf of sandboxed code. For blocklists, verify the check is enforced at the same layer as the access (dot notation, not just `getattr`). For assignment guards, verify no reachable host function writes properties internally. For expression sandboxes, verify member access to globals cannot reach a function that evals attacker-controlled strings.

## How finders spotted it
- Diffed the two `blocked_attrs` sets and saw the subprocess list was a strict subset missing all frame-traversal attributes; also noticed `_safe_getattr` only hooks `getattr()` so dot notation bypasses it [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-qf73-2hrx-xprp.md)
- Noted the safety model relies solely on write-time assignment checks, reasoned any reachable host callable doing internal writes would sidestep it, then reached the `SandboxGlobal` constructor via `this.constructor` and `Function.prototype.call` [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-2gg9-6p7w-6cpj.md)
- Noted expressions can member-get window objects, then searched global scope for a function usable as a `toString` gadget that internally calls attacker-controlled properties to reach eval [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-7f2v-3qq3-vvjf.md)

## Variants seen
- Exception frame traversal reaching real builtins through a weaker subprocess blocklist [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-qf73-2hrx-xprp.md)
- Constructor gadget (`this.constructor.call`) copying keys into host globals, poisoning them process-wide [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-2gg9-6p7w-6cpj.md)
- `toString` coercion gadget on a debug global yielding DOM XSS [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-7f2v-3qq3-vvjf.md)

## Impact and severity
E1 reached arbitrary shell command execution inside the subprocess, bypassing AST blocklist, `_safe_getattr` and exec-by-name checks simultaneously [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-qf73-2hrx-xprp.md). E2 poisoned built-ins persisted across tenants/requests in the same process and chained into host gadgets (`execSync` on `Math.cmd`) to reach RCE [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-2gg9-6p7w-6cpj.md). E3 yielded DOM XSS when untrusted specs were rendered [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-7f2v-3qq3-vvjf.md). Impact rises with process-wide persistence and chaining into host gadgets that consume mutated globals [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-2gg9-6p7w-6cpj.md), and with reach into real builtins that defeats multiple guards at once [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-qf73-2hrx-xprp.md).

## Evidence
- E1: https://bugbountylibrary.com/r/ghsa%3AGHSA-qf73-2hrx-xprp.md
- E2: https://bugbountylibrary.com/r/ghsa%3AGHSA-2gg9-6p7w-6cpj.md
- E3: https://bugbountylibrary.com/r/ghsa%3AGHSA-7f2v-3qq3-vvjf.md
