fix(ext/node): add post-resolution deny check in TCPWrap connect#33880
Conversation
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
left a comment
There was a problem hiding this comment.
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:
-
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. -
Accept either deny or ENOTFOUND on Windows — use
[WILDCARD]inmain.outfor 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
left a comment
There was a problem hiding this comment.
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 decimal2130706433/ hex0x7f000001numeric IP aliases which Linux/macOSgetaddrinfonormalizesliteral_only("if": "windows") — exercises literal127.0.0.1only, since Windowsgetaddrinforejects the numeric forms withENOTFOUND(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.
…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
Summary
check_net_resolved()after hostname resolution in TCPWrap'sconnect()andconnect6()methods--deny-netrules targeting resolved IPs are enforced even when the hostname is provided as a numeric alias (e.g. decimal2130706433or hex0x7f000001for127.0.0.1)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 #33203Test plan
tests/specs/node/tcp_numeric_deny/that verifiesnode:net.connect()with numeric hostname aliases is denied when--deny-net=127.0.0.0/8is set