Skip to content

Added punycode to native modules & option to run without 'use strict;'#13

Merged
patriksimek merged 2 commits into
patriksimek:masterfrom
redsift:redsift-pull-request
Dec 11, 2015
Merged

Added punycode to native modules & option to run without 'use strict;'#13
patriksimek merged 2 commits into
patriksimek:masterfrom
redsift:redsift-pull-request

Conversation

@randalpinto

Copy link
Copy Markdown
Contributor

Added punycode to native modules & option to run without 'use strict;'

patriksimek added a commit that referenced this pull request Dec 11, 2015
Added punycode to native modules & option to run without 'use strict;'
@patriksimek patriksimek merged commit d14d02e into patriksimek:master Dec 11, 2015
@patriksimek

Copy link
Copy Markdown
Owner

Thanks.

patriksimek added a commit that referenced this pull request May 18, 2026
…ocess/inspector subpaths

The denylist in `lib/builtin.js` was exact-match, so two host-passthrough
families slipped through every enforcement layer:

  - `require('process').getBuiltinModule(name)` (Node 22+) reloads ANY core
    module — including `child_process` — regardless of the embedder's
    allow/deny configuration. Also exposes `binding`, `dlopen`,
    `_linkedBinding`, and the raw host `process.env`.
  - `require('inspector/promises').Session().post('Runtime.evaluate', ...)`
    evaluates attacker JS in the host realm. The subpath shape
    `inspector/promises` does not match the `inspector` denylist entry.

Both reached host RCE under the otherwise-correct configs
`builtin: ['*', '-child_process']` and `builtin: ['*', '-child_process',
'-inspector']`.

Structural fix in `lib/builtin.js`:

  - `DANGEROUS_BUILTINS` extended with `process`.
  - New `isDangerousBuiltin(key)` promotes the check to family-prefix
    matching: any `<family>/...` whose family is in the set is blocked. The
    `node:` URL-style prefix is stripped before matching so
    `node:process` and `node:inspector/promises` share fate with their
    canonical spellings.
  - Enforced at both `BUILTIN_MODULES` (closes the `'*'` wildcard expansion
    path) and `addDefaultBuiltin` (closes the explicit-allowlist path and
    the low-level `makeBuiltins` API).
  - `SPECIAL_MODULES` / `mocks` / `overrides` escape hatches preserved —
    embedders who genuinely need a stub for any blocked name can register a
    sandbox-safe replacement.

Supersedes GHSA-947f-4v7f-x2v8 (whose exact-match denylist missed both
families). Restores and tightens Defense Invariant #13 (NodeVM builtin
allowlist is a closed system) — added in this commit.

Tests: `test/ghsa/GHSA-rp36-8xq3-r6c4/repro.js` (175 lines) covers
`require('process').getBuiltinModule('child_process')`,
`require('inspector/promises').Session().post('Runtime.evaluate', ...)`,
the `node:` URL-style spellings, the explicit-allowlist path
(`builtin: ['process']` and `builtin: ['inspector/promises']`), the
low-level `makeBuiltins` API, and confirms `SPECIAL_MODULES` overrides
still work.

Docs: `docs/ATTACKS.md` Category 21 extended with the new attack flow,
both canonical PoCs, and the three-layer mitigation. New Defense
Invariant #13 added under the existing #12 (WebAssembly JSPI). New row
in the "How The Bridge Defends" table.

Version bump deferred — this lands alongside seven other advisories in
the pre-existing [3.11.4] release slot.
patriksimek added a commit that referenced this pull request May 18, 2026
…' wildcard expansion

NodeVM's `'*'` wildcard expansion in `lib/builtin.js` sourced the allowed
builtin list from `require('module').builtinModules`, filtered only against
`internal/*` and `DANGEROUS_BUILTINS`. Node's private implementation siblings
of network and stream modules — `_http_client`, `_http_server`, `_http_agent`,
`_http_common`, `_http_incoming`, `_http_outgoing`, `_tls_common`, `_tls_wrap`,
and the `_stream_*` family — passed both filters, so they were silently
admitted under the documented `builtin: ['*', '-http', '-https', '-net',
'-dgram', '-tls', '-dns', '-http2']` exclusion pattern.

This handed the sandbox the network primitives the embedder had explicitly
attempted to remove:

  - `require('_http_client').ClientRequest({host, port, path})` — outbound
    HTTP request, bypasses `-http`/`-https`.
  - `require('_http_server').Server(handler).listen(0)` — listening HTTP
    socket, bypasses `-net`.
  - `require('_tls_wrap').TLSSocket` — TLS primitives, bypasses `-tls`.

Not RCE — the underscored modules load through the normal `vm.readonly()`
wrap. The impact is capability bypass: SSRF-class access to localhost
services, cloud metadata endpoints, internal admin panels, and any other
network resource the host process can reach (CVSS 8.6).

Structural fix in `lib/builtin.js`:

  - `BUILTIN_MODULES` filter extended with `!s.startsWith('_')`. The `'*'`
    wildcard now expands only to documented public Node builtins.
  - Forward-compatible: any new underscored builtin Node introduces in a
    future release is automatically excluded without requiring a vm2
    release.
  - `addDefaultBuiltin` is unchanged, so explicit opt-in
    (`builtin: ['_http_client']`, `makeBuiltins(['_http_client'])`),
    `mocks`, and `overrides` registrations under underscored names continue
    to work — embedders who genuinely need an underscored sibling can still
    name it.

Complements the GHSA-rp36-8xq3-r6c4 fix that just landed (Category 21 /
Defense Invariant #13: host-passthrough primitives are unreachable under
any config). Together they enforce: "no host-passthrough primitive AND
no undocumented underscored sibling is reachable under `builtin: ['*']`."

Tests: `test/ghsa/GHSA-r9pm-gxmw-wv6p/repro.js` (202 lines) covers
`require('_http_client')`, `require('_http_server')`, `require('_tls_wrap')`,
the `node:`-prefixed spellings, the `_stream_*` family, the canonical
`ClientRequest` unreachability check, and confirms the explicit-opt-in /
`mock` / `override` escape hatches still function.

Docs: `docs/ATTACKS.md` gains Category 34 (renumbered from the branch's
provisional Category 30 — that slot is now occupied by GHSA-v6mx host
prototype mutation). New row in the "How The Bridge Defends" table.

Version bump deferred — this lands alongside the other advisories in the
pre-existing [3.11.4] release slot.
patriksimek added a commit that referenced this pull request May 18, 2026
… NodeVM

NodeVM's `require.builtin` allowlist defended against host-code-loading
primitives (`module`, `worker_threads`, `cluster`, `vm`, `repl`, `inspector`,
`process`, `trace_events`, `wasi`) but did not cover a parallel class of
builtins whose primary capability is reading state of the entire host Node
process. Each gave a one-line exfiltration primitive once the sandbox held a
readonly proxy over the host module:

  - `require('diagnostics_channel').channel('http.server.request.start')
    .subscribe(cb)` — sandbox callback receives raw host `IncomingMessage`
    objects for every HTTP request the embedder serves, with full
    `Authorization`, `Cookie`, `x-session-token` headers intact.
  - `require('async_hooks').executionAsyncResource()` — returns the current
    host `AsyncResource`. Embedders using `AsyncLocalStorage` for per-request
    user/auth context (`express`, `fastify`, `next.js` patterns) pin that
    state on the resource; the sandbox reads it directly.
  - `require('perf_hooks').performance.getEntriesByType('mark')` — reads
    every host-side `performance.mark(name)`. Production code routinely
    embeds request IDs, user IDs, route paths, or partial query strings in
    mark names.
  - `require('v8').getHeapSnapshot()` / `.writeHeapSnapshot(path)` /
    `.queryObjects(Ctor)` — full host V8 heap serialization (every string,
    every Buffer, every closure capture) to memory or arbitrary host
    filesystem path; Node 20+ `queryObjects` enumerates every host-realm
    instance of a constructor.

Not RCE — the underscored or named modules load through the normal
`vm.readonly()` wrap. The impact is information disclosure: the data the
embedder routes through these APIs in the same process *is* host data, by
spec. Wrapping the module in a proxy cannot localize the data source.

Structural fix in `lib/builtin.js`:

  - `DANGEROUS_BUILTINS` extended with `diagnostics_channel`, `async_hooks`,
    `perf_hooks`, `v8`. Reuses the existing four-layer enforcement
    established by GHSA-947f-4v7f-x2v8 and tightened by GHSA-rp36-8xq3-r6c4
    (family-prefix + `node:` normalization via `isDangerousBuiltin`).
  - `v8` was added during the fix beyond the originally-named three —
    `v8.writeHeapSnapshot(path)` is strictly worse than `perf_hooks` for
    the same invariant, so excluding it would leave a wide same-class
    bypass.
  - `mocks` / `overrides` / `SPECIAL_MODULES` escape hatches preserved:
    embedders who genuinely need a sandbox-local clock or async context can
    register a controlled wrapper under the same name. The denylist only
    rejects the default host-passthrough loader.

Reinforces Defense Invariant #13 (NodeVM builtin allowlist is a closed
system) — extending its scope from code-execution primitives to
information-disclosure surfaces.

Tests: `test/ghsa/GHSA-9g8x-92q2-p28f/repro.js` (251 lines) covers all four
builtins, both bare-name and `node:`-prefixed spellings, the wildcard
expansion path, the explicit-allowlist path, the low-level `makeBuiltins`
API, and confirms `mocks` / `overrides` escape hatches still work.

Docs: `docs/ATTACKS.md` gains Category 35 (renumbered from the branch's
provisional Category 30 — that slot is now occupied by GHSA-v6mx host
prototype mutation; Cats 31-34 are also already taken). Category 21's
DANGEROUS_BUILTINS list cross-references Category 35 for the four added
names. New row in the "How The Bridge Defends" table.

Version bump deferred — this lands alongside the other advisories in the
pre-existing [3.11.4] release slot.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants