Skip to content

Honor the fromURL graceful-failure contract for non-IPv6 hosts - #218

Merged
beaugunderson merged 1 commit into
mainfrom
bg-fromurl-graceful
Aug 10, 2026
Merged

beaugunderson merged 1 commit into
mainfrom
bg-fromurl-graceful

Conversation

@beaugunderson

@beaugunderson beaugunderson commented Aug 10, 2026 •

Copy link
Copy Markdown
Owner

Address6.fromURL() is documented and typed to return { error, address: null, port: null } when a URL can't be parsed, but that contract was only implemented on the regex-miss path. The URL host character class [0-9a-f:.] is a superset of valid IPv6, so any host made only of hex digits, colons and dots matched the regex and was handed straight to new Address6(host), whose AddressError propagated out uncaught.

IPv4 literals are the visible case:

Address6.fromURL('http://example.com/');    // { error: 'failed to parse address from URL', … }
Address6.fromURL('http://127.0.0.1/admin'); // threw AddressError

but so did [:::], [1:2:3:4:5:6:7:8:9] and [::ffff:999.1.1.1]. A plain hostname like example.com avoided the throw only incidentally — letters outside a-f break the regex match before the constructor is ever reached.

The fix

Both regex-miss paths already returned the documented error object, so this hoists that message into a variable and reuses it when the constructor rejects a host the regex accepted. fromURL now never throws, for any input.

History

This is only partly a regression. The unbracketed IPv4 throw has been there since 7.0.0; before that, 6.4.0 silently parsed 127.0.0.1 as the address 127, returning wrong data rather than erroring. The bracketed form (http://[127.0.0.1]:80/) did return the graceful error object until 10.2.1, which is the first release carrying #202 — that PR widened the host character class from [0-9a-f:] to [0-9a-f:.] so v4-in-v6 hosts like [::ffff:1.2.3.4] would parse, and . also let IPv4 literals through to the constructor.

Tests

Seven new cases in test/functionality-v6-test.ts: IPv4 host, bracketed IPv4 with port, bare IPv4, [:::], nine groups, out-of-range v4-in-v6 octets, plus a table of eleven hosts asserting fromURL never throws. All seven fail before the fix. The existing valid-address and v4-in-v6 cases are untouched.

Reported by @zikk090 via GHSA-mxvh-v779-f36j, declined as an advisory and fixed as a correctness bug.

Address6.fromURL() is documented and typed to return
`{ error, address: null, port: null }` when a URL can't be parsed, but
that contract was only implemented on the regex-miss path. The URL host
character class `[0-9a-f:.]` is a superset of valid IPv6, so any host
made only of hex digits, colons and dots matched the regex and was
handed straight to `new Address6(host)`, whose AddressError propagated
out uncaught.

IPv4 literals are the visible case — `http://127.0.0.1/`,
`http://169.254.169.254/`, `http://0.0.0.0/` all threw — but so did
`[:::]`, `[1:2:3:4:5:6:7:8:9]` and `[::ffff:999.1.1.1]`. A plain
hostname like `example.com` avoided the throw only because letters
outside a-f break the regex match first.

Catch the constructor and return the same error object the regex-miss
path returns, so the function never throws for any input.

Reported by @zikk090 via GHSA-mxvh-v779-f36j. Declining that as a
security advisory: an uncaught synchronous throw from a parser on the
caller's own stack is a correctness bug, not a denial of service.
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.

1 participant