# Caddy forward_auth copy_headers Does Not Strip Client-Supplied Headers, Allowing Identity Injection and Privilege Escalation

Caddy forward_auth copy_headers skips Set when auth response lacks the header, leaving client-supplied identity headers intact for backend privilege escalation.

- Payout: Research
- Severity: high
- Program: Caddy
- Found by: NucleiAv
- Disclosed: 2026-03-06
- CVE: CVE-2026-30851
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-7r4p-vjf4-gxv4
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-7r4p-vjf4-gxv4
- Tags: area:cloud domain:infra impact:integrity impact:privesc kind:research platform:ghsa sev:high tech:go technique:regression technique:source-review vuln:access-control vuln:auth-bypass vuln:privilege-escalation

**Target:** reverse proxy forward_auth directive (Caddyfile config parser)
**Root cause:** PR #6608 added a MatchNot guard so the header Set only runs when the auth response header is non-empty; no Delete is generated, so a client-supplied header of the same name survives when the auth service omits it.
**Steps:**
1. Configure `forward_auth 127.0.0.1:9091 { uri / copy_headers X-User-Id X-User-Role }` then `reverse_proxy 127.0.0.1:9092`
2. Auth service returns `200 OK` for any `Authorization: Bearer ...` without identity headers
3. Send `curl http://127.0.0.1:8080/ -H "Authorization: Bearer token123" -H "X-User-Id: admin" -H "X-User-Role: superadmin"`
4. Backend receives `X-User-Id: admin` and `X-User-Role: superadmin`
**How found:** Code review of forwardauth/caddyfile.go traced the MatchNot guard added in PR #6608 (fixing empty-string header UX bug #6610) and noticed no Delete operation existed, meaning the prior incidental overwrite protection was removed.
**Exploitation:** Attacker with any valid low-privilege token sends forged identity headers matching copy_headers names; auth service validates token and returns 200 without those headers, Caddy's MatchNot guard skips Set and never deletes originals, backend trusts injected admin identity.
**Insight:** When a proxy copies trusted headers from an auth subrequest, check whether the copy is conditional; any conditional Set without an unconditional Delete leaves client values intact — grep for MatchNot/Set pairs in header-copy logic.

## Proof of concept (copied exactly from the original report)

**curl request injecting identity headers**

```
curl -v http://127.0.0.1:8080/ \
  -H "Authorization: Bearer token123" \
  -H "X-User-Id: admin" \
  -H "X-User-Role: superadmin"
```

