CVE-2026-92952: vm2 Incomplete Nodejs.* Symbol Filtering Lets Vulnerability
MODERATECVE-2026-92952 is a MODERATE (CVSS 6.8) incomplete nodejs.* symbol filtering lets vulnerability (CWE-669: Incorrect Resource Transfer Between Spheres) affecting vm2 libraries in the npm ecosystem. The issue involves incomplete nodejs.* symbol filtering lets sandbox override host webstream state checks. Remediation is available by upgrading affected packages to version 3.11.7.
CVE-2026-92952 at a Glance
Security Impact
CVSS: 6.8
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:H/A:N).
How to Fix CVE-2026-92952
To remediate this vulnerability, upgrade all affected vm2 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 vm2@3.11.7
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
npm ls vm2
How to Check if You Are Affected
To determine whether your Node.js application is affected by CVE-2026-92952, inspect your project dependencies using npm:
npm ls vm2
Analyze the terminal output to locate any version of vm2 in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 3.11.7), your application is affected.
Note: The npm ls vm2 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-669: Incorrect Resource Transfer Between Spheres, where the product does not properly transfer a resource/behavior to another sphere, or improperly imports a resource/behavior from another sphere, in a manner that provides unintended control over that resource. In vm2, 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-92952 Affected vm2 Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
vm2
|
npm |
≥ 3.11.4
< 3.11.7
|
3.11.7
|
References
Frequently Asked Questions
What is CVE-2026-92952?
Which vm2 versions are affected?
What version fixes CVE-2026-92952?
Is vm2 affected?
What is the CVSS score of CVE-2026-92952?
What CWE is associated with CVE-2026-92952?
How can I check npm dependencies for CVE-2026-92952?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-jf8q-945g-9q4c",
"modified" : "2026-10-01T15:42:15Z",
"published" : "2026-10-01T15:42:15Z",
"aliases" : [ "CVE-2026-92952" ],
"summary" : "vm2: Incomplete nodejs.* symbol filtering lets sandbox override host WebStream state checks",
"details" : "## Summary\n\nvm2 current head (`v3.11.5`, commit `7a1f5100b96f48d34e0fe104ab37c0acc5944f92`) still exposes registered Node.js internal symbols from host WebStream prototypes to sandbox code.\n\nThe prior `nodejs.*` symbol hardening blocks `Symbol.for('nodejs.<name>')` at the source, but the extraction filters and bridge write traps still enumerate a fixed set of known registered symbols. On Node.js `v25.8.0`, `stream/web` exposes two additional registered symbols:\n\n- `nodejs.stream.disturbed`\n- `nodejs.stream.errored`\n\nSandbox code can extract those real host symbols with `Object.getOwnPropertySymbols(streamWeb.ReadableStream.prototype)` and then use them as write keys on host objects. On a real host `ReadableStream`, an attacker can make `stream.Readable.isDisturbed(stream)` return `false` after the stream has already been read.\n\n## Technical Details\n\n`lib/setup-sandbox.js` correctly blocks future `nodejs.*` keys at the `Symbol.for()` source:\n\n```js\nif (apply(localStringStartsWith, keyStr, ['nodejs.'])) {\n ...\n return fresh;\n}\n```\n\nHowever, the extraction filters are still driven by a fixed `realDangerousSymbols` list. That list does not include `nodejs.stream.disturbed` or `nodejs.stream.errored`, so `Object.getOwnPropertySymbols()` and related paths can still return those real host symbols.\n\n`lib/bridge.js` has the same fixed-list problem in `isDangerousCrossRealmSymbol()` and in the host-result scrub list. Because the two new symbols are not recognized, the `set` and `defineProperty` traps allow sandbox-originated writes using those keys.\n\nCurrent-head source references:\n\n- `lib/setup-sandbox.js:180-196` denies `Symbol.for('nodejs.*')` by namespace.\n- `lib/setup-sandbox.js:214-230` uses a fixed `realDangerousSymbols` list for extraction filtering; the two reported symbols are absent.\n- `lib/bridge.js:187-199` uses a fixed `isDangerousCrossRealmSymbol()` list; the two reported symbols are absent.\n- `lib/bridge.js:1499-1509` treats the write trap as the last line of defense, but it only rejects keys recognized by that fixed list.\n\n## Impact\n\nSandbox code can corrupt host-visible WebStream state checks for host WebStream objects that cross into the sandbox. In the validated PoV, a stream that the host has already consumed is made to appear undisturbed to `stream.Readable.isDisturbed()`.\n\nThis can bypass host logic that relies on Node's public stream-state helpers to enforce one-shot body consumption, reject errored streams, or decide whether a host WebStream is safe to hand to another component.\n\nThis is not a host-code-execution primitive in the current PoV. The report is an incomplete-fix / guard-coverage gap in the same symbol-boundary family as the prior `nodejs.*` symbol advisory.\n\n## Affected Package/Versions\n\nConfirmed affected on Node.js `v25.8.0`:\n\n- `v3.11.4`\n- `v3.11.5`\n- current head `7a1f5100b96f48d34e0fe104ab37c0acc5944f92`\n\n`v3.11.3` is also affected, but it predates the broader `nodejs.*` symbol fix. For this incomplete-fix report, the suggested affected range is `>= 3.11.4, <= 3.11.5` on Node.js versions where these stream symbols exist.\n\nNo patched version is known.\n\n## Configuration Required\n\nThe PoV uses a `VM` where the embedder exposes a host WebStream object and the host `stream/web` module object to sandbox code:\n\n```js\nconst vm = new VM({ sandbox: { rs, streamWeb } });\n```\n\nThis matches the same trust boundary as the earlier cross-realm symbol-write class: sandbox code must not be able to obtain registered Node.js internal symbols and write them back onto host objects.\n\nThe PoV is local-only. It does not require network access, a public target, `NodeVM` builtin access, `process`, filesystem access, or child-process access.\n\n## Local Proof of Concept\n\nRun from the `oss-zero-day-harness` directory:\n\n```sh\nnode submission-bundle/vm2-pov-test-incomplete-nodejs-stream-symbol-filter/pov-nodejs-stream-symbol-incomplete-fix.js\n```\n\nThe PoV:\n\n1. The host creates a `ReadableStream`.\n2. The host reads one chunk so `stream.Readable.isDisturbed(rs)` is `true`.\n3. Sandbox code confirms `Symbol.for('nodejs.stream.disturbed')` is blocked and returns a sandbox-local symbol.\n4. Sandbox code extracts the real registered `nodejs.stream.disturbed` / `nodejs.stream.errored` symbols from the host `ReadableStream.prototype`.\n5. Sandbox code writes an own `nodejs.stream.disturbed` property onto the host stream with value `false`.\n6. The host calls `stream.Readable.isDisturbed(rs)` again and receives `false`.\n\nObserved result on current head:\n\n```json\n{\n \"beforeHostDisturbed\": true,\n \"controls\": {\n \"symbolForDisturbedIsRegistered\": false,\n \"symbolForErroredIsRegistered\": false\n },\n \"extracted\": [\n {\n \"description\": \"nodejs.stream.disturbed\",\n \"keyFor\": \"nodejs.stream.disturbed\"\n },\n {\n \"description\": \"nodejs.stream.errored\",\n \"keyFor\": \"nodejs.stream.errored\"\n }\n ],\n \"overrideDisturbedOk\": true,\n \"afterHostDisturbed\": false\n}\n```\n\n## Official Disclosure Policy Fit\n\nvm2's `SECURITY.md` asks reporters not to create a public issue and to submit It asks for reproduction steps, affected versions, environment/configuration details, and potential impact:\n\n- Policy: https://github.com/patriksimek/vm2/blob/main/SECURITY.md\n- Private report route: https://github.com/patriksimek/vm2/security/advisories/new\n\nThis bundle is formatted for that private GitHub report flow and should not be posted publicly before maintainer triage and a fixed release.\n\n## Suggested Fix Direction\n\nMake the dangerous-symbol checks namespace-based instead of list-based:\n\n- In `setup-sandbox.js`, make `isDangerousSymbol(sym)` return true for any registered symbol whose `Symbol.keyFor(sym)` starts with `nodejs.`.\n- In `bridge.js`, make `isDangerousCrossRealmSymbol(key)` do the same for any symbol key crossing the bridge.\n- In host-result scrubbing, delete all own symbol keys whose registered key starts with `nodejs.` instead of iterating a hard-coded list.\n- Keep the current explicit list only as regression documentation, not as the complete security boundary.\n\nRegression tests should include:\n\n- `Object.getOwnPropertySymbols(ReadableStream.prototype)` must not expose `nodejs.stream.disturbed` or `nodejs.stream.errored`.\n- A sandbox-local `Symbol.for('nodejs.stream.disturbed')` write must not affect host `stream.Readable.isDisturbed()`.\n- Even if the real symbol is passed into the sandbox by a host test harness, `set`, `defineProperty`, and `deleteProperty` traps must reject writes and deletes against host objects.\n\n## Why This Is Not Intended Behavior\n\nThe hardening comments and tests establish the intended invariant:\n\n- any `nodejs.*` internal symbol should be sandbox-local when requested through `Symbol.for()`;\n- dangerous registered symbols should not be enumerable/extractable from host objects;\n- even if a sandbox obtains one, bridge write traps should reject writes using that key.\n\nThis report shows that the source-side rule is active, but the extraction and write-trap rules are incomplete for newer registered `nodejs.stream.*` symbols.\n\nNode's stream state helpers consult these symbols directly. For example, `stream.Readable.isDisturbed()` reads the internal disturbed symbol before falling back to public state. After sandbox writes an own property under the extracted symbol, the host helper returns attacker-controlled state.\n\nNode's public documentation describes `stream.isErrored(stream)` as reporting whether a stream has encountered an error, and `stream.Readable.isDisturbed(stream)` as reporting whether the stream has been read from or cancelled:\n\n- https://nodejs.org/api/stream.html#streamiserroredstream\n- https://nodejs.org/api/stream.html#streamreadableisdisturbedstream",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:H/A:N"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "npm",
"name" : "vm2"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "3.11.4"
}, {
"fixed" : "3.11.7"
} ]
} ],
"database_specific" : {
"last_known_affected_version_range" : "<= 3.11.6"
}
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/patriksimek/vm2/security/advisories/GHSA-jf8q-945g-9q4c"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-92952"
}, {
"type" : "WEB",
"url" : "https://github.com/patriksimek/vm2/commit/a45444d5abbdfa59055528f584df5f21cc7c42da"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/patriksimek/vm2"
}, {
"type" : "WEB",
"url" : "https://github.com/patriksimek/vm2/releases/tag/v3.11.7"
}, {
"type" : "WEB",
"url" : "https://www.vulncheck.com/advisories/vm2-3.11.4-through-3.11.6-sandbox-symbol-filtering-bypass"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-669" ],
"severity" : "MODERATE",
"github_reviewed" : true,
"github_reviewed_at" : "2026-10-01T15:42:15Z",
"nvd_published_at" : null
}
}