Both fallbacks in splitPos relied on golang.org/x/text/search with
search.IgnoreCase, which performs Unicode equivalence matching far beyond
ASCII case folding. Combined with the validated-ASCII guarantee on every
SplitPath entry, that fallback turned non-PHP filenames into PHP scripts:
- when the inner loop hit a non-ASCII byte and the IndexString fallback
returned -1, the loop broke without resetting match=false, so a stale
match=true caused a non-existent .php to be reported (PoC:
"/name.<U+00A1>.txt").
- search.IgnoreCase folded fullwidth, mathematical and circled letters
onto ASCII, so "/shell.<math sans-serif php>",
"/shell.<fullwidth p>hp", "/shell.<circled php>" were all detected as
".php" files.
Replace the fallback with strict byte-level ASCII case-insensitive
matching: any byte >= utf8.RuneSelf in the path can never be part of a
match, since SplitPath entries are validated ASCII-only and lower-cased
in Provision(). This keeps the hot path branch-light and removes the
x/text/search dependency from the main module.
Reported against FrankenPHP as GHSA-3g8v-8r37-cgjm and
GHSA-v4h7-cj44-8fc8. The vulnerable function in this module was adapted
from the same FrankenPHP code.
* reverseproxy: Add ability to clear dynamic upstreams cache during retries
This is an optional interface for dynamic upstream modules to implement if they cache results.
TODO: More documentation; this is an experiment.
* Add some godoc
* Export interface; update godoc
* admin: Redact sensitive request headers in API logs
* Fix govulncheck and typed atomic lint failures
* Sync Go module metadata after dependency downgrade
* add 'root' key to Helper.State for access in frankenphp's `php_server` directive
* clone state before passing it to child directives, but keep sharing it among sibling directives
* propagate named route state from children to parent
* use BlockState to set "root" instead
* gofmt -w .
* go fmt ./...
* here we go
When using copy_headers in a forward_auth block, client-supplied headers with
the same names were not being removed before being forwarded to the backend.
This happens because PR #6608 added a MatchNot guard that skips the Set
operation when the auth service does not return a given header. That guard
prevents setting headers to empty strings, which is the correct behavior,
but it also means a client can send X-User-Id: admin in their request and
if the auth service validates the token without returning X-User-Id, Caddy
skips the Set and the client value passes through unchanged to the backend.
The fix adds an unconditional delete route for each copy_headers entry,
placed just before the existing conditional set route. The delete always runs
regardless of what the auth service returns. The conditional set still only
runs when the auth service provides that header.
The end result is:
- Client-supplied headers are always removed
- When the auth service returns the header, the backend gets that value
- When the auth service does not return the header, the backend sees nothing
Existing behavior is unchanged for any deployment where the auth service
returns all of the configured copy_headers entries.
Fixes GHSA-7r4p-vjf4-gxv4
This refactors the initial approach in PR #7281, replacing the UsagePool
with a dedicated package-level sync.Map and atomic.Int64 to track
in-flight requests without global lock contention.
It also introduces a lookup map in the admin API to fix a potential
O(n^2) iteration over upstreams, ensuring that draining upstreams
are correctly exposed across config reloads without leaking memory.
Co-authored-by: Y.Horie <u5.horie@gmail.com>
reverseproxy: optimize in-flight tracking and admin API
- Replaced sync.RWMutex with sync.Map and atomic.Int64 to avoid lock contention under high RPS.
- Introduced a lookup map in the admin API to fix a potential O(n^2) iteration over upstreams.
When a request arrives via a Unix domain socket (RemoteAddr == "@"),
net.SplitHostPort fails, causing addForwardedHeaders to strip all
X-Forwarded-* headers even when the connection is trusted via
trusted_proxies_unix.
Handle Unix socket connections before parsing RemoteAddr: if untrusted,
strip headers for security; if trusted, let clientIP remain empty (no
peer IP for a Unix socket hop) and fall through to the shared header
logic, preserving the existing XFF chain without appending a spurious
entry.
Amp-Thread-ID: https://ampcode.com/threads/T-019c4225-a0ad-7283-ac56-e2c01eae1103
Co-authored-by: Amp <amp@ampcode.com>
* capture the buffered body once, then reset clonedReq.Body before each retry
* no copy
* keep receiver name
* set the buf to nil after extraction and only return it to pool if not nil
---------
Co-authored-by: WeidiDeng <weidi_deng@icloud.com>
* chore: ugh, lint fix...
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
* more lint fixes
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
---------
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
`pflag.GetStringSlice` treats commas as delimiters, which causes issues
when passing headers whose values contain commas (`X-Robots-Tag:
noindex, nofollow`). These are incorrectly split into multiple headers
and errors out:
- `X-Robots-Tag: noindex`
- ` nofollow`
Switch to `pflag.GetStringArray`, which does not split on commas[1].
Note that this changes behavior for cases where multiple headers were
provided in a single argument with commas (`--header-down "X-Foo:
Bar,X-Bar: Foo"`). Such cases will now be treated as a single header
value. If this breaking change is unacceptable, we will need a smarter
fallback mechanism.
[1] https://github.com/spf13/pflag/pull/90
* chore: upgrade .golangci.yml and workflow to v2
run `golangci-lint fmt`
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
* run `golangci-lint run --fix`
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
* more lint fixes
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
* bring back comments to .golangci.yml
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
* appease the linter some more
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
* oops
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
* use embedded structs
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
* use embedded structs where they were used before
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
* disable rule `-QF1006`
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
* missed a spot
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
---------
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>