GHSA-2gg9-6p7w-6cpj: SandboxJS: Sandbox integrity escape
SummarySandboxJS blocks direct assignment to global objects (for example `Math.random = ...`), but this protection can be bypassed through an exposed callable constructor path: `this.constructor.call(target, attackerObject)`. Because `this.constructor` resolves to the internal `SandboxGlobal` function and `Function.prototype.call` is allowed, attacker code can write arbitrary properties into host global objects and persist those mutations across sandbox instances in the same process.DetailsThe intended safety model relies on write-time checks in assignment operations. In `assignCheck`, writes are denied when the destination is marked global (`obj.isGlobal`), which correctly blocks straightforward payloads like `Math.random = () => 1`.Reference: [`src/executor.ts#L215-L218`](https://github.com/nyariv/SandboxJS/blob/cc8f20b4928afed5478d5ad3d1737ef2dcfaac29/src/executor.ts#L215-L218)The bypass works because the dangerous write is not performed by an assignment opcode. Instead, attacker code reaches a host callable that performs writes internally. The constructor used for sandbox global objects is `SandboxGlobal`, implemented as a function that copies all keys from a provided object into `this`.Reference: [`src/utils.ts#L84-L88`](https://github.com/nyariv/SandboxJS/blob/cc8f20b4928afed5478d5ad3d1737ef2dcfaac29/src/utils.ts#L84-L88)At runtime, global scope `this` is a `SandboxGlobal` instance (`functionThis`), so `this.constructor` resolves to `SandboxGlobal`. That constructor is reachable from sandbox code, and calls through `Function.prototype.call` are allowed by the generic call opcode path.References:[`src/utils.ts#L118-L126`](https://github.com/nyariv/SandboxJS/blob/cc8f20b4928afed5478d5ad3d1737ef2dcfaac29/src/utils.ts#L118-L126)[`src/executor.ts#L493-L518`](https://github.com/nyariv/SandboxJS/blob/cc8f20b4928afed5478d5ad3d1737ef2dcfaac29/src/executor.ts#L493-L518)This creates a privilege gap:Direct global mutation is blocked in assignment logic.A callable host function that performs arbitrary property writes is still reachable.The call path does not enforce equivalent global-mutation restrictions.Attacker-controlled code can choose the write target (`Math`, `JSON`, etc.) via `.call(target, payloadObject)`.In practice, the payload:overwrites host `Math.random` successfully. The mutation is visible immediately in host runtime and in fresh sandbox instances, proving cross-context persistence and sandbox boundary break.PoCInstall dependency:Global write bypass with `pwned` markerExpected output:`With bypass (host Math.random())` proves the sandbox changed host runtime state immediately.  
`With bypass (fresh sandbox Math.random())` proves the mutation persists across new sandbox instances, which shows cross-execution contamination.Command `id` execution via host gadgetThis second PoC demonstrates exploitability when host code later uses a mutated global property in a sensitive sink. It uses the POSIX `id` command as a harmless execution marker.Expected output:ImpactThis is a sandbox integrity escape. Untrusted code can mutate host shared global objects despite explicit global-write protections. Because these mutations persist process-wide, exploitation can poison behavior for other requests, tenants, or subsequent sandbox runs. Depending on host application usage of mutated built-ins, this can be chained into broader compromise, including control-flow hijack in application logic that assumes trusted built-in behavior.
CVSS v3.1: 10