security(validators): stop lookalike-domain and IPv6-transition bypasses - #763
Merged
Merged
Conversation
An exact allowlist entry was spliced into a regex with dots unescaped: "https://login.acme.com" compiled to "^login.acme.com$", where every "." matches any character. An attacker-registrable lookalike (loginXacme.com, login-acme.com) passed IsValidRedirectURI and IsValidOrigin, so OAuth access tokens, ID tokens and password-reset tokens were delivered to their domain. Only the wildcard branch escaped its dots — the exact branch is the documented production hardening. Exact origins are now compared as strings, never as patterns. Wildcards are QuoteMeta'd whole and only the "*" is re-opened. Both callers route through one matcher; they previously carried copy-pasted blocks, which is how one came to escape dots and the other did not. Separately, the SSRF blocklist covered private IPv4 and IPv6 ranges but not the transition mechanisms that embed an IPv4 address inside an IPv6 one: 2002:a9fe:a9fe:: (6to4) and 64:ff9b::a9fe:a9fe (NAT64) both reach 169.254.169.254. Block 2002::/16, 2001::/32, 64:ff9b::/96, 64:ff9b:1::/48, ::/128 and 100::/64 outright — none is a legitimate webhook or OIDC destination. Closes GHSA-89vc-q979-qwxv, GHSA-63w4-vxm7-v2mg, GHSA-j558-mp2m-fhrp, GHSA-qxfq-3qvw-w5fr.
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.
Fixes 4 of the 6 advisories still open in triage. Verified live on
mainbefore fixing — these were not theoretical.1. Allowlist bypass via unescaped regex dots — token theft
GHSA-89vc-q979-qwxv,GHSA-63w4-vxm7-v2mg(duplicates of each other),GHSA-j558-mp2m-fhrp(same root cause)internal/validators/url.gospliced each configured origin intoregexp.MatchString("^"+pattern+"$", ...). The wildcard branch escaped dots; the exact-origin branch — the one operators are told to use in production — did not. Sohttps://login.acme.comcompiled to^login.acme.com$, in which every.matches any character.Probed on
mainbefore the fix:An attacker registers the lookalike and receives OAuth access tokens, ID tokens and password-reset tokens. This is an incomplete fix of the already-published GHSA-x3f4-v83f-7wp2 / CVE-2026-54072, which added these validators to stop exactly this class of leak.
Fix. Exact origins are compared as strings, never as patterns. Wildcards are
regexp.QuoteMeta'd whole and then only the*is re-opened, so the wildcard is the single metacharacter surviving from operator config. Both callers now route through oneoriginMatcheshelper — they previously carried copy-pasted blocks, which is precisely how one came to escape dots while the other did not.Strictly a tightening: every legitimate origin that matched before still matches.
2. SSRF guard bypass via IPv6 transition addresses
GHSA-qxfq-3qvw-w5frisPrivateIPcovered RFC 1918, CGNAT, loopback, link-local, ULA, multicast and reserved — but not the transition mechanisms that embed an arbitrary IPv4 address inside an IPv6 one.2002:a9fe:a9fe::(6to4) and64:ff9b::a9fe:a9fe(NAT64) both resolve to169.254.169.254, the cloud metadata endpoint, and sailed past every check.Fix. Block
2002::/16(6to4),2001::/32(Teredo),64:ff9b::/96(NAT64 well-known),64:ff9b:1::/48(RFC 8215 local-use), plus::/128and100::/64. Blocking the ranges outright is stronger and simpler than decoding the embedded address, and costs nothing — none is a legitimate webhook or OIDC issuer destination.Admin-privileged to reach (webhook endpoint / SSO OIDC discovery URLs), hence lower severity than #1.
Tests
internal/validators/allowlist_bypass_test.go— 19 assertions fail againstmain, confirmed by reverting both files and re-running.:443normalisation)evil-acme.com, suffix-append and cross-label[^.]*all rejecteda+b.acme.commatches itself, notab.acme.com)Verification
go build ./...,go vet ./...,make lint(0 issues),make test— all pass. The full suite matters here: tightening redirect validation could have broken callers, and did not.Not included
GHSA-m82j-rq33-qjx2(password-reset poisoning via Host header when--urlis unset) is still open. It needs a change to default host-derivation behaviour that affects reverse-proxy and multi-tenant deployments — a deliberate decision, not a drive-by fix.