# REPRO-2026-00098: SandboxJS: Host Prototype Pollution via Array Intermediary (Sandbox Escape) ## Summary Status: published Severity: critical Type: security Confidence: Unknown ## Identifiers REPRO ID: REPRO-2026-00098 GHSA: GHSA-ww7g-4gwx-m7wj CVE: CVE-2026-25881 ## Package Name: @nyariv/sandboxjs Ecosystem: npm Affected: <= 0.8.30 Fixed: 0.8.31 ## Root Cause # Root Cause Analysis: GHSA-ww7g-4gwx-m7wj ## Summary A sandbox escape vulnerability in `@nyariv/sandboxjs` (versions <= 0.8.30) allows sandboxed untrusted code to bypass the `isGlobal` protection mechanism and pollute host built-in prototypes (e.g., `Map.prototype`, `Set.prototype`). The vulnerability occurs when global prototype references are placed into array literals - the `isGlobal` taint flag is stripped during array creation via `valueOrProp()`, allowing the retrieved prototype to be modified directly from within the sandbox. ## Impact - **Package:** `@nyariv/sandboxjs` (npm) - **Affected Versions:** `<= 0.8.30` - **Fixed In:** `0.8.31` - **CVSS Score:** 9.1 (Critical) - **CWE:** CWE-1321 (Improperly Controlled Modification of Object Prototype Attributes) **Risk:** This is a sandbox escape vulnerability that breaks isolation between untrusted sandboxed code and the host environment. Attackers can: 1. Persistently pollute host prototypes (e.g., `Map.prototype`, `Set.prototype`) 2. Overwrite built-in methods (e.g., `Set.prototype.has`) 3. Inject properties that can be used in RCE gadgets if host code uses polluted properties in sensitive sinks like `child_process.execSync()` **Consequences:** Any application using this package to execute untrusted JavaScript is vulnerable to complete host compromise. ## Root Cause ### Technical Explanation The sandbox uses a `Prop` class with an `isGlobal` flag to track whether a value is a global/prototype that should be protected from modification. The `set()` method in `src/utils.ts` checks this flag: ```typescript set(key: string, val: unknown) { // ... if (prop.isGlobal) { throw new SandboxError(`Cannot override global variable '${key}'`); } (prop.context as any)[prop.prop] = val; return prop; } ``` However, when values pass through array literal creation in `src/executor.ts` (lines 559-571), the `valueOrProp()` function unwraps `Prop` objects into raw values, stripping the `isGlobal` taint: ```typescript addOps(LispType.CreateArray, (exec, done, ticks, a, b: Lisp[], obj, context, scope) => { const items = (b as LispItem[]) .map((item) => { if (item instanceof SpreadArray) { return [...item.item]; } else { return item; } }) .flat() .map((item) => valueOrProp(item, context)); // <- isGlobal flag lost here done(undefined, items); }); ``` When `Map.prototype` is accessed via `[Map.prototype][0]`, the retrieved value no longer has the `isGlobal` flag, bypassing the protection. ### Fix Commit - https://github.com/nyariv/SandboxJS/commit/f369f8db26649f212a6a9a2e7a1624cb2f705b53 ## Reproduction Steps Run the reproduction script: ```bash ./repro/reproduction_steps.sh ``` The script performs three tests: 1. **Prototype Pollution Test:** Places `Map.prototype` into an array, retrieves it, and sets `polluted='pwned'`. Verifies that `'polluted' in Map.prototype` returns `true` and `Map.prototype.polluted === 'pwned'`. 2. **Method Overwrite Test:** Retrieves `Set.prototype` via array and overwrites `Set.prototype.has` with `isFinite`. Verifies the overwrite succeeds. 3. **RCE Gadget Test:** Pollutes `Map.prototype` with `cmd='id'`, demonstrating that `new Map().cmd` returns `'id'`, which could be passed to `execSync()` by vulnerable host code. ### Expected Evidence The script outputs `[PASS]` for all three tests and confirms: - `"polluted" in Map.prototype = true` - `Map.prototype.polluted = pwned` - `Set.prototype.has === isFinite: true` - `new Map().cmd = id` ## Evidence **Log Files:** - `logs/reproduction.log` - Complete reproduction output **Key Excerpts from Successful Run:** ``` [TEST 1] Prototype pollution via array intermediary Before: "polluted" in Map.prototype = false After: "polluted" in Map.prototype = true Map.prototype.polluted = pwned [PASS] Prototype pollution confirmed! [TEST 2] Overwrite Set.prototype.has Set.prototype.has === isFinite: true [PASS] Set.prototype.has was successfully overwritten! [TEST 3] RCE gadget via prototype pollution new Map().cmd = id [PASS] RCE gadget works - injected command in prototype! ``` **Environment:** - Node.js (tested with npm-installed package) - `@nyariv/sandboxjs` version 0.8.30 (vulnerable) ## Recommendations / Next Steps ### Immediate Actions 1. **Upgrade to version 0.8.31 or later** - The fix commit addresses the taint stripping issue 2. **Audit existing deployments** - Check if any applications using this package process untrusted user input ### Fix Approach (for maintainers) 1. **Preserve `isGlobal` through array/object literals:** Modify the array/object creation code to preserve the `Prop` wrapper instead of unwrapping via `valueOrProp()` 2. **Hard block built-in prototype writes:** Add explicit checks for built-in prototype objects (`Map.prototype`, `Set.prototype`, etc.) regardless of how they're obtained 3. **Defense in depth:** Consider freezing built-in prototypes before running untrusted code ### Testing Recommendations Add regression tests that: - Attempt prototype pollution via `[Map.prototype][0]`, `[Set.prototype][0]`, etc. - Verify the `isGlobal` protection is maintained through array/object literals - Test method overwriting attempts on built-in prototypes ## Additional Notes ### Idempotency Confirmation The reproduction script has been verified to pass two consecutive runs: - Run 1: All tests passed - Run 2: All tests passed The script includes cleanup to remove prototype pollution after testing (`delete Map.prototype.polluted`, etc.), ensuring idempotent behavior. ### Edge Cases - Other built-in prototypes (`Array.prototype`, `Object.prototype`, `Promise.prototype`, etc.) may also be vulnerable via the same vector - Object literals (not just arrays) may have similar taint-stripping issues - The vulnerability requires the sandbox to have access to global prototypes, which is the default configuration ### Related Identifiers - **CVE:** CVE-2026-25881 - **GHSA:** GHSA-ww7g-4gwx-m7wj - **NVD:** https://nvd.nist.gov/vuln/detail/CVE-2026-25881 ## Reproduction Details Reproduced: 2026-02-19T19:47:55.006Z Duration: 961 seconds Tool calls: 116 Turns: 93 Handoffs: 3 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00098 pruva-verify GHSA-ww7g-4gwx-m7wj pruva-verify CVE-2026-25881 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00098&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00098/artifacts/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - GitHub Advisory: https://github.com/advisories/GHSA-ww7g-4gwx-m7wj - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-25881 ## Artifacts - repro/reproduction_steps.sh (reproduction_script, 3979 bytes) - repro/rca_report.md (analysis, 6070 bytes) - bundle/ticket.json (other, 13725 bytes) - bundle/ticket.md (ticket, 5407 bytes) - bundle/source.json (other, 7683 bytes) - repro/test_repro.js (other, 2842 bytes) - repro/package.json (other, 279 bytes) - logs/variant_run.log (log, 10966 bytes) - logs/vuln_test.log (log, 4582 bytes) - logs/fixed_test.log (log, 4913 bytes) - logs/reproduction.log (log, 959 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00098 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00098/artifacts/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00098 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev