Normalize the node: prefix on negative builtin deny tokens
fix(GHSA-8686-vhfx-7r3j): normalize the node: prefix on negative builtin deny tokens
Normalize the node: prefix on negative builtin deny tokensb0f506-fix-normalize-the-node-prefix-on-negative-cve-2026-92957fixnormalize node prefix negative builtin deny
Committed by patriksimek-bot
commit message
fix(GHSA-8686-vhfx-7r3j): normalize the node: prefix on negative builtin deny tokens
Root cause: `makeBuiltinsFromLegacyOptions` expanded `builtin: ['*']` by
iterating the canonical `BUILTIN_MODULES` names and matching negative deny
tokens by exact string (`builtins.indexOf('-' + name)`). `BUILTIN_MODULES`
holds un-prefixed names, so a `node:`-spelled deny token such as
`-node:child_process` matched nothing and denied nothing — silently. The
embedder's config read as a denial while the sandbox received the real host
`child_process` (`execSync` / `spawn` → host RCE). The canonical
`-child_process` spelling worked, which is what made the gap invisible.
Fix (lib/builtin.js): the `'*'` wildcard deny check now tests both
`-${name}` and `-node:${name}`, so the two spellings of a deny token are
equivalent and either denies both spellings of the module. This is the
deny-side mirror of the `node:`-prefix normalization the resolver and
`isDangerousBuiltin` already perform on the require side. It restores
Invariant 13 — the NodeVM builtin allowlist is a closed system — and can
only ever remove a builtin the exact-match check would have added, so no
previously-reachable module becomes unreachable.
Composes with the member-sanitizer layer landed in aa146a7: the deny check
decides whether `addDefaultBuiltin` is called at all, the sanitizer decides
which members of a registered module are neutralized. Disjoint gates, no
shared state; verified that `-node:child_process` denies both spellings
while crypto/tls/http/https/sqlite sanitizers still fire under `['*']`.
Tests: test/ghsa/GHSA-8686-vhfx-7r3j/repro.js — both `child_process`
spellings denied under `-node:child_process`, a canonical `-child_process`
control, and a no-over-deny check on benign builtins.
Docs: docs/ATTACKS.md Category 21 gains the deny-token-spelling canonical
example, a "Why It Works" paragraph on the silent no-op, mitigation item 5,
and a detection rule; a row is added to "How The Bridge Defends".
No version bump.Editor assessment
This commit patches CVE-2026-92957. The body explains the silent no-op: -node:child_process matched nothing because BUILTIN_MODULES holds unprefixed names, so the sandbox received the real host child_process and one execSync was host RCE. It describes the dual-spelling deny check, its composition with member sanitizers, and the tests. The only formal flaw is the GHSA-id scope, which is an advisory identifier rather than a codebase area. The asymmetry between canonical and node:-prefixed deny tokens is the real lesson; a sentence on how it arose would help future maintainers.