Vulnerability Analysis | Mitigation Guide | Security Testing

CVE-2026-40575: OAuth2 Proxy Authentication Bypass via Header Spoofing — What It Is & How to Fix It

Executive Summary

CVE-2026-40575 is a critical authentication bypass vulnerability (CVSS 9.1) in OAuth2 Proxy, one of the most widely-deployed reverse proxy solutions for enforcing OAuth2/OIDC authentication in Kubernetes, cloud-native, and self-hosted environments. By spoofing the X-Forwarded-Uri HTTP header, an unauthenticated remote attacker can trick a vulnerable OAuth2 Proxy instance into evaluating its skip-auth rules against a controlled path — rather than the actual request path — granting unauthorized access to any route protected by the proxy. Disclosed on April 22, 2026, all organizations using OAuth2 Proxy v7.5.0 through v7.15.1 with --reverse-proxy and --skip-auth-route or --skip-auth-regex rules configured are at immediate risk. Upgrade to v7.15.2 or later and configure --trusted-proxy-ip without delay.


1. What Is This Vulnerability?

Background: OAuth2 Proxy and How It Works

OAuth2 Proxy is an open-source reverse proxy that sits in front of backend applications and enforces OAuth2 / OpenID Connect authentication before allowing access. It is widely used in Kubernetes ingress chains, Docker Compose stacks, and corporate single-sign-on (SSO) deployments. When a request arrives, OAuth2 Proxy checks whether the user holds a valid session; if not, it redirects them to the configured identity provider (IdP).

To support flexible deployments, OAuth2 Proxy supports skip-auth rules — patterns (regex or explicit routes) that tell the proxy to let certain paths through without authentication. Examples include public health-check endpoints like /healthz or unauthenticated webhook receivers. When deployed behind a load balancer or another reverse proxy, OAuth2 Proxy can be configured with --reverse-proxy to trust upstream X-Forwarded-* headers.

The Flaw: Trusting a Client-Supplied Header

CVE-2026-40575 is a logic error in how OAuth2 Proxy evaluates which URI to check against its skip-auth rules. When --reverse-proxy is enabled, the proxy trusts the X-Forwarded-Uri header to determine the effective request path. However, this header is not stripped or validated before reaching OAuth2 Proxy — meaning an external attacker who can send requests directly (or through a misconfigured upstream that does not overwrite headers) can inject any value they choose.

The vulnerable code path, simplified:

// Pseudocode of the vulnerable logic (pre-v7.15.2)
incomingUri = request.Header.Get("X-Forwarded-Uri")
if incomingUri == "" {
    incomingUri = request.URL.Path
}
// skip-auth rules are evaluated against incomingUri
if matchesSkipAuthRule(incomingUri) {
    // BYPASS: forward request to upstream without auth check
    proxyToUpstream(request)
    return
}
// Normal auth enforcement...
enforceAuthentication(request)

Because incomingUri is sourced from the request header first, an attacker can set X-Forwarded-Uri: /public/healthz (a known skip-auth path) while the actual URL they are accessing is /admin/users or another protected endpoint. OAuth2 Proxy sees a match on the skip-auth rule and forwards the request unauthenticated.

Attack Vector

The attack requires:

  • OAuth2 Proxy running with --reverse-proxy flag enabled, AND
  • At least one --skip-auth-regex or --skip-auth-route rule configured, AND
  • The attacker being able to reach the OAuth2 Proxy listener (port 4180 by default) while being able to set arbitrary request headers

If the proxy is correctly isolated behind a load balancer that rewrites X-Forwarded-Uri to the real path, exploitation is significantly harder — but many real-world deployments do not enforce this.

Real-World Impact

While no specific public breach has been attributed to CVE-2026-40575 at the time of writing, the attack's prerequisites match a very common Kubernetes and cloud-native deployment pattern. Exposing OAuth2 Proxy to untrusted network segments, or leaving it reachable on port 4180 without a trusted upstream stripping headers, is a widespread misconfiguration. An attacker who exploits this vulnerability can:

  • Access internal dashboards, admin panels, and APIs protected only by OAuth2 Proxy
  • Extract sensitive data from authenticated-only API endpoints
  • Pivot laterally inside Kubernetes clusters by leveraging compromised backend services
  • Exfiltrate secrets, configurations, or PII from internal tooling assumed to be protected

2. Who Is Affected?

Directly vulnerable: OAuth2 Proxy versions 7.5.0 through 7.15.1 running with all three conditions present:

  1. --reverse-proxy flag is enabled (or OAUTH2_PROXY_REVERSE_PROXY=true)
  2. One or more --skip-auth-regex patterns or --skip-auth-route rules are configured
  3. The proxy listener is reachable by a client that can set arbitrary HTTP headers

Deployment environments at highest risk:

  • Kubernetes clusters where OAuth2 Proxy is deployed as a sidecar or standalone deployment with port 4180 exposed without network policy enforcement
  • Docker Compose stacks where OAuth2 Proxy is behind nginx or Traefik but X-Forwarded-Uri is not overwritten by the upstream proxy
  • Bare-metal or VM deployments where the proxy is directly reachable on the LAN or public internet
  • Helm chart deployments of oauth2-proxy that have not pinned to a patched image tag

Not directly vulnerable if:

  • No --skip-auth-route or --skip-auth-regex rules are configured (all requests require authentication)
  • --reverse-proxy is disabled
  • A trusted upstream proxy unconditionally overwrites X-Forwarded-Uri with the actual request path before it reaches OAuth2 Proxy
  • Already upgraded to v7.15.2 or later

3. How to Detect It (Testing)

Manual Testing Steps

Step 1: Enumerate skip-auth paths

Review the OAuth2 Proxy configuration to identify all --skip-auth-route and --skip-auth-regex entries. These are the paths you will use in the spoofed X-Forwarded-Uri header.

# Check running config via process inspection
ps aux | grep oauth2-proxy
# Or review the config file / Kubernetes ConfigMap
kubectl get configmap oauth2-proxy-config -n <namespace> -o yaml

Step 2: Send a spoofed request

Identify a protected path (e.g., /dashboard, /api/v1/users) and a known skip-auth path (e.g., /healthz, /ping). Then send:

# Replace <PROXY_HOST> with the OAuth2 Proxy address and port
curl -v \
  -H "X-Forwarded-Uri: /healthz" \
  http://<PROXY_HOST>:4180/api/v1/users

Step 3: Evaluate the response

  • Vulnerable: You receive a 200 OK (or a response from the upstream application) without being redirected to the OAuth2/OIDC login page. The proxy forwarded the request based on the spoofed header.
  • Not vulnerable / patched: You receive a 302 Found redirect to the identity provider login page, or a 401 Unauthorized response. The proxy ignored the spoofed header and enforced authentication against the real path.

Step 4: Vary the skip-auth pattern

If the first skip-auth path doesn't produce a bypass, repeat with other configured patterns — especially regex patterns that might match sub-paths (e.g., ^/public/.* matched by X-Forwarded-Uri: /public/anything).

Automated Scanning

Tool: nuclei (ProjectDiscovery)

nuclei -u http://<PROXY_HOST>:4180 \
  -t cves/2026/CVE-2026-40575.yaml \
  -header "X-Forwarded-Uri: /healthz"

If a community template is not yet available, create a simple custom template:

id: CVE-2026-40575-oauth2-proxy-bypass
info:
  name: OAuth2 Proxy X-Forwarded-Uri Auth Bypass
  severity: critical
  cve-id: CVE-2026-40575

requests:
  - method: GET
    path:
      - "{{BaseURL}}/api/v1/protected"
    headers:
      X-Forwarded-Uri: /healthz
    matchers:
      - type: status
        status:
          - 200
      - type: word
        negative: true
        words:
          - "Sign In"
          - "Login"
          - "oauth2/sign_in"

Tool: Burp Suite

  1. In Proxy > Intercept, capture a request to a protected path
  2. In the request editor, add the header: X-Forwarded-Uri: /healthz
  3. Forward the request and observe the response
  4. If you receive the upstream application's response rather than an auth redirect, the instance is vulnerable

Expected output indicating vulnerability:

  • HTTP 200 with application content
  • No Set-Cookie for OAuth2 Proxy session
  • No Location: header pointing to the IdP

Code Review Checklist

  • Is --reverse-proxy enabled in the OAuth2 Proxy configuration?
  • Are any --skip-auth-regex or --skip-auth-route rules defined?
  • Does the upstream proxy (nginx, Traefik, HAProxy, AWS ALB) unconditionally overwrite X-Forwarded-Uri before requests reach OAuth2 Proxy?
  • Is OAuth2 Proxy port (default 4180) network-restricted so only the upstream proxy can reach it?
  • Is the OAuth2 Proxy image/binary version 7.15.2 or later?
  • Is --trusted-proxy-ip configured post-upgrade?

4. How to Fix It (Mitigation)

Step-by-Step Remediation

1. Upgrade OAuth2 Proxy to v7.15.2 or later

The official fix ships in v7.15.2, which introduces the --trusted-proxy-ip flag to restrict which source IP addresses may supply X-Forwarded-* headers.

# Pull the patched container image
docker pull quay.io/oauth2-proxy/oauth2-proxy:v7.15.2

# Or for Kubernetes Helm deployments
helm upgrade oauth2-proxy oauth2-proxy/oauth2-proxy \
  --set image.tag=v7.15.2 \
  --namespace <namespace>

2. Configure --trusted-proxy-ip after upgrading

This flag tells the patched OAuth2 Proxy to only trust X-Forwarded-* headers from listed source IP addresses or CIDR ranges. Any request arriving from outside those ranges will have X-Forwarded-Uri ignored.

# Example: trust only the nginx ingress controller's pod CIDR
oauth2-proxy \
  --reverse-proxy \
  --trusted-proxy-ip=10.0.0.0/8 \
  --skip-auth-route="^/healthz$" \
  ... other flags ...

In a Kubernetes ConfigMap or Helm values:

extraArgs:
  trusted-proxy-ip: "10.100.0.0/16"
  reverse-proxy: "true"

3. Strip X-Forwarded-Uri at the upstream proxy (defense-in-depth)

Regardless of the OAuth2 Proxy version, configure your upstream proxy to always overwrite X-Forwarded-Uri with the actual request path. This prevents the attack even if a deployment is slow to upgrade.

nginx:

location / {
    # Always overwrite with the real request URI — never trust client-supplied value
    proxy_set_header X-Forwarded-Uri $request_uri;
    proxy_pass http://oauth2-proxy:4180;
}

Traefik (middleware configuration):

# Traefik does not natively support header overwrite via simple config;
# use a custom ForwardAuth middleware and ensure your backend strips the header,
# or use a Traefik plugin that enforces header sanitization.

HAProxy:

frontend http-in
    http-request del-header X-Forwarded-Uri
    http-request set-header X-Forwarded-Uri %[path]

4. Apply network-level isolation

Restrict direct access to the OAuth2 Proxy port (4180) at the network layer so only your trusted upstream proxy can reach it. In Kubernetes:

# NetworkPolicy to allow only ingress from the nginx-ingress namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: oauth2-proxy-ingress-restriction
  namespace: <your-namespace>
spec:
  podSelector:
    matchLabels:
      app: oauth2-proxy
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: ingress-nginx
      ports:
        - protocol: TCP
          port: 4180

5. Audit and narrow skip-auth rules

Review every --skip-auth-route and --skip-auth-regex entry. Remove rules that are no longer needed and tighten patterns to be as specific as possible (e.g., pin exact paths rather than broad regexes).

Code Fix Example

Before (vulnerable behavior — pre-v7.15.2 logic):

// Simplified vulnerable path evaluation
func getEffectiveUri(r *http.Request, reverseProxy bool) string {
    if reverseProxy {
        if fwdUri := r.Header.Get("X-Forwarded-Uri"); fwdUri != "" {
            return fwdUri  // ← Trusts client-supplied header unconditionally
        }
    }
    return r.URL.Path
}

After (patched behavior — v7.15.2):

// Simplified patched path evaluation
func getEffectiveUri(r *http.Request, reverseProxy bool, trustedIPs []net.IPNet) string {
    if reverseProxy && isTrustedSource(r.RemoteAddr, trustedIPs) {
        // Only trust X-Forwarded-Uri if the request comes from a trusted proxy IP
        if fwdUri := r.Header.Get("X-Forwarded-Uri"); fwdUri != "" {
            return fwdUri
        }
    }
    return r.URL.Path  // Fall back to the actual request path
}

Configuration Hardening

Beyond the immediate fix, harden your OAuth2 Proxy deployment:

  • Disable --reverse-proxy if you don't need X-Forwarded-* header trust
  • Use --cookie-secure=true to ensure session cookies are HTTPS-only
  • Enable --cookie-httponly=true to prevent JavaScript access to session cookies
  • Set --pass-access-token=false unless downstream services genuinely need the bearer token
  • Configure --email-domain or --allowed-group to limit who can authenticate, reducing blast radius of any future bypass
  • Enable audit logging (--logging-local-users=true) for post-incident forensics

5. How to Test the Fix (Validation)

Regression Test Scenarios

  • Scenario A: Upgraded instance ignores spoofed X-Forwarded-Uri from untrusted client IP and enforces authentication on protected route
  • Scenario B: Upgraded instance correctly passes through requests on legitimate skip-auth routes when the real URI matches the pattern
  • Scenario C: Trusted upstream proxy (with --trusted-proxy-ip configured) can still supply X-Forwarded-Uri and have it honored, preserving expected functionality
  • Scenario D: No legitimate user authentication flows are broken after upgrade

Security Test Cases

Test Case 1: Verify the bypass no longer works

  • Precondition: OAuth2 Proxy upgraded to v7.15.2+, --trusted-proxy-ip set to the upstream proxy's CIDR only
  • Steps:
    curl -v \
      -H "X-Forwarded-Uri: /healthz" \
      http://<PROXY_HOST>:4180/api/v1/users
    
  • Expected Result: 302 Found redirect to IdP login URL — the bypass is rejected

Test Case 2: Verify legitimate skip-auth still works

  • Precondition: Same as Test Case 1; request originates from a trusted proxy IP
  • Steps: Send a request to /healthz through the trusted upstream proxy without a session
  • Expected Result: 200 OK — the real skip-auth path is still bypassed as intended

Test Case 3: Verify authenticated users are unaffected

  • Precondition: Valid OAuth2 session cookie exists
  • Steps: Send a normal authenticated request (no spoofed headers) to a protected path
  • Expected Result: 200 OK with application content — normal authenticated access works

Test Case 4: Verify --trusted-proxy-ip enforcement

  • Precondition: OAuth2 Proxy on v7.15.2+; --trusted-proxy-ip set to 192.168.1.0/24
  • Steps: Send from an IP outside that CIDR with X-Forwarded-Uri: /healthz targeting /admin
  • Expected Result: 302 redirect to IdP — header ignored because source is untrusted

Automated Tests

Add the following to your CI/CD pipeline to permanently catch regressions:

#!/usr/bin/env bash
# oauth2_proxy_bypass_test.sh
# Run after every OAuth2 Proxy upgrade or config change

PROXY_URL="${OAUTH2_PROXY_URL:-http://localhost:4180}"
PROTECTED_PATH="/api/v1/users"
SKIP_AUTH_PATH="/healthz"

echo "=== CVE-2026-40575 Regression Test ==="

RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" \
  -H "X-Forwarded-Uri: $SKIP_AUTH_PATH" \
  "$PROXY_URL$PROTECTED_PATH")

if [ "$RESPONSE" -eq 200 ]; then
    echo "FAIL: Authentication bypass succeeded — instance is VULNERABLE"
    exit 1
elif [ "$RESPONSE" -eq 302 ] || [ "$RESPONSE" -eq 401 ]; then
    echo "PASS: Authentication enforced correctly (HTTP $RESPONSE)"
    exit 0
else
    echo "WARN: Unexpected response code $RESPONSE — manual review required"
    exit 2
fi

6. Prevention & Hardening

Best Practices

Practice 1: Apply a zero-trust model for internal proxies

Never assume that headers arriving at an internal service are trustworthy. Even in a Kubernetes cluster, pod-to-pod traffic can be spoofed if no NetworkPolicy or service mesh (e.g., Istio mTLS) is in place. Treat every HTTP header that originates from outside your trusted proxy tier as untrusted input.

Practice 2: Pin skip-auth rules to the minimum necessary scope

Each --skip-auth-route entry is a potential attack surface. Before adding a skip-auth rule, ask: can this endpoint instead be moved to a separate, unauthenticated service? If the rule must exist, use the most specific regex or route pattern possible and document the business justification.

Practice 3: Run OAuth2 Proxy in a hardened network segment

Place the OAuth2 Proxy behind a network boundary that only allows the trusted upstream proxy to connect to port 4180. Use Kubernetes NetworkPolicy, AWS Security Groups, or firewall rules to enforce this topology. The CVE-2026-40575 attack is substantially harder to execute when direct external access to port 4180 is blocked.

Practice 4: Implement a dependency update policy

OAuth2 Proxy, like all authentication-critical infrastructure components, should be subject to mandatory upgrade SLAs. Critical CVEs (CVSS ≥ 9.0) should trigger an upgrade within 24–72 hours. Subscribe to security advisories via the GitHub Security Advisory feed for oauth2-proxy/oauth2-proxy.

Practice 5: Validate your IdP integration regularly

Even with OAuth2 Proxy correctly configured, verify your identity provider settings: ensure token lifetimes are appropriate, refresh tokens are revoked on logout, and group/role membership is enforced at the IdP level. OAuth2 Proxy is a gatekeeper, but the IdP is the ultimate source of truth.

Monitoring & Detection

Set up alerts for the following indicators of exploitation attempts or active compromise:

Header anomaly detection (nginx/WAF rule):

# Alert if X-Forwarded-Uri does not match the actual request path
# (Indicates potential header spoofing attempts)
if ($http_x_forwarded_uri != "" and $http_x_forwarded_uri != $request_uri) {
    access_log /var/log/nginx/suspicious.log combined;
    return 400;
}

SIEM query (Splunk example):

index=access_logs sourcetype=nginx
| where isnotnull('http_x_forwarded_uri')
  AND 'http_x_forwarded_uri' != request_uri
| stats count by client_ip, http_x_forwarded_uri, request_uri
| where count > 5
| sort -count

Kubernetes audit log alert (Falco rule):

- rule: OAuth2 Proxy Port Accessed Without Upstream
  desc: Direct access to OAuth2 Proxy port 4180 bypassing ingress controller
  condition: >
    ka.target.name startswith "oauth2-proxy" and
    ka.request.operation = "GET" and
    not ka.source.ip in (trusted_ingress_ips)
  output: "Suspicious direct OAuth2 Proxy access from %ka.source.ip%"
  priority: WARNING

Metrics to monitor:

  • Sudden spike in 4xx responses from OAuth2 Proxy (could indicate automated probing)
  • Requests to protected paths without a corresponding session cookie
  • High volume of requests from a single IP to known skip-auth paths
  • Any 200 OK responses from OAuth2 Proxy to paths that should require authentication

References

Latest from the blog

See all →