Backstory
The headers at the center of CVE-2025-64484 arrived in February 2016, when oauth2-proxy still lived in Bitly's repository. A contributor added a pass-user-headers flag so the proxy would send X-Forwarded-User and X-Forwarded-Email to upstreams such as Grafana, which read identity from headers rather than from HTTP Basic auth. From then on, the proxy's contract with the application behind it was a username in a header, and the safety of that contract depended on the proxy controlling who could set it.
The maintainers saw that dependency early. In June 2020, Nick Meves opened PR 624 to strip inbound X-Forwarded auth headers, writing that otherwise this "might result in a backend authorizing a spoofed user set by a malicious client." PR 904 flipped skip-auth-strip-headers to true on 8 November 2020 for "OOTB secure default settings." Joel Speed's header injector middleware, merged in PRs 705 and 826 and shipped in v7.0.0 on 1 February 2021, carried the rule forward: every configured header got a preserveRequestValue flag defaulting to false, documented as "headers that match this header will be stripped." Preserving an inbound value stayed available for chained-proxy deployments, as an explicit opt-in.
The stripping itself was one line: req.Header.Del(header.Name). Go resolves header keys through CanonicalMIMEHeaderKey, which "converts the first letter and any letter following a hyphen to upper case; the rest are converted to lowercase." An underscore is a valid header field byte and not a hyphen, so X_Forwarded_User canonicalized to a different map key than X-Forwarded-User, survived the Del call, and was forwarded unchanged.
The other half of the collision lives in the upstream. RFC 3875 specifies that a CGI-style server converts the header field name to upper case and replaces every hyphen with an underscore, so both spellings collapse to HTTP_X_FORWARDED_USER. WSGI frameworks inherit that mapping, and nginx has defaulted underscores_in_headers to off for the same reason. Telekom Security published this exact technique against Apache, nginx, and Caddy in May 2020, two months before the strip logic was written, and the oauth2-proxy advisory cites that research.
Jan Larwig committed the fix on 8 November 2025. A new normalizeHeaderName lowercases the name and replaces underscores with hyphens, and stripNormalizedHeader deletes every request key whose normalized form matches, using direct map deletion "because req.Header.Del accesses the map via the header's canonicalized name." Version 7.13.0 shipped the same day, with an InsecureSkipHeaderNormalization opt-out for operators who need the old matching. GitHub credits the report to 47Cid.
Technical analysis (revisited)
The bug class is CWE-644, improper neutralization of HTTP headers. The attack primitive is a parser differential: the proxy and the application behind it disagree about which byte sequences name the same header. oauth2-proxy stripped X-Forwarded-User and passed X_Forwarded_User through; a Django, Flask, FastAPI, or PHP upstream then folded the smuggled spelling back into the same variable it trusts for identity.
Nine months of hindsight sharpen the scoping. The project advisory states that "OAuth2 Proxy authentication/authorization itself is not compromised," and the CVSS vector AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N requires an attacker who already holds a valid session. The impact is privilege escalation inside the upstream application, by an authenticated low-privilege user asserting a different identity, rather than an unauthenticated bypass of the OAuth2 flow. The 8.5 score is driven largely by S:C, scope change, because the damage lands in a different security authority than the component that failed.
Exposure is also conditional on the upstream. A deployment fronting a Go or Java service that treats underscore and hyphen names as distinct is not affected by this path at all, and nginx placed in front of oauth2-proxy drops underscore headers by default. Wiz records that "no public proof-of-concept exploit has been confirmed as functional" and rates exploitation unlikely, while recommending 7.13.0 and, as an interim control, configuring upstreams not to treat the two spellings as equivalent. Black Kite's third-party-risk write-up warns specifically against enabling the new InsecureSkipHeaderNormalization escape hatch.
The technique is not new to this project. Puma shipped the same fix shape in September 2024 for CVE-2024-45614, discarding underscore headers when a hyphenated version also exists, and httpoxy demonstrated the CGI namespace collision across twelve CVEs in 2016.
Lifecycle timeline
- 2025-11-05: CVE-2025-64484 reserved by the GitHub CNA (source)
- 2025-11-08: Fix commit landed in oauth2-proxy, adding header name normalization (source)
- 2025-11-08: Patch released, oauth2-proxy v7.13.0 (source)
- 2025-11-08: Official Helm chart oauth2-proxy-8.3.3 published, bumping appVersion to 7.13.0 (source)
- 2025-11-10: CVE record published, CVSS 3.1 8.5 High, CWE-644 (source)
- 2025-11-10: NVD entry published (source)
- 2025-11-12: GitHub Security Advisory GHSA-vjrc-mh2v-45x6 published (source)
- 2025-11-12: Bitnami advisory BIT-oauth2-proxy-2025-64484 published, fixed in 7.13.0 (source)
- 2025-11-13: Tenable container security check 436026 published (source)
- 2025-11-14: CISA ADP Vulnrichment SSVC assessment added, exploitation "none," automatable "no" (source)
- 2025-11-17: Go vulnerability database entry GO-2025-4113 published (source)
- 2026-06-17: NVD record last modified, status set to Deferred (source)
Real-world outcome
No public exploitation of CVE-2025-64484 has surfaced in the nine months since disclosure. The CVE is absent from the CISA Known Exploited Vulnerabilities catalog, confirmed against the live feed on 11 August 2026, and CISA's own SSVC assessment four days after publication recorded exploitation as "none" and automatable as "no." EPSS sits at 0.619 percent, the 46th percentile, as of 10 August 2026. NVD never produced a primary score of its own; the only CVSS in the record is the GitHub CNA's, and the record is now Deferred. No security firm has reported in-the-wild activity, and no Qualys QID or Nessus network plugin was published, which follows from a flaw that requires an authenticated session and is therefore not meaningfully internet-scannable.
The original CVE Brief analyst note, dated 10 November 2025, described the flaw as letting an unauthenticated remote attacker bypass the OAuth2 flow entirely and anticipated public exploit code and possible KEV listing. The project advisory published two days later scoped it differently: an already authenticated user, no compromise of the proxy's own authentication, and impact confined to upstream applications that fold underscores into dashes.
Patch timing ran ahead of disclosure. The fix shipped on 8 November 2025, two days before the CVE was published and four days before the advisory went public, and the official Helm chart carried the patched image twenty-two minutes after the upstream release. Operators tracking the project had a patched artifact before the CVE existed publicly.
Lessons
This case shows that stripping a header is a string-matching problem, and string matching is only as sound as the agreement between the two parsers on either side of it. Go's canonicalization and the CGI/WSGI mapping each behave exactly as documented; the vulnerability lives in the gap between them, which neither component owns.
It also shows how far a known technique can travel before it becomes a CVE in a given product. Telekom Security documented underscore-versus-hyphen smuggling in May 2020, two months before oauth2-proxy's strip logic was written, and nginx had defaulted underscores_in_headers to off years earlier. Published research about a class does not propagate to every implementation of that class on its own.
The scoring gap is instructive as well. An 8.5 with S:C sat alongside a CISA exploitation rating of "none," an EPSS in the 46th percentile, and no KEV listing. Both readings are accurate. Severity describes what an attacker who already has a session can reach; the exploitation signals describe how attractive that is at scale. For a bug gated behind authentication and conditional on the upstream framework, the two diverge.
Finally, the header-trust surface did not close with this fix. In April 2026 the project disclosed CVE-2026-40575, an X-Forwarded-Uri spoofing bypass scored 9.1 with PR:N, in the same family of headers.
References
- GitHub Security Advisory GHSA-vjrc-mh2v-45x6
- NVD, CVE-2025-64484
- MITRE CVE record, CVE-2025-64484
- oauth2-proxy v7.13.0 release
- Fix commit, header name normalization
- Fix commit, strip normalized header variants
- PR 624, strip inbound X-Forwarded auth headers
- PR 904, secure-by-default header stripping
- Original pass-user-headers commit, February 2016
- Header injector middleware commit
- Wiz vulnerability database entry
- Black Kite, Focus Friday TPRM analysis
- Telekom Security, Smuggling HTTP headers through reverse proxies
- RFC 3875 section 4.1.18, CGI meta-variable naming
- Go textproto.CanonicalMIMEHeaderKey
- nginx underscores_in_headers directive
- CVE-2024-45614, Puma header normalization (GHSA-9hf4-67fc-4vf4)
- CVE-2025-54576, oauth2-proxy skip_auth_routes bypass (GHSA-7rh7-c77v-6434)
- CVE-2025-29927, Next.js middleware authorization bypass (GHSA-f82v-jwr5-mffw)
- CVE-2026-40575, oauth2-proxy X-Forwarded-Uri spoofing (GHSA-7x63-xv5r-3p2x)
- httpoxy
- Go vulnerability database, GO-2025-4113
- Official Helm chart oauth2-proxy-8.3.3
- Bitnami advisory BIT-oauth2-proxy-2025-64484
- Tenable container security check 436026