On Windows, Caddy path matchers treat /private\secret.txt as outside /private/, but file_server later resolves the same request path as private\secret.txt on disk. An unauthenticated remote client can request /private%5csecret.txt and bypass Caddy path-scoped auth/deny routes protecting /private/.
Caddy’s stripHTML template function cannot reliably remove all HTML tags from input strings. Certain malformed HTML, such as <<>img src=x onerror=alert()>, can bypass the tag-stripping logic, potentially leaving dangerous content in the output if it is later rendered as HTML. This may allow client-side XSS in cases where untrusted strings are rendered unsafely.
forward_auth copy_headers deletes the exact client-supplied identity header before copying the trusted value from the auth gateway. But when the request later goes through php_fastcgi, Caddy normalizes HTTP headers into CGI variables by replacing - with _. This lets a client send an underscore alias that survives the forward_auth delete step but becomes the same PHP/FastCGI variable: Remote-Groups -> HTTP_REMOTE_GROUPS Remote_Groups -> HTTP_REMOTE_GROUPS Remote-User -> HTTP_REMOTE_USER Remote_User -> HTTP_REMOTE_USER Result: …
Caddy's remote admin access control performs path authorization using prefix matching: admin.go: strings.HasPrefix(r.URL.Path, allowedPath) This allows a client certificate authorized only for /pki/ca/prod to access sibling PKI resources whose paths merely share the same prefix, such as /pki/ca/prod-backup. This is an authorization bug in Caddy's source code, not a misconfiguration issue. The configured policy is more restrictive than the behavior that Caddy actually enforces.
A remote admin client certificate restricted to the following path: /config/apps/http/servers/srv/routes/0 can still read and modify a different array element by requesting: /config/apps/http/servers/srv/routes/01 This happens because: the authorization layer uses string prefix matching the /config traversal layer parses array indices numerically using strconv.Atoi() So: authorization sees /…/01 as matching /…/0 traversal resolves 01 to numeric index 1 the request therefore targets routes[1], not routes[0] This is not just a prefix-match …
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}
The FastCGI transport's splitPos() in modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go misuses golang.org/x/text/search with search.IgnoreCase when the request path contains a non-ASCII byte. Two distinct flaws in that fallback let an attacker mislead Caddy's FastCGI splitting into treating a non-.php (or other configured split_path extension) file as a script. In any deployment where the attacker can place content into a file served via FastCGI (uploads, file storage, etc.), this can be escalated to remote code …
The vars_regexp matcher in vars.go:337 double-expands user-controlled input through the Caddy replacer. When vars_regexp matches against a placeholder like {http.request.header.X-Input}, the header value gets resolved once (expected), then passed through repl.ReplaceAll() again (the bug). This means an attacker can put {env.DATABASE_URL} or {file./etc/passwd} in a request header and the server will evaluate it, leaking environment variables, file contents, and system info. header_regexp does NOT do this — it passes header …
Caddy'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. When 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 …
Caddy's FastCGI path splitting logic computes the split index on a lowercased copy of the request path and then uses that byte index to slice the original path. This is unsafe for Unicode because strings.ToLower() can change UTF-8 byte length for some characters. As a result, Caddy can derive an incorrect SCRIPT_NAME/SCRIPT_FILENAME and PATH_INFO, potentially causing a request that contains .php to execute a different on-disk file than intended (path …
Caddy's FastCGI path splitting logic computes the split index on a lowercased copy of the request path and then uses that byte index to slice the original path. This is unsafe for Unicode because strings.ToLower() can change UTF-8 byte length for some characters. As a result, Caddy can derive an incorrect SCRIPT_NAME/SCRIPT_FILENAME and PATH_INFO, potentially causing a request that contains .php to execute a different on-disk file than intended (path …
Two swallowed errors in ClientAuthentication.provision() cause mTLS client certificate authentication to silently fail open when a CA certificate file is missing, unreadable, or malformed. The server starts without error but accepts any client certificate signed by any system-trusted CA, completely bypassing the intended private CA trust boundary.
Two swallowed errors in ClientAuthentication.provision() cause mTLS client certificate authentication to silently fail open when a CA certificate file is missing, unreadable, or malformed. The server starts without error but accepts any client certificate signed by any system-trusted CA, completely bypassing the intended private CA trust boundary.
Caddy's HTTP path request matcher is intended to be case-insensitive, but when the match pattern contains percent-escape sequences (%xx) it compares against the request's escaped path without lowercasing. An attacker can bypass path-based routing and any access controls attached to that route by changing the casing of the request path.
Caddy's HTTP path request matcher is intended to be case-insensitive, but when the match pattern contains percent-escape sequences (%xx) it compares against the request's escaped path without lowercasing. An attacker can bypass path-based routing and any access controls attached to that route by changing the casing of the request path.
Caddy's HTTP host request matcher is documented as case-insensitive, but when configured with a large host list (>100 entries) it becomes case-sensitive due to an optimized matching path. An attacker can bypass host-based routing and any access controls attached to that route by changing the casing of the Host header.
Caddy's HTTP host request matcher is documented as case-insensitive, but when configured with a large host list (>100 entries) it becomes case-sensitive due to an optimized matching path. An attacker can bypass host-based routing and any access controls attached to that route by changing the casing of the Host header.
The path sanitization in file matcher doesn't sanitize backslashes which can lead to bypassing path related security protections.
The path sanitization in file matcher doesn't sanitize backslashes which can lead to bypassing path related security protections.
The local caddy admin API (default listen 127.0.0.1:2019) exposes a state-changing POST /load endpoint that replaces the entire running configuration. When origin enforcement is not enabled (enforce_origin not configured), the admin endpoint accepts cross-origin requests (e.g., from attacker-controlled web content in a victim browser) and applies an attacker-supplied JSON config. this can change the admin listener settings and alter HTTP server behavior without user intent.