CVE-2026-92945: vm2 before 3.11.7 Module Allowlist Bypass via Prefix Matching
Summary
isPathAllowedForModule decides whether a resolved path belongs to an allowlisted external module using a raw string prefix test. nodemodules/foo2 starts with nodemodules/foo, so a package whose name merely shares a prefix with an allowlisted one is treated as being inside it, and a relative require from the allowlisted package reaches it even with transitive loading disabled.
Where it is
lib/resolver-compat.js, lines 122 to 132, quoted from HEAD 7a1f5100b96f48d34e0fe104ab37c0acc5944f92:
js isPathAllowedForModule(path, mod) { if (!super.isPathAllowed(path)) return false; if (mod) { if (mod.allowTransitive) return true; if (path.startsWith(mod.path)) { const rem = path.slice(mod.path.length); if (!/(?:^|[\\/])nodemodules(?:$|[\\/])/.test(rem)) return true; } } return this.externals.some(regex => regex.test(path)); }
With mod.path of .../nodemodules/foo and a resolved path of .../nodemodules/foo2/index.js, startsWith is true and rem is 2/index.js, which contains no nodemodules segment, so the function returns true.
The nodemodules test in rem is what stops a genuine transitive dependency from slipping through. It does not stop a sibling, because a sibling's remainder never contains that segment.
Impact
Code running in NodeVM under an external module allowlist with transitive: false can reach a package that was not allowlisted, provided an allowlisted package performs a relative require to a prefix-sharing sibling.
Two preconditions are worth stating plainly rather than leaving implicit. The deployment must already have such a package layout, and an allowlisted package must have a reachable code path that does the relative require. This is not something the attacker creates; it is something they find. That narrows it considerably, and it is why I have not scored it higher.
Reachability
NodeVM.run at lib/nodevm.js:506 executes the script. require comes from createRequireForModule at lib/setup-node-sandbox.js:168-172 and reaches the resolver callback at lib/nodevm.js:380-384. LegacyResolver.resolveFull at lib/resolver-compat.js:145-160 sets currMod for direct requires, the relative specifier resolves through DefaultResolver.resolveFull and tryFile at lib/resolver.js:327-330, and the authorization decision lands on the function above.
Suggested fix
Require a separator after the prefix, so a sibling cannot match:
js if (path === mod.path || path.startsWith(mod.path + path.sep)) {
That is the same anchoring the rem regex already applies to nodemodules, applied one level earlier.
Other sources
vm2 before 3.11.7 contains a module allowlist bypass vulnerability in isPathAllowedForModule that uses raw string prefix matching instead of boundary-anchored comparison. Attackers can reach non-allowlisted packages sharing a prefix with allowlisted modules by performing relative requires from allowlisted packages when transitive loading is disabled.
— MITRE
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
npm/vm2to a version that resolves this vulnerability.Fixed in 3.11.7 - Upgrade
Upgrade
vm2to a version that resolves this vulnerability.Fixed in 3.11.7
Event History
Frequently Asked Questions
Which deployments are exposed to this issue?
Deployments using vm2 versions before 3.11.7 are exposed when they use a module allowlist with transitive loading disabled. Exploitation also depends on a non-allowlisted package sharing a prefix with an allowlisted module.
What does an attacker need to exploit the bypass?
The attacker needs to perform relative requires from an allowlisted package. The bypass relies on reaching a non-allowlisted package whose name shares a raw string prefix with an allowlisted module.
How can I determine whether my configuration is at risk?
Check whether vm2 is earlier than 3.11.7, whether transitive loading is disabled, and whether your allowlisted module names have non-allowlisted packages sharing their prefixes. Configurations meeting all of these conditions are affected.