---
id: "GHSA-25cw-38mq-q7m6"
page: "https://infrastack.ai/live/GHSA-25cw-38mq-q7m6"
kind: "vulnerability"
aliases: ["GHSA-25cw-38mq-q7m6"]
title: "file matcher resolves /file/./ to a regular file, bypassing suffix path rules"
severity: "medium"
cvss: null
epss: null
exploited: false
status: "open"
category: "package"
products: [{"name": "github.com/caddyserver/caddy/v2", "fixed": ["2.11.8"], "affected": "<= 2.11.7", "ecosystem": "go"}]
ecosystems: ["go"]
tags: ["caddy", "cwe-41", "frankenphp", "package", "php", "self-hosted"]
not_affected_if: ["managed-hosting", "no-try-files-path", "prefix-rules-only"]
published_at: "2026-11-04T00:59:36+00:00"
first_seen_at: "2026-11-09T22:53:33+00:00"
updated_at: "2026-11-09T22:58:46+00:00"
sources: [{"source": "ghsa-repo", "url": "https://github.com/caddyserver/caddy/security/advisories/GHSA-25cw-38mq-q7m6", "first_seen_at": "2026-11-09T22:53:33+00:00"}]
exploit_sources: []
---

# file matcher resolves /file/./ to a regular file, bypassing suffix path rules

Link for people: https://infrastack.ai/live/GHSA-25cw-38mq-q7m6 (this .md is for agents)

Caddy's file matcher turns a request like /.env/./ into the regular file /.env, so path rules written to block that file are skipped. An unauthenticated client can read blocked static files such as .env, backups or configuration.

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- go:github.com/caddyserver/caddy/v2 affected <= 2.11.7 fixed 2.11.8

## How to check

- Run `caddy version` and compare against the fixed release.
- Look in the Caddyfile for `try_files {path}` combined with `path` or `path_regexp` block rules, or for `php_fastcgi` or FrankenPHP `php_server` followed by `file_server`.

## Fix

- github.com/caddyserver/caddy/v2: upgrade to `2.11.8`

## Timeline

- 2026-11-09T22:53:33+00:00 ghsa-repo https://github.com/caddyserver/caddy/security/advisories/GHSA-25cw-38mq-q7m6

## Documents

- [advisory] Maintainer's advisory: github.com https://github.com/caddyserver/caddy/security/advisories/GHSA-25cw-38mq-q7m6

## From the sources

### Summary

`try_files {path}` resolves `/.env/./` to the regular file `/.env`, while path matchers see `/.env/`. A rule such as `path *.env` placed before the rewrite doesn't match, and `file_server` serves the file without its canonical redirect. `php_fastcgi` and FrankenPHP's `php_server` are affected through their built-in try_files route.

### Details

The `file` matcher (`modules/caddyhttp/fileserver/matcher.go`) runs `path.Clean` on each expanded candidate, which drops the trailing slash, and restores it only when the try_files pattern itself ends in `/`. So `try_files {path}` resolves `/.env/./` to the regular file `/.env` and rewrites the request to it.

Path matchers see `/.env/` for that request, so suffix rules, exact paths, globs ending in a file name and `$`-anchored `path_regexp` evaluated before the rewrite don't match. `file_server` then skips its canonical redirect because `path.Base` of the original path (`.`) and of the rewritten path (`.env`) differ, and serves the file.

Fix: keep the trailing slash of the expanded candidate through `path.Clean`. `strictFileExists` already requires a candidate ending in `/` to be a directory, so `/.env/` and `/.env/./` no longer match the regular file, while `/dir/./` still matches the directory and `split_path` keeps working. Patch: public PR from `dunglas:fileserver-match-trailing-slash`.

Discovered by Anthropic's OSS Scanner, as vulnerability ANT-2026-VAGWNYYZ.

### Proof of concept

```caddyfile
:8080 {
	root /srv
	@blocked path *.env
	route {
		respond @blocked 403
		try_files {path} {path}/ /index.html
		file_server
	}
}
```

```console
$ curl -i localhost:8080/.env                        # 403
$ curl -i --path-as-is localhost:8080/.env/./        # 200, body of /srv/.env
```

The same works with `php_fastcgi` followed by `file_server`.

### Impact

Unauthenticated clients read static files (`.env`, backups, configuration) that a suffix or exact path rule is meant to block. Prefix rules and `*/.*` still block. Nothing gets executed.

---

_Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai_
