Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
The vm2 escape people are calling a “9.5 sandbox escape” is an authorization failure in NodeVM’s custom module resolver. After the resolver approved a module such as foo, the check that decided which files could load used a raw string prefix. A sibling directory named foo2 therefore passed the same check. When the VM ran with context: 'host', the sibling’s top-level code executed with host authority. The fix is in vm2 v3.12.2, released September 8, 2026, and the flaw only matters for deployments that use the specific configuration described below.
Which vm2 setups are exposed
The vulnerable path requires all of the following. If any one is missing, this defect does not reach your code through this route.
- You construct a
NodeVMinstance and configure external modules throughrequire.external. - You supply a custom resolver, so the embedder decides what a module specifier resolves to.
- You set a root directory for module loading.
- You run the VM with
context: 'host'. - Guest code controls the module specifiers it requires, and a file exists whose path begins with the same characters as an allowlisted module’s resolved path.
Default vm2 installations are not universally exploitable by this route. A sandbox that only runs trusted code, or that never allows host-context loading, is outside the described scenario.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsThis is a different defect from the 2023 exception-sanitization escape in vm2. Do not treat a fix for one as a fix for the other.
#1 Best Overall
The missing boundary
According to the vm2 maintainer advisory GHSA-5h3f-q97h-ccvc, LegacyResolver.customResolve recorded an embedder-resolved path as ^<path>. The pattern had no path separator and no end-of-string anchor after the path. A pattern built that way matches anything that starts with the same characters.
The following illustration shows the shape of the problem. It is not vm2’s source code.
const allowed = '/app/modules/foo';
const pattern = new RegExp('^' + allowed);
pattern.test('/app/modules/foo2/index.js'); // true
Rank #2
The check authorizes the file foo2/index.js even though the embedder only approved foo. The authorization question is “is this exact module or a file inside it?”, and a prefix match answers a different question: “does this string begin with that string?”
How the escape plays out
The advisory describes the sequence below. Each step depends on the previous one, which is why the bug depends on the order of requires.
- Guest code requires the allowlisted bare module
foo. The custom resolver approves it and the resolved path is recorded as^<path>. - Guest code then requires the absolute path of a sibling, such as
foo2/index.js, in the same parent directory. - The recorded prefix matches the sibling’s path, so vm2 treats it as authorized.
- Because the VM runs with
context: 'host', vm2 loads the sibling throughhostRequire. Its top-level code runs with host authority before vm2 wraps the exports.
In the maintainer’s proof of concept, the allowlisted package returns FOO_OK. The sibling returns PREFIX_PWN after host-side child_process execution. A negative control that skips the earlier custom resolution is denied with ENOTFOUND. These results come from the maintainer’s reproduction and have not been independently re-run for this article.
Rank #3
Affected versions and what is confirmed
The advisory metadata lists versions through 3.12.1 as affected and 3.12.2 as patched. The detailed body is narrower. Its direct testing covered only a pinned source revision beginning 91034466, identified there as vm2 3.11.8. The advisory states that no patched revision was identified in that tested evidence.
The vm2 v3.12.2 release notes, dated September 8, 2026, state that the release closes GHSA-5h3f-q97h-ccvc. They describe the fix as recording resolver answers as boundary-matched base paths, with exact extension spellings for extension-probed answers. The release is labeled a patch release with no API changes.
In practice, treat the 3.12.1 upper bound in the metadata as the published range, and treat 3.11.8 as the only version the advisory directly tested. Do not read the advisory as having confirmed the behavior across every version in between.
Rank #4
Severity, the 9.5 figure, and the CVE question
The headline’s “9.5” is not the score in the maintainer advisory. GHSA-5h3f-q97h-ccvc classifies the issue as CWE-863, Incorrect Authorization, and reports a CVSS 3.1 base score of 10.0 with changed scope and high confidentiality, integrity, and availability impact. The advisory page also says “No known CVE.”
A separate article published October 4, 2026 uses the identifier CVE-2026-100721 and the 9.5 score. The advisory does not confirm that identifier or that score. If you need a figure for a risk register, cite the advisory’s CVSS 3.1 10.0 and note the discrepancy.
Fixing an affected deployment
- Confirm that your code uses
NodeVMwith a custom resolver,require.external, a root directory, andcontext: 'host'. If it does not, this defect is not in your execution path. - Upgrade vm2 to v3.12.2 or later. Check the project’s current release guidance before you roll it out.
- Review your resolver. Confirm that it authorizes the exact resolved path and descendants that start after a path separator. A raw string-prefix test is not a safe authorization boundary.
- Add regression tests for both custom-resolver return forms: a plain string path and an object of the form
{path: resolvedPath}.
Writing a boundary-aware check
The table compares the vulnerable pattern with a path-aware check across the four criteria the advisory’s guidance implies: the exact resolved path, descendants only after a separator, normalization, and coverage of both return forms.
| Criterion | Raw string prefix (vulnerable) | Boundary-aware check |
|---|---|---|
Exact resolved path, e.g. /app/modules/foo |
Allowed | Allowed |
Legitimate descendant, e.g. /app/modules/foo/lib/index.js |
Allowed | Allowed, because the next character is a separator |
Sibling sharing a prefix, e.g. /app/modules/foo2/index.js |
Allowed (the escape) | Denied |
| Normalization | Not addressed by the check; paths that differ only in form can slip past | Normalize both paths before comparison, for example with path.resolve |
Both return forms (string and {path: resolvedPath}) |
Only as safe as each branch | Same check applied to both branches |
A workable implementation uses the platform’s separator, so Windows and POSIX paths behave correctly:
import path from 'node:path';
function isAuthorized(allowed, candidate) {
const base = path.resolve(allowed);
const target = path.resolve(candidate);
return target === base || target.startsWith(base + path.sep);
}
Regression test cases
The advisory recommends coverage for both return forms. Use this matrix, and run each case before and after a prior resolution of foo:
Free tools Windows power users keep installed
One-click scans. No signup required.
- The exact allowlisted module loads.
- A legitimate descendant of the allowlisted module loads.
- A sibling that shares the prefix, such as
foo2, is denied. - A request made without the preceding custom resolution is denied.
The sibling case is the one that exposes the bug. If it passes after the earlier resolution, the boundary is still missing.
What this does and does not establish
The advisory describes a single mechanism and a single demonstrated outcome. It does not provide a prevalence figure, and no source here measures how many deployments use the vulnerable configuration. Your exposure depends on whether your own NodeVM setup matches the preconditions listed at the top of this article.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

