Refuse the private addresses written the other way - #27
Open
beardthelion wants to merge 1 commit into
Open
Conversation
The navigation floor matched exact hostnames and dotted-quad IPv4, so every IPv6 spelling of the same destination went straight past it. A Bot talked into opening http://[::ffff:169.254.169.254]/ reached the cloud metadata endpoint and screenshotted the deployment's credentials back into the transcript, which is the one thing the architecture doc promises cannot happen under any configuration. The same hole covered mapped loopback and RFC1918, AWS's IPv6 metadata address, link-local and unique-local IPv6, and a trailing root dot on any of the refused names. Reduce the hostname to one form before comparing anything: drop the root dot, and unwrap the IPv4 an IPv6 address carries in its low 32 bits under any of the three prefixes that reach it, the dual-stack ::ffff:0:0/96, the NAT64 well-known 64:ff9b::/96, and the deprecated compatible ::/96. Then classify IPv6 the way RFC1918 is already classified, so loopback, link-local and unique-local sit behind the same opt-in as their IPv4 equivalents while public IPv6 stays reachable. :: and ::1 keep their own handling: their low bits are 0.0.0.0 and 0.0.0.1, which are not addresses anybody routes to, so 0.0.0.0/8 is left alone rather than read as an embedded address.
beardthelion
requested review from
MikeRyanDev,
davidmckayv,
guidovizoso and
tylerslaton
as code owners
August 20, 2026 02:57
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #25.
The navigation floor compared exact hostnames and dotted-quad IPv4, so every IPv6 spelling of the same destination went past it, cloud metadata included.
docs/architecture.md:160says metadata is refused under every configuration; before this it was reachable ashttp://[::ffff:169.254.169.254]/whatever the configuration said.The same function backs
checkAgentEndpoint, where a registered agent is a URL this server POSTs to on every run, so the hole was reachable from agent registration as well as from the browser.What it does
Reduce the hostname to one form before comparing anything:
metadata.google.internal.is the name it resolves to.::ffff:0:0/96for a dual-stack socket,64:ff9b::/96for NAT64 (translated by the gateway on an IPv6-only network), and the deprecated::/96.fe80::/10andfc00::/7the way RFC1918 is already classified, so they sit behind the same private-host opt-in as their IPv4 equivalents.fd00:ec2::254to the never-allowed set, alongside the quad-form metadata address.::and::1keep their existing handling rather than being read as embedded IPv4, since their low bits are0.0.0.0and0.0.0.1, which are not addresses anybody routes to. Anything in0.0.0.0/8is left alone for the same reason.The bracketed
"[::1]"entry comes out ofINTERNAL_HOSTNAMES: canonicalization strips the brackets, so it can no longer be reached.Verification
12 test cases added to
server/tests/computer-target.test.ts, all failing before the change and passing after. They cover mapped metadata, AWS IPv6 metadata, the trailing root dot, NAT64, the IPv4-compatible form, mapped loopback and RFC1918, link-local and unique-local.The other direction is covered too, because a floor that refuses too much is its own outage: public IPv6 (
[2606:4700::1111]) andexample.com.stay allowed, mapped loopback is allowed when the deployment opts in, and metadata stays refused under both settings.Existing tests unchanged and passing. The
serversuite has the same 71 failures before and after this branch, all of them integration tests wanting a Postgres this machine does not have.bun run typecheckandbunx biome checkare clean.