CVE-2026-92945: vm2 before 3.11.7 Module Allowlist Bypass via Prefix Matching

Published Sep 17, 2026
·
Updated

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

2 affected componentsFixes available
npm/vm2<3.11.7
npm/vm2<=3.11.6
3.11.7

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade npm/vm2 to a version that resolves this vulnerability.

    Fixed in 3.11.7
  2. Upgrade

    Upgrade vm2 to a version that resolves this vulnerability.

    Fixed in 3.11.7

Event History

Sep 17, 2026
CVE Published
via MITRE·01:46 PM
Data Sourced
via MITRE·01:46 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·02:17 PM
DescriptionSeverityWeakness
Oct 1, 2026
Advisory Published
via GitHub·03:26 PM
Data Sourced
via GitHub·03:26 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

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.

2

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.

3

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.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203