Commit Graph

2629 Commits (cba4f9aabbee15eb669a0f42e66179f914a2ca5d)

Author SHA1 Message Date
Zen Dodd cba4f9aabb
deps: update GitHub Actions and Go modules (#7876)
Co-authored-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2026-07-25 10:32:30 +00:00
Mohammed Al Sahaf 31daa220fc
log: support `d` (day) in `roll_interval` directive (#7900)
Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2026-07-22 01:39:33 +03:00
Herman Slatman 9738f49b95
pki: Handle error immediately after reading root from disk (#7896)
In https://github.com/caddyserver/caddy/pull/7057, the behavior
of `KeyPair.Load` was changed to return a certificate chain. While
the change was primarily meant for intermediates, it also affected
how a root (or roots) were loaded. The existing error handling
logic relied on a shared `err != nil` check, but with the new
behavior there's no guarantee that there's actually a root in
the PEM file on disk.

This commit handles the error immediately after reading the PEM
from disk.

Fixes: #7895
2026-07-19 23:59:46 -04:00
futurehua 93c0721156
chore: fix sabotage spelling in nolint comments (#7892)
Signed-off-by: futurehua <futurehua@outlook.com>
2026-07-18 08:14:38 +00:00
Pichu Chen 986753a7c6
logging: hash query parameter values in QueryFilter (#7884) 2026-07-15 16:38:58 +00:00
a 873fac5fc0
caddyfile: treat quoted braces as literal arguments (#7875) 2026-07-12 10:33:32 +10:00
TowyTowy b2be548275
reverseproxy: fix broken reservoir sampling in random_choose policy (#7873)
The random_choose selection policy is meant to implement
power-of-d-choices: sample d available upstreams uniformly, then pick
the least-loaded of that sample. The sampling loop, however, was not a
correct reservoir sample (Algorithm R): it never filled the first k
reservoir slots unconditionally, instead writing every candidate to a
random slot j = rand(i+1), which can evict an earlier candidate while
leaving another slot nil. It also derived j from the upstream's index
in the pool rather than from the number of available upstreams seen,
skewing the sample whenever unavailable upstreams precede available
ones.

As a result the reservoir frequently held fewer than min(k, available)
upstreams, so the least-load comparison often never happened. Most
notably, with two upstreams and 'random_choose 2' (the canonical
power-of-two-choices setup), half of all requests were routed to the
more-loaded upstream even when it was saturated and the other idle --
identical behavior to plain 'random'. The nil slots this leaves behind
were also the cause of the panic reported in #3810, which was patched
by skipping nils in leastRequests rather than by fixing the sampling.

Replace the loop with a standard Algorithm R reservoir sample over the
available upstreams: fill the first k slots, then replace a random slot
with probability k/n. Every available upstream is now sampled uniformly
and the reservoir always holds min(k, available) candidates.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 09:25:31 +10:00
techknowlogick b2693fb63a
build(deps): bump cel-go from v0.28.1 to v0.29.2 (#7872) 2026-07-12 09:17:08 +10:00
TowyTowy c1907df2ed
intercept: fix misleading handle_response error for extra matcher args (#7871)
Same obsolete two-argument check as the reverse_proxy one removed in
#7869: it referenced the long-removed inline status-code-replacement
syntax and pointed users at replace_status even when no status
replacement was involved. The generic check now rejects all
excess-argument cases with an accurate message.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 09:16:34 +10:00
Ta Duc Thien c6180a0852
caddyhttp: fix path_regexp (MatchPathRE) Windows backslash bypass (#7858)
* caddyhttp: normalize Windows path in path_regexp matcher, shared with path matcher

Apply the same Windows path normalization to MatchPathRE that MatchPath already had, and factor it into a shared normalizeWindowsPath helper so both matchers use one implementation.

Also strip trailing dots and spaces per path component (not only at the end of the whole path), matching how Windows resolves paths; this fixes the same gap in the path matcher too. Adds regression tests for both matchers.

See #5613.

* caddyhttp: normalize trailing dots/spaces in escaped-path branch too

The MatchPath escaped-path branch (taken when a matcher pattern contains
'%') only folded backslash separators via windowsEscapedPathSeparatorRepl;
it skipped the per-component trailing dot/space normalization now applied
on the decoded branch. On Windows this left a bypass: a matcher such as
`path /private%2f*` could be evaded by GET /private.%5csecret.txt, since
`private.` and `private` resolve to the same directory on NTFS.

Add normalizeWindowsEscapedPath, which trims trailing dots and spaces —
literal ("." / " ") and percent-encoded ("%2e" / "%20") — from every
component in raw/escaped space, splitting on both '/' and encoded '%2f'
separators while preserving them, and leaving "." / ".." for CleanPath.
Regression tests cover the literal and percent-encoded variants against a
'%'-containing matcher.

---------

Co-authored-by: thientd <thien.taduc@ninhthanh.com>
2026-07-11 08:37:44 +10:00
WeidiDeng 1830809afe
reverseproxy: save dial info in a context key instead of a variable key to avoid race conditions when forward_auth is used (#7859) 2026-07-10 09:37:07 -06:00
TowyTowy 945d199724
reverseproxy: fix misleading handle_response error for extra matcher args (#7869)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 05:40:23 +00:00
alhuda 75c988d118
reverseproxy: compare sticky-session cookie hash in constant time (#7853) 2026-07-10 09:16:25 +10:00
Kévin Dunglas 4e62095245
core: preserve metrics registry in Context.WithValue (#7861)
* core: preserve metrics registry in Context.WithValue

Context.WithValue rebuilt the Context without copying the unexported
metricsRegistry field, so any module provisioned under a context
derived via WithValue saw a nil registry from GetMetricsRegistry().

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Kévin Dunglas <kevin@dunglas.fr>

---------

Signed-off-by: Kévin Dunglas <kevin@dunglas.fr>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-08 04:52:36 +10:00
alhuda 08ad064160
caddyhttp: fix escaped path matcher over-matching longer paths (#7828) 2026-07-08 04:29:44 +10:00
Kévin Dunglas 13a4c3f43c
caddyhttp: add URL pattern request matcher (#7787)
* caddyhttp: add url_pattern request matcher

Match requests against a URLPattern (https://urlpattern.spec.whatwg.org/),
supporting named groups, wildcards and regexp components beyond the path
matcher. Relative patterns match any origin; absolute patterns or base_url
scope to scheme and host.

Exposes a url_pattern CEL function and publishes captured groups as
{http.url_pattern.<component>.<group>} placeholders.

* caddyhttp: add Caddyfile adapt test for url_pattern matcher
2026-06-22 10:25:43 -06:00
yintaisha f450068460
http: normalize method names to uppercase in MatchMethod.Provision (#7832)
Signed-off-by: yintaisha <yintaishan@outlook.com>
2026-06-21 08:19:21 +00:00
Matthew Holt 51a4bde176
Update human and agent contributing guidelines 2026-06-20 14:19:24 -06:00
Matthew Holt 5760382213
caddyhttp: Clean up variable scope in vars matcher 2026-06-20 13:45:10 -06:00
Matthew Holt 30f0ddd912
caddyhttp: Document dropping underscore headers 2026-06-20 13:45:10 -06:00
Matthew Holt 6ab855d3c4
browse: Update Caddy logo 2026-06-20 13:45:10 -06:00
wangdongyong 69d6ace32e
tracing: fix BatchSpanProcessor goroutine leak on config reload (#7826)
* tracing: fix BatchSpanProcessor goroutine leak on config reload

When the `tracing` directive is enabled, each config reload leaks one
`go.opentelemetry.io/otel/sdk/trace.(*batchSpanProcessor).processQueue`
goroutine. On a server that reloads frequently (e.g. polling a remote
config source every few seconds) this accumulates into tens of thousands
of leaked goroutines over time.

A goroutine dump shows many identical stacks:

```
goroutine ... [select]:
go.opentelemetry.io/otel/sdk/trace.(*batchSpanProcessor).processQueue(...)
	.../sdk/trace/batch_span_processor.go:327
go.opentelemetry.io/otel/sdk/trace.NewBatchSpanProcessor.func2()
	.../sdk/trace/batch_span_processor.go:129
created by go.opentelemetry.io/otel/sdk/trace.NewBatchSpanProcessor
	.../sdk/trace/batch_span_processor.go:127
```

`tracing` keeps a global, reference-counted `TracerProvider` so it can be
reused across reloads (`tracerProvider.getTracerProvider`). Caddy reloads
provision the new config *before* cleaning up the old one, so the counter
never drops to 0 and the provider is correctly reused — `Shutdown` is
never called, by design.

The problem is on the caller side in `newOpenTelemetryWrapper`:

```go
traceExporter, err := autoexport.NewSpanExporter(ctx)
...
tracerProvider := globalTracerProvider.getTracerProvider(
    sdktrace.WithBatcher(traceExporter),   // evaluated on every reload
    sdktrace.WithResource(res),
)
```

`sdktrace.WithBatcher(e)` is `WithSpanProcessor(NewBatchSpanProcessor(e))`,
and `NewBatchSpanProcessor` **starts its `processQueue` goroutine eagerly at
construction time** — not when the option is applied. The option is built
on every `Provision` (every reload), but `getTracerProvider` only applies
it when it actually creates a new provider (`t.tracerProvider == nil`). On
the reuse path the option is silently discarded, so the just-started
BatchSpanProcessor goroutine is orphaned: it is never registered with any
provider and therefore never shut down. Result: one leaked goroutine (plus
a leaked exporter) per reload.

Defer construction of the exporter/batcher until a new provider is actually
needed. `getTracerProvider` now takes a `buildOpts` factory that is invoked
only on the create path, so nothing with a side effect is built on the
reuse path.

The reference counter is now incremented only after the provider is
successfully obtained, preserving the previous semantics where a failed
exporter creation did not affect the counter.

- `Test_tracersProvider_buildOptsOnlyOnCreate` — asserts `buildOpts` runs
  exactly once across one create + five reuses (the regression guard).
- `Test_tracersProvider_buildOptsError` — asserts that on a build error the
  provider stays nil and the counter is not incremented.
- Existing tracing tests updated for the new signature and still pass.

Verified manually with a reload loop: before the fix, 50 reloads leaked 50
`processQueue` goroutines; after the fix, 0 are leaked while the provider is
still reused (counter stays at 1).

* add test for buldOpts error path

---------

Co-authored-by: Zen Dodd <mail@steadytao.com>
2026-06-18 11:42:58 -06:00
Saleh d2e0ad1e92
reverseproxy: log status 499 instead of 0 when client disconnects (#7827) 2026-06-18 13:31:02 +10:00
Matthew Holt ab56721a73
Remove -v from tests
Also add release notes to TODO for release proposal flow
2026-06-17 10:38:00 -06:00
Matt Holt 4dbe0a93be
readme: Update logo
Signed-off-by: Matt Holt <mholt@users.noreply.github.com>
2026-06-16 23:39:45 -06:00
alhuda ae9bc028e6
rewrite: scope keyed query replace to its named key (#7818)
* rewrite: scope keyed query replace to its named key

* rewrite: cover keyed search_regexp query replace in test

* rewrite: provision query replace test via Provision path
2026-06-16 01:01:05 +10:00
Luccin Masirika 39c9a85f80
fileserver: append repeated `hide` subdirectives instead of overwriting (#7817)
Multiple `hide` subdirectives in a file_server Caddyfile block silently overwrote each other, so only the last one took effect. Append to the list instead, so repeated entries accumulate and imported snippets can compose with site-specific hides.
2026-06-13 22:44:19 +10:00
long.black 16235cced5
intercept: fix replace_status being silently dropped (#7810)
* test: add failing tests for intercept replace_status (#7805)

Add integration tests that verify replace_status actually substitutes
the HTTP status code sent to the client. Currently these tests fail
because replace_status is silently a no-op due to value-receiver
boxing and shouldBuffer returning false.

Tests added:
- TestInterceptReplaceStatusWithMatcher: 500 -> 200 with @err matcher
- TestInterceptReplaceStatusWithoutMatcher: 403 -> 200 unconditionally
- TestInterceptReplaceStatusNotMatched: 200 passes through unchanged

* fix: make intercept replace_status actually substitute the status code

Fix #7805: replace_status was silently a no-op because:
1. shouldBuffer returned false when a replacement status was set,
   causing the original status to be streamed directly to the wire
2. The value-receiver WriteHeader method operated on a stale copy

The fix:
- shouldBuffer now returns true when replace_status matches, so
  the response is buffered instead of streamed
- After next.ServeHTTP returns, if routes are nil (replace_status
  only), write the substituted status and buffered body to the client

The interceptedResponseHandler.WriteHeader substitution branch is no
longer needed for this path since substitution happens post-ServeHTTP.

* refactor: remove dead WriteHeader method and resolved TODO

The value-receiver WriteHeader on interceptedResponseHandler was
unreachable dead code — the substitution is now handled post-ServeHTTP
via buffering. Remove it along with the TODO comment that noted
status code replacement was unfinished.

* style: apply nit suggestions from dunglas code review

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 15:50:58 -06:00
alhuda 52dc6709fb
rewrite: fix wrong index check in trimPathPrefix (#7812) 2026-06-12 14:11:21 -06:00
Dionis Ramadani 25b3eab6db
Merge commit from fork
* fix(reverseproxy): hop-by-hop header fix

* test(headers): Added unit tests for hop-by-hope header behavior
2026-06-12 12:39:01 -06:00
Bluegate Studio fcba554d65
caddyhttp: New expected_underscore_headers server option (#7809)
* caddyhttp: restore allow_underscore_in_headers server option (#7808)

* caddyhttp: mark insecure_allow_underscore_in_headers as EXPERIMENTAL

* caddyhttp: replace underscore bool with expected_underscore_headers allowlist

* fix gofmt alignment in serveroptions.go

* caddyhttp: drop repeated allowlisted underscore headers

* caddyhttp: add tests for repeated-value drop and variant-drop logging
2026-06-11 22:26:21 -06:00
Sam Ottenhoff 997d3f6b0a
encode: add standard benchmark and conformance harness (#7804)
This is a shared encode_test harness with HTML/JSON/JS/CSS payloads taken from caddyserver.com

Benchmarks:
- BenchmarkStandardEncodingPayloads: raw encoder NewEncoder/Write/Close path
- BenchmarkEncodeHandlerCorpus: full Encode.ServeHTTP middleware path
- Grid: 4 payloads × gzip levels 1/5/9 × zstd fastest/default/best
- Each subtest runs with 4 parallel workers; compare runs on MB/s and allocs/op

Conformance tests:
- Encoder contract: Reset, Flush, Close, and pool-style Reset-after-Close reuse
- Corpus HTTP encoding: Content-Encoding, Vary, ETag suffix, header stripping
- Response semantics: minimum_length, 304, HEAD, range, WebSocket bypass,
  If-None-Match rewrite, Cache-Control no-transform, content-type matcher rejection
2026-06-11 17:55:18 -06:00
Rhul 0f7f8e9cf6
forwardauth: error on duplicate uri subdirective (#7814) 2026-06-10 23:31:04 -04:00
Matthew Holt 4fd8c87f56
caddyhttp: Default max_header_bytes to 16 KiB 2026-06-09 12:07:27 -06:00
Rhul 55b3397a2d
reverseproxy: validate on weighted_round_robin loadbalancing policy (#7807)
* reverseproxy: validate on weighted_round_robin policy

Validate that weighted_round_robin has a non-zero total weight.
This prevents configurations such as:
    weighted_round_robin 0 0
from being accepted and causing a divide-by-zero panic during request handling.

* test: validation test on zero weight upstreams.

* test: provision called instead of totalweight setting

* reverseproxy: validate on negative upstream weights

* test: regression test on weighted_round_robin selection policy
2026-06-08 02:18:20 +10:00
Micky d3986f824d
Add missing "is" (#7802)
Signed-off-by: Micky <60691199+AliMickey@users.noreply.github.com>
2026-06-06 21:02:51 +00:00
Y.Horie d730df2a83
cmd: colored error message in WrapCommandFuncForCobra (#7760) (#7768)
Signed-off-by: Y.Horie <u5.horie@gmail.com>
Co-authored-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2026-06-04 21:41:35 -04:00
Rhul 3b7bde8f25
httpcaddyfile: error on duplicate named_routes (#7800)
* fix: error on duplicate named_routes

Fixes issue #7798

Validate named route names before inserting them into the
named route map.

This prevents later definitions from overwriting existing
named routes and returns an error when a route name is
defined more than once.

* test: add test for duplicate named_routes
2026-06-04 14:55:08 -04:00
Muhammad Syafri, S.Kom 915793f6e0
caddyhttp: add {http.request.proto_name} placeholder for spec-compliant protocol names (#7782)
* caddyhttp: add {http.request.proto_name} placeholder for spec-compliant protocol names

{http.request.proto} exposes Go's raw http.Request.Proto field which
returns HTTP/2.0 and HTTP/3.0 for HTTP/2 and HTTP/3 respectively.
These strings are non-standard since the specs define them as HTTP/2
and HTTP/3.

To preserve backward compat (especially CGI/FastCGI expectations),
{http.request.proto} is kept as-is. A new {http.request.proto_name}
placeholder is introduced that normalises the version string to the
spec-defined form:
  HTTP/2.0 -> HTTP/2
  HTTP/3.0 -> HTTP/3
  all others returned unchanged

Closes #7734

* caddyhttp: Use ProtoMajor for proto_name normalization and update docs

---------

Co-authored-by: jalikajalika5 <105954036+jalikajalika5@users.noreply.github.com>
2026-06-04 11:03:19 -04:00
WeidiDeng fcc7860d03
reverseproxy: replace placeholders specified for sni while using http3 (#7737)
* reverseproxy: replace placeholders specified for sni while using http3

* add test for placeholder

* reverseproxy: replace placeholders specified for sni while using http3

* add test for placeholder

* reverseproxy: test HTTP/3 SNI host placeholder

---------

Co-authored-by: Zen Dodd <mail@steadytao.com>
2026-06-02 21:49:00 -06:00
JM Sanchez e2eee6a7fc
templates: Patch for GHSA-vcc4-2c75-vc9v (#7785)
* Patch GHSA-vcc4-2c75-vc9v in stripHTML

templates: fix funcStripHTML bypass via depth counter

The previous false-start approach allowed XSS bypass via inputs like <<>img src=x onerror=alert(1)> and failed on stacked angle brackets.

Replace the tagStart/inTag state machine with a depth counter that mirrors PHP strip_tags behaviour: each '<' increments depth, each '>' decrements it, and text is only emitted at depth zero. Quoted attribute values (both single and double) are tracked so '>' inside href values does not prematurely close a tag.

Signed-off-by: JM Sanchez <77505889+jmrcsnchz@users.noreply.github.com>

* Update tplcontext_test.go

Templates: expand TestStripHTML with attack path coverage

Signed-off-by: JM Sanchez <77505889+jmrcsnchz@users.noreply.github.com>

---------

Signed-off-by: JM Sanchez <77505889+jmrcsnchz@users.noreply.github.com>
2026-06-01 13:35:02 -06:00
Bruno Teixeira Lopes 0e8eb41b87
httpcaddyfile: fix incorrect error message on duplicate matchers (#7780)
Parse each matcher segment individually using NewDispenser(segment) instead
of DispenseDirective(dir), which coalesced all same-name segments into one
token stream. This caused the second definition name to be misinterpreted
as a matcher module name, producing 'module not registered: http.matchers.@name'
instead of the correct 'matcher is defined more than once' error.

By parsing segments individually, the existing duplicate check in
parseMatcherDefinitions naturally catches the duplicate on the second pass.

Signed-off-by: Brunotlps <brunoteixlps@gmail.com>
2026-05-30 07:05:41 +10:00
Kévin Dunglas 3eb8e48ff0
Merge commit from fork
* feat: drop headers with underscore in their names

* feat: Caddyfile binding and tests for underscore-in-header drop

Add the `allow_underscore_in_headers` global server option, refine the
doc comment, and cover the filter end-to-end: server-level unit tests
(drop, opt-out, debug log, RFC-7230 space rejection), a fastcgi unit
test for the trimmed header name replacer, and forward_auth integration
tests for both the default-drop and opt-out paths.

* remove allow_underscore_in_headers option for now
2026-05-29 11:37:17 -06:00
Muhammad Syafri, S.Kom 03e08ee6a9
encode: prioritize zstd and br over gzip in content negotiation (#7772)
* fix(encode): prioritize zstd and br over gzip in content negotiation

* test(encode): update unit tests to reflect new default priority ties

* fix(encode): move default preferences to dynamic encode handler and restore generic negotiation helper

* test(encode): call real Provision function in served-response test

* test(encode): rename served-response test to TestServeHTTPDefaultEncodingPreference

* refactor(encode): use slices.SortStableFunc and httptest.NewRecorder as recommended

* refactor(encode): simplify sorting with cmp.Compare and check request error in test

* test(encode): fix variable redeclaration in TestServeHTTPDefaultEncodingPreference

Fix 'no new variables on left side of :=' error by changing 'err :=' to 'err ='
on line 347, since err was already declared on line 332.

This fixes the build failure in the encode module tests.
2026-05-29 05:26:19 +10:00
gelsomino 86121c860f
caddytls: skip idna.ToASCII for pure ASCII SNI values (#7770)
SNI is always ASCII on the wire (RFC 6066), and most config
patterns are also ASCII. For pure ASCII input, idna.ToASCII
only validates and lowercases, which is equivalent to a simple
strings.ToLower. Add a fast path to avoid the overhead of
idna.ToASCII in the common case.
2026-05-28 11:18:09 +10:00
Muhammad Syafri, S.Kom 4d60d936ed
perf(replacer): optimize memory allocation for file placeholders (#7773)
Co-authored-by: jalikajalika5 <105954036+jalikajalika5@users.noreply.github.com>
2026-05-27 14:20:33 +00:00
Lohit 176b043b01
rewrite: prevent placeholder re-expansion in injected query (#7761)
When the rewrite URI template ends with a literal '?' and contains a placeholder that expands to client-controlled bytes (e.g. {http.request.header.X-Fwd}), those bytes flow into buildQueryString which runs a second Replacer pass. If the bytes contain placeholder syntax such as {env.SECRET}, that placeholder is evaluated, allowing disclosure of environment variables, files (via {file./path}), or internal request vars through the rewritten request URI.

Escape '{' and '}' in the injected query before assigning it to the query variable, so the second pass cannot find any placeholder syntax to evaluate. Operator-written placeholders in the rewrite template are already expanded by the first pass on the path component, so the only '{' or '}' surviving into the injected query must have come from replacement values.

Fixes GHSA-j8px-rmrx-76h9.

Includes three regression tests mirroring the 'is not re-expanded' tests in modules/caddyhttp/vars_test.go.

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2026-05-26 16:51:18 -06:00
Matthew Holt 4c04143261
Clarify policies for agents / LLM use 2026-05-26 14:03:39 -06:00
Zen Dodd 94fcea08f4
go.mod: update x/net (#7767) 2026-05-25 12:24:44 -04:00
Matthew Holt 44b667a79f
go.mod: Update x/crypto 2026-05-22 09:25:04 -06:00