Skip to content

fix(ext/node): add post-resolution deny check in TCPWrap connect#33880

Merged
bartlomieju merged 4 commits into
mainfrom
fix/tcp-wrap-deny-net-bypass
May 7, 2026
Merged

fix(ext/node): add post-resolution deny check in TCPWrap connect#33880
bartlomieju merged 4 commits into
mainfrom
fix/tcp-wrap-deny-net-bypass

Conversation

@bartlomieju

Copy link
Copy Markdown
Member

Summary

  • Add check_net_resolved() after hostname resolution in TCPWrap's connect() and connect6() methods
  • This ensures --deny-net rules targeting resolved IPs are enforced even when the hostname is provided as a numeric alias (e.g. decimal 2130706433 or hex 0x7f000001 for 127.0.0.1)
  • Aligns the Node TCP compat path with existing checks in native Deno.connect(), Node UDP, and the net/TLS/QUIC paths added in fix(permissions): check deny rules against resolved IPs to prevent numeric hostname bypass #33203

Test plan

  • Added spec test tests/specs/node/tcp_numeric_deny/ that verifies node:net.connect() with numeric hostname aliases is denied when --deny-net=127.0.0.0/8 is set

Add `check_net_resolved()` after hostname resolution in TCPWrap's
`connect()` and `connect6()` methods. This ensures that `--deny-net`
rules targeting resolved IPs are enforced even when the hostname is
provided as a numeric alias (e.g. decimal or hex representation).

This aligns the Node TCP compat path with the existing checks in
native `Deno.connect()`, Node UDP, and the net/TLS/QUIC paths
added in #33203.

@fibibot fibibot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Substantively right — the post-resolution check_net_resolved calls in TCPWrap::connect() (line 608-616) and TCPWrap::connect6() (line 680-687) close the numeric-alias bypass for --deny-net rules. Aligns with the existing checks in native Deno.connect, Node UDP, and the net/TLS/QUIC paths from #33203. Important security alignment.

Test fixture has a Windows portability issue

The spec test tests/specs/node/tcp_numeric_deny/main.out expects all 3 forms (decimal 2130706433, hex 0x7f000001, literal 127.0.0.1) to produce the same deny error. On Linux/macOS this works because the resolver normalizes numeric IP aliases. On Windows, lookup_host rejects the numeric forms with ENOTFOUND (the OS resolver doesn't accept them), so the deny check never fires:

actual:
  decimal:ENOTFOUND
  hex:ENOTFOUND
  literal:Requires net access to "127.0.0.1:12345", run again with the --allow-net flag

expected:
  decimal:Requires net access to "127.0.0.1:12345", run again with the --allow-net flag
  hex:Requires net access to "127.0.0.1:12345", run again with the --allow-net flag
  literal:Requires net access to "127.0.0.1:12345", run again with the --allow-net flag

Fails test specs (2/2) debug windows-x86_64 and windows-aarch64.

Fix direction

Two options:

  1. Skip on Windows — add "windows": false (or platform-conditional) in __test__.jsonc. The security fix still applies on Windows for any host that does resolve to a denied IP; the test just needs Unix-only fixtures.

  2. Accept either deny or ENOTFOUND on Windows — use [WILDCARD] in main.out for the numeric lines, since the security goal is satisfied either way (no connection succeeds). Less precise but covers both platforms with one fixture.

Option 1 is cleaner and matches the convention used in other net-permission tests that target specific resolver behaviors. The substance of the security fix is platform-independent and doesn't need Windows-specific test coverage to land.

CI

131 success, 3 fail (2 platforms × the same Windows test + ci status rollup), 2 skipping. All non-Windows shards green. Once the test fixture handles Windows, this is ready to land.

Not APPROVE-ing while CI is red; will re-confirm once the fixture is fixed.

Windows getaddrinfo does not resolve decimal/hex numeric hostname
aliases (e.g. 2130706433, 0x7f000001), returning ENOTFOUND instead.
Split the test so decimal/hex cases only run on unix, while the
literal IP test runs on Windows.

@fibibot fibibot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My prior portability concern is resolved

New commit e4701f8e ("fix: split tcp_numeric_deny test for Windows compatibility") splits the spec test into two platform-conditional cases:

  • numeric_aliases ("if": "unix") — exercises the decimal 2130706433 / hex 0x7f000001 numeric IP aliases which Linux/macOS getaddrinfo normalizes
  • literal_only ("if": "windows") — exercises literal 127.0.0.1 only, since Windows getaddrinfo rejects the numeric forms with ENOTFOUND (an OS-resolver limitation, not Deno's)

The new main_literal.mjs is a 1-call tryConnect("127.0.0.1") test asserting the deny error fires. That still validates the post-resolution check_net_resolved path on Windows for the case that can be exercised — any real-world Windows host that resolves to a denied IP via DNS goes through the same code path the literal test hits. ✓

Verified all Windows specs shards now pass at e4701f8e:

test specs (1/2) debug windows-aarch64    success
test specs (1/2) debug windows-x86_64     success
test specs (2/2) debug windows-aarch64    success
test specs (2/2) debug windows-x86_64     success
+ all 4 release windows shards

The Rust change in TCPWrap::connect() / connect6() is unchanged from the 8868c55d substance I previously approved.

CI: 1 unrelated fail

test specs (2/2) debug linux-aarch64    fail   specs::cert::ip_address_unsafe_ssl   (Cloudflare 1.1.1.1 503 flake)

Same recurring 1.1.1.1 cert flake I've called out on #33877, #33884, #33888, #33892, #33894, #33896, #33897 this week. Completely orthogonal to the deny-net work.

Holding at COMMENT per the strict CI-must-be-green rule. Substance is APPROVE-quality — important security alignment with the existing check_net_resolved calls in native Deno.connect/Node UDP/net/TLS/QUIC paths from #33203. Will re-confirm with APPROVE once the cert flake clears on a re-run.

@bartlomieju
bartlomieju merged commit e40b1b1 into main May 7, 2026
136 checks passed
@bartlomieju
bartlomieju deleted the fix/tcp-wrap-deny-net-bypass branch May 7, 2026 09:54
littledivy pushed a commit to crowlKats/deno that referenced this pull request Jun 10, 2026
…oland#33880)

- Add `check_net_resolved()` after hostname resolution in TCPWrap's
`connect()` and `connect6()` methods
- This ensures `--deny-net` rules targeting resolved IPs are enforced
even when the hostname is provided as a numeric alias (e.g. decimal
`2130706433` or hex `0x7f000001` for `127.0.0.1`)
- Aligns the Node TCP compat path with existing checks in native
`Deno.connect()`, Node UDP, and the net/TLS/QUIC paths added in denoland#33203
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