Skip to content

net: enforce strict socket address formats in SocketAddress.parse - #64832

Open
araujogui wants to merge 5 commits into
nodejs:mainfrom
araujogui:socketaddress-parse-reject-url-components
Open

net: enforce strict socket address formats in SocketAddress.parse#64832
araujogui wants to merge 5 commits into
nodejs:mainfrom
araujogui:socketaddress-parse-reject-url-components

Conversation

@araujogui

@araujogui araujogui commented Jul 30, 2026

Copy link
Copy Markdown
Member

SocketAddress.parse() no longer builds a http://${input} URL and reads the hostname and port back out of the Ada WHATWG URL parser. The input is now split in C++ into host and port, and the host is validated by uv_inet_pton() — the same check the SocketAddress constructor already used.

The Ada URL parser is deliberately lenient about hosts, so parse() accepted much that is not a socket address: legacy IPv4 forms (0177.0.0.1, 0x7f.0.0.1, 2130706433, 127.1), URL syntax (user@1.2.3.4:80, 1.2.3.4:80/foo), and input it rewrites first, such as embedded tabs and non-ASCII digits that IDNA maps to ASCII. Those legacy forms are the ambiguity behind CVE-2021-29923 and CVE-2021-29922. It also disagreed with the constructor, which rejects 0177.0.0.1 with ERR_INVALID_ADDRESS.

Separately, SocketAddress.parse('1.2.3.4:80') returned port 0, because the URL parser drops a port equal to the scheme's default. It now returns 80, fixes #62906.

The accepted grammar is documented in doc/api/net.md. I left IPv6 zone ids out of it: sin6_scope_id isn't exposed on the JS object, so a scoped address can't round trip through its own JSON, and resolving an interface name would mean the result depends on which machine did the parsing.

Copilot AI review requested due to automatic review settings July 30, 2026 00:59
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/net

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@nodejs-github-bot nodejs-github-bot added needs-ci PRs that need a full CI run. net Issues and PRs related to the net subsystem. labels Jul 30, 2026

@Renegade334 Renegade334 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This would make the port issue much simpler with this additional validation. If the string ends in /:(\d+)$/, then it can just be sliced off, and if the rest of the string gets parsed as a valid hostname by URLParse then the port can be obtained from the digit string with NumberParseInt.

Comment thread lib/internal/socketaddress.js Outdated
@codecov

codecov Bot commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.37037% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.16%. Comparing base (0de4fcc) to head (45c3368).

Files with missing lines Patch % Lines
src/node_sockaddr.cc 76.47% 0 Missing and 4 partials ⚠️
src/node_sockaddr_parser.cc 98.63% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #64832      +/-   ##
==========================================
- Coverage   90.17%   90.16%   -0.01%     
==========================================
  Files         771      772       +1     
  Lines      265470   265542      +72     
  Branches    50463    50487      +24     
==========================================
+ Hits       239383   239426      +43     
+ Misses      17055    17048       -7     
- Partials     9032     9068      +36     
Files with missing lines Coverage Δ
lib/internal/socketaddress.js 98.88% <100.00%> (-0.11%) ⬇️
src/node_sockaddr.h 51.28% <ø> (ø)
src/node_sockaddr_parser.cc 98.63% <98.63%> (ø)
src/node_sockaddr.cc 74.84% <76.47%> (+0.03%) ⬆️

... and 29 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread doc/api/net.md Outdated
@araujogui
araujogui requested a review from Renegade334 August 6, 2026 15:21

@Renegade334 Renegade334 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM!

@Renegade334 Renegade334 added author ready PRs with CI started, the required approvals, and no outstanding review comments. request-ci Add this label to start a Jenkins CI on a PR. Only starts once the PR has an approving review. commit-queue-squash PRs the Commit Queue should land as one squashed commit. labels Aug 7, 2026
@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. Only starts once the PR has an approving review. label Aug 7, 2026
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

@araujogui

Copy link
Copy Markdown
Member Author

@Renegade334 maybe we should include semver-major tag?

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

@jasnell

jasnell commented Aug 18, 2026

Copy link
Copy Markdown
Member

I'm not yet convinced this is the best way to approach this (with the regex). Won't block for now but there's probably a more performant / reliable way.

@araujogui
araujogui force-pushed the socketaddress-parse-reject-url-components branch from 971d5b4 to 9d21a93 Compare August 20, 2026 14:05
@araujogui

araujogui commented Aug 20, 2026

Copy link
Copy Markdown
Member Author

I'm not yet convinced this is the best way to approach this (with the regex). Won't block for now but there's probably a more performant / reliable way.

@jasnell I implemented a new strict socket address parser replacing Ada, it fixes #62906 and is faster. Benchmark results compared to main:

                                                           confidence improvement accuracy (*)   (**)  (***)
net/net-socketaddress-parse.js input='ipv4-port' n=1000000        ***     52.37 %       ±0.63% ±0.84% ±1.10%
net/net-socketaddress-parse.js input='ipv4' n=1000000             ***     43.38 %       ±0.64% ±0.86% ±1.12%
net/net-socketaddress-parse.js input='ipv6-port' n=1000000        ***     59.56 %       ±0.49% ±0.65% ±0.85%
net/net-socketaddress-parse.js input='ipv6' n=1000000             ***     47.35 %       ±0.50% ±0.66% ±0.86%

Be aware that when doing many comparisons the risk of a false-positive result increases.
In this case, there are 4 comparisons, you can thus expect the following amount of false-positive results:
  0.20 false positives, when considering a   5% risk acceptance (*, **, ***),
  0.04 false positives, when considering a   1% risk acceptance (**, ***),
  0.00 false positives, when considering a 0.1% risk acceptance (***)

@trivikr trivikr removed the author ready PRs with CI started, the required approvals, and no outstanding review comments. label Aug 22, 2026
@araujogui
araujogui force-pushed the socketaddress-parse-reject-url-components branch from 9d21a93 to 75ae3d0 Compare September 1, 2026 18:13
Comment thread benchmark/net/net-socketaddress-parse.js
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Benchmark GHA (net / net-socketaddress-parse): https://github.com/nodejs/node/actions/runs/33605572788

Results

[!WARNING]
Do not take GHA benchmark results as face value, always confirm them
using a dedicated machine, e.g. Jenkins CI.

Benchmark results:

                                                           confidence improvement accuracy (*)    (**)   (***)
net/net-socketaddress-parse.js input='ipv4-port' n=1000000        ***     29.96 %      ±10.23% ±13.49% ±17.31%
net/net-socketaddress-parse.js input='ipv4' n=1000000             ***     34.36 %      ±10.45% ±13.78% ±17.68%
net/net-socketaddress-parse.js input='ipv6-port' n=1000000        ***     28.40 %      ±10.07% ±13.27% ±17.03%
net/net-socketaddress-parse.js input='ipv6' n=1000000             ***     31.02 %      ±10.04% ±13.24% ±16.99%

Be aware that when doing many comparisons the risk of a false-positive
result increases. In this case, there are 4 comparisons, you can thus
expect the following amount of false-positive results:
  0.20 false positives, when considering a   5% risk acceptance (*, **, ***),
  0.04 false positives, when considering a   1% risk acceptance (**, ***),
  0.00 false positives, when considering a 0.1% risk acceptance (***)

[!WARNING]
Do not take GHA benchmark results as face value, always confirm them
using a dedicated machine, e.g. Jenkins CI.

@araujogui araujogui changed the title net: reject non-address SocketAddress.parse input net: enforce strict socket address formats in SocketAddress.parse Sep 2, 2026
Signed-off-by: Guilherme Araújo <arauujogui@gmail.com>
SocketAddress.parse() ran its input through the WHATWG URL host parser,
which accepts far more than an address with an optional port. It took
the legacy IPv4 notations behind CVE-2021-29922 and CVE-2021-29923,
including octal, hexadecimal, integer and shorthand forms, and it
dropped a port that matched the default for the assumed scheme, so
'1.2.3.4:80' parsed as port 0.

Replace it with a small parser that matches the documented grammar and
nothing else. It only frames the input; the host is handed to
uv_inet_pton, which rejects the legacy forms itself.

IPv6 zone ids are deliberately outside the grammar. sin6_scope_id is
not exposed on the JS object, so a scoped address could not round trip
through its own JSON, and an interface name cannot be resolved without
consulting the local interface table, which would make the result
depend on the host it was parsed on. '%' stays in the IPv6 delimiter
set so that it can never reach uv_ip6_addr(), which would otherwise
resolve it.

Assisted-by: Claude Opus 5
Signed-off-by: Guilherme Araújo <arauujogui@gmail.com>
@araujogui
araujogui force-pushed the socketaddress-parse-reject-url-components branch from 75ae3d0 to 7fd8d95 Compare September 11, 2026 00:04
The parse benchmark only measured inputs that parse successfully, so the
rejection paths were unmeasured. Add a group covering the four ways the
parser bails: a non-address host and a legacy hex form that only
uv_inet_pton rejects, an out of range port, and an unterminated IPv6
address that never reaches uv_ip6_addr.

Assisted-by: Claude Opus 5
Signed-off-by: Guilherme Araújo <arauujogui@gmail.com>
@araujogui
araujogui force-pushed the socketaddress-parse-reject-url-components branch from f702a10 to 45c3368 Compare September 11, 2026 00:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

commit-queue-squash PRs the Commit Queue should land as one squashed commit. needs-ci PRs that need a full CI run. net Issues and PRs related to the net subsystem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SocketAddress parses port 80 as 0

7 participants