Skip to content

fix(caddyfile): stop blanking every forwarded TLS/request header - #3124

Merged
forgetso merged 1 commit into
mainfrom
fix/caddyfile-tls-header-blanking
Aug 25, 2026
Merged

fix(caddyfile): stop blanking every forwarded TLS/request header#3124
forgetso merged 1 commit into
mainfrom
fix/caddyfile-tls-header-blanking

Conversation

@forgetso

Copy link
Copy Markdown
Member

Problem

Every x-tls-* / x-client-ip / x-method / x-remote-* header forwarded to the provider was written as a two-line header_up pair — the value line plus a header_up X "^{placeholder}$" "" guard meant to blank the header when the placeholder didn't expand:

header_up x-tls-resumed "{http.request.tls.resumed}"
header_up x-tls-resumed "^{http.request.tls.resumed}$" ""

The guard is self-defeating: Caddy expands placeholders inside the search argument too, so ^{http.request.tls.resumed}$ becomes the same value line 1 just set, the regex always matches, and the header is replaced with "".

Impact

Every stable TLS/connection field was blanked on 100% of requestsx-tls-resumed, x-tls-version, x-tls-cipher, x-tls-server-name, x-client-ip, x-method, x-remote-host, x-remote-port. Verified against 200k recent production sessions: x-tls-resumed was "" or absent on every single one.

The smoking gun: x-duration-ms uses the same pattern but survives, because its value ticks upward between the two evaluations, so the regex no longer matches. That's what identified the mechanism.

req.TLS is fine (chaddy's handler.go gates on req.TLS.HandshakeComplete and populates x-tls-clienthello), and the placeholders are valid — the values were purely being wiped by the guard.

Fix

Collapse each pair to a single line. Every placeholder is a valid Caddy v2.8.4 shorthand or {http.request.*} field (checked against shorthands.go / replacer.go), which resolves to its value or an empty string — never a literal — so the guard is unnecessary.

Also corrects two genuinely-invalid placeholders: {http.request.tls.public_key} / {http.request.tls.public_key_sha256} don't exist (Caddy only has them under the client-cert namespace). Left with the guard removed they'd forward a literal {...}; repointed to {http.request.tls.client.public_key[_sha256]}, which resolve to "" for the captcha clients (no client certs).

Result

x-tls-resumed now carries the real true/false session-resumption bit, and the other TLS/connection headers populate as intended. Applied to both provider.Caddyfile and local.Caddyfile; both pass caddy fmt.

🤖 Generated with Claude Code

Each x-tls-* / x-client-ip / x-method / x-remote-* header_up was written as a
two-line pair: the value line plus a `header_up X "^{placeholder}$" ""` guard
intended to blank the header when the placeholder didn't expand. The guard was
self-defeating — Caddy expands placeholders inside the search argument too, so
the regex always equalled the value just set and every stable field was
replaced with "". In production this blanked x-tls-resumed, x-tls-version,
x-tls-cipher, x-tls-server-name, x-client-ip, x-method, x-remote-host, etc. on
100% of requests (verified across 200k recent sessions: x-tls-resumed was ""
or absent on every one). Only x-duration-ms survived, because its value ticks
up between the two evaluations so the regex no longer matched — which is the
tell that identified the mechanism.

Collapse each pair to a single line. Every placeholder is a valid Caddy v2.8.4
shorthand or {http.request.*} field (verified against shorthands.go /
replacer.go) that resolves to its value or an empty string, never a literal,
so the guard is unnecessary.

Also fixes two placeholders that were invalid: {http.request.tls.public_key}
and {http.request.tls.public_key_sha256} do not exist (only the client-cert
namespace has public_key). Left as-is with the guard removed they would have
forwarded a literal; repointed to {http.request.tls.client.public_key[_sha256]},
which resolve to "" unless the client presents a certificate.

After this, x-tls-resumed carries the real TLS session-resumption bit
(true/false) and the other TLS/connection fields populate as intended.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@forgetso
forgetso merged commit cd95bfe into main Aug 25, 2026
10 of 12 checks passed
@forgetso
forgetso deleted the fix/caddyfile-tls-header-blanking branch August 25, 2026 22:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant