vm2: NodeVM builtin:['*'] Exposes os and dns — Host Process Exposure From the Sandbox
A critical vulnerability in the vm2 npm package breaks the sandbox isolation that NodeVM is designed to enforce. When configured with builtin: ['*'], two Node.js built-in modules — os and dns — slip through the dangerous-builtin filter. Both expose host-process state that the vm2 boundary cannot contain, and both carry write APIs that mutate global host-process state from the sandbox.
Description
A critical vulnerability affects the vm2 npm package when NodeVM is configured with builtin: ['*'] — the documented "full builtins" pattern recommended in the README and used in the test fixtures. Under this configuration, two Node.js built-in modules — os and dns — are admitted into the sandbox even though they expose host-process state that the vm2 boundary cannot contain.
Both modules are reachable from inside the sandbox. Both expose state belonging to the entire host process rather than sandbox-local state. And both carry write APIs that mutate global host-process state from the sandbox: os.setPriority(), dns.setServers(), and dns.setDefaultResultOrder().
The issue is tracked as GHSA-m5w8-4gq2-6f8x and rated Critical. It is classified as a sibling of GHSA-9g8x-92q2-p28f, the prior advisory that closed the "process-wide observability builtins" class but left these two modules out of the fix.
An attacker must first be able to execute attacker-controlled JavaScript inside the NodeVM sandbox. In the vulnerable configuration, the attack flow is:
Attacker-controlled JavaScript → executed inside vulnerable NodeVM →builtin: ['*']exposesos/dns→ host information disclosure or DNS manipulation.
What Happened
The prior advisory, GHSA-9g8x-92q2-p28f, closed a class of "process-wide observability builtins" by adding diagnostics_channel, async_hooks, perf_hooks, and v8 to the DANGEROUS_BUILTINS set in lib/builtin.js. The fix's rationale, documented in the commit message and docs/ATTACKS.md Category 35, was general:
Process-wide observability builtins. Unlike most Node builtins, these expose state of the entire host process rather than sandbox-local state — the vm2 boundary cannot usefully contain them because the data they surface […] belongs to the embedder. Even a readonly proxy that forwards every call to the host module is a working host-data exfiltration primitive.
Two builtins satisfying the exact same description were not added: os and dns. Both are reachable today under the documented builtin: ['*'] configuration. Both expose host-process state that the vm.readonly() proxy cannot localise. And both have write APIs that mutate global host-process state from the sandbox.
The dns.setServers() call in particular turns sandbox code into a process-wide DNS hijack primitive — the advisory describes it as strictly worse than every read-only leak that GHSA-9g8x added.
The fix adds os and dns to DANGEROUS_BUILTINS, extending the same protection to the rest of the class. The existing isDangerousBuiltin(key) family-prefix matcher, introduced by GHSA-rp36-8xq3-r6c4, automatically catches node:os, node:dns, and node:dns/promises once the family names are present.
Why This Matters
The purpose of vm2 is to run JavaScript inside a sandbox and isolate it from the host environment. In the affected configuration, os and dns can access or modify information belonging to the host process. This means the sandbox does not provide the expected isolation for these particular capabilities.
The most significant capability described by the advisory is dns.setServers(), which can modify the DNS resolver configuration used by the host process. Subsequent DNS lookups made by that process can be directed through the configured resolver.
Having a vulnerable vm2 version alone does not mean the application is directly exploitable. The application must actually use the affected NodeVM configuration (builtin: ['*']) and provide a way for attacker-controlled JavaScript to reach the sandbox.
Affected Packages
| Package | Ecosystem | Affected versions | Fixed version |
|---|---|---|---|
vm2 | npm | <= 3.11.5 | >= 3.11.6 |
The vulnerability affects all NodeVM configurations that expand the builtin allowlist via '*' and have not manually appended -os or -dns exclusions — which is the recommended configuration in the README and the test fixtures.
- Advisory ID: GHSA-m5w8-4gq2-6f8x
- Severity: Critical
- CWE: CWE-200 (Exposure of Sensitive Information to an Unauthorized Actor), CWE-732 (Incorrect Permission Assignment for Critical Resource), CWE-285 (Improper Authorization)
Potentially Affected Scenarios
The advisory is relevant when both of the following conditions exist:
- The application uses
NodeVMwithbuiltin: ['*']. - Attacker-controlled JavaScript can be executed inside that
NodeVMsandbox.
Environments where untrusted or less-trusted JavaScript is executed inside NodeVM with the full builtins configuration are at risk. A vulnerable version of vm2 alone does not establish that the application is directly exploitable.
Attack Details
The attack requires attacker-controlled JavaScript to execute inside the affected NodeVM. The simplified attack flow is:
Attacker-controlled JavaScript → NodeVM sandbox →builtin: ['*']→ access toos/dns→ host information disclosure or DNS manipulation.
How the modules reach the sandbox
Under builtin: ['*'], the BUILTIN_MODULES list in lib/builtin.js is built by filtering Node.js's own builtinModules against the DANGEROUS_BUILTINS set:
const BUILTIN_MODULES = (nmod.builtinModules || Object.getOwnPropertyNames(process.binding('natives')))
.filter(s => !s.startsWith('internal/') && !s.startsWith('_') && !isDangerousBuiltin(s));
The DANGEROUS_BUILTINS set at the time of the vulnerability:
const DANGEROUS_BUILTINS = new Set([
'module', 'worker_threads', 'cluster', 'vm', 'repl',
'inspector', 'process', 'trace_events', 'wasi',
// GHSA-9g8x-92q2-p28f:
'diagnostics_channel', 'async_hooks', 'perf_hooks', 'v8'
]);
os and dns are absent. Under builtin: ['*'] they are admitted into the user-visible builtin map and loaded via the default vm.readonly(hostRequire(key)) path:
builtins.set(key, special ? special : vm => vm.readonly(hostRequire(key)));
The readonly proxy forwards every method call to the host realm. For modules whose entire purpose is to read or mutate host-process state, the readonly wrap protects nothing — the same observation the GHSA-9g8x commit message makes for v8 and perf_hooks.
Advisory-identified capabilities
The advisory identifies the following capabilities exposed through these modules:
os.userInfo()— exposes host username, UID, GID, home directory and shellos.networkInterfaces()— exposes host network interfaces, including IP and MAC informationos.hostname()— exposes the host hostnameos.loadavg(),os.uptime(),os.freemem()andos.totalmem()— expose host system informationos.setPriority()— can modify the host process prioritydns.getServers()— exposes configured DNS serversdns.setServers()— modifies the DNS resolver list used by the host processdns.setDefaultResultOrder()— modifies DNS result ordering
Host Capabilities Exposed
os module
The os module can expose information about the actual host process, including:
- Username
- UID/GID
- Home directory
- Shell
- Hostname
- Network interfaces
- IP addresses
- MAC addresses
- System memory and uptime information
It also provides os.setPriority(), which can modify the host process priority.
dns module
The dns module can expose or modify DNS configuration belonging to the host process. Most importantly, dns.setServers() can replace the host process's DNS resolver list, meaning subsequent DNS lookups made by that process can be sent through the configured resolver.
The advisory describes dns.setServers() as a process-wide write primitive and identifies it as strictly worse than every read-only leak that the prior GHSA-9g8x fix addressed.
The dns/promises subpath also exists and shares the same module surface. Adding dns to DANGEROUS_BUILTINS automatically catches dns/promises via the existing family-prefix matcher.
What Data Is at Risk
Based directly on the advisory, the exposed information includes:
- Host identity: username, UID, GID, home directory and shell.
- Network information: host network interfaces, IP addresses and MAC addresses.
- Host information: hostname, uptime, memory and load information.
- DNS configuration: configured DNS servers.
The advisory also identifies DNS manipulation as an integrity risk because dns.setServers() can change the resolver configuration used by the host process.
Risks
The primary risks identified by the advisory are:
1. Host Information Disclosure
An attacker executing JavaScript inside the affected sandbox can access information belonging to the host process through the os module.
2. Network Topology Disclosure
os.networkInterfaces() can expose the host's network interfaces, including IP and MAC addresses.
3. Process-Wide DNS Manipulation
dns.setServers() can modify the DNS resolver list used by the host process. Subsequent DNS lookups can therefore be directed through the configured resolver. The advisory describes this as a process-wide DNS hijack primitive — strictly worse than every read-only leak that GHSA-9g8x added.
4. Host Process Priority Modification
os.setPriority() can modify the priority of the host process.
Recommended Actions
Upgrade vm2
Upgrade from the affected versions to:
vm2 3.11.6 or later.
The fix adds os and dns to DANGEROUS_BUILTINS, closing the class. The existing family-prefix matcher automatically covers node:os, node:dns, and node:dns/promises.
Verify Application Configuration
Check whether the application actually uses:
NodeVM({
require: {
builtin: ['*']
}
})
Also verify whether attacker-controlled JavaScript can reach and execute inside the NodeVM. A vulnerable version of vm2 alone does not establish that the application is directly exploitable.
Register Safe Wrappers If Needed
Embedders who genuinely need a sandbox-local os or dns (typically os.platform(), os.EOL, os.constants) can register a hand-written safe wrapper under those names via the mock / override escape hatch, mirroring the approach documented for the GHSA-9g8x denials.
If Exploitation Is Suspected
The advisory recommends reviewing the environment and rotating credentials if exploitation is suspected, particularly because DNS manipulation can affect subsequent host-process DNS resolution.
The Fix
The fix adds os and dns to DANGEROUS_BUILTINS:
const DANGEROUS_BUILTINS = new Set([
'module', 'worker_threads', 'cluster', 'vm', 'repl',
'inspector', 'process', 'trace_events', 'wasi',
'diagnostics_channel', 'async_hooks', 'perf_hooks', 'v8',
// SECURITY (this advisory):
// `os.userInfo()` / `os.networkInterfaces()` leak host process identity
// and network topology in the same class as the GHSA-9g8x readers.
// `os.setPriority()`, `dns.setServers()`, and `dns.setDefaultResultOrder()`
// are write primitives that mutate host-process state from the sandbox.
// `dns.setServers()` is a process-wide DNS resolver hijack reachable in
// one line of sandbox code.
'os', 'dns'
]);
A vulnerable version of vm2 alone does not establish that the application is directly exploitable. The application must actually use the affected NodeVM configuration and provide a way for attacker-controlled JavaScript to reach it.
QIs my application exploitable just because it uses vm2?▼
NodeVM with builtin: ['*'], and attacker-controlled JavaScript can be executed inside that NodeVM sandbox. A vulnerable version of vm2 alone does not establish that the application is directly exploitable.QWhy are os and dns dangerous in the sandbox?▼
os and dns can access or modify information belonging to the host process. This means the sandbox does not provide the expected isolation for these particular capabilities. os exposes host identity, network interfaces, and system information, while dns.setServers() can modify the DNS resolver configuration used by the host process.QWhat is dns.setServers() and why is it significant?▼
dns.setServers() is the most significant capability described by the advisory. It can replace the host process's DNS resolver list, meaning subsequent DNS lookups made by that process can be sent through the configured resolver. The advisory describes it as a process-wide DNS hijack primitive — strictly worse than every read-only leak that the prior GHSA-9g8x fix addressed.QWhat is the relationship between this advisory and GHSA-9g8x-92q2-p28f?▼
diagnostics_channel, async_hooks, perf_hooks, and v8 to DANGEROUS_BUILTINS. This advisory (GHSA-m5w8-4gq2-6f8x) is a sibling: it identifies that os and dns satisfy the exact same description but were not added in the prior fix. Adding them now extends the same protection to the rest of the class.QI need os.platform() inside my sandbox. Will the fix break my application?▼
builtin: ['*'] to expose the full os module. The fix adds os to DANGEROUS_BUILTINS, which blocks it entirely. The escape hatch is to register a hand-written safe wrapper under the os name via the mock / override configuration, exposing only the specific functions you need (such as os.platform(), os.EOL, or os.constants) without granting access to os.userInfo() or os.networkInterfaces().