With the previous changes made to normalize request paths, we faced some issues:
* Regressions in Next.js route groups: https://github.com/cloudflare/cloudflared/issues/1719
* WordPress trailing slashes: https://github.com/cloudflare/cloudflared/issues/1717
This PR attempts to fix a vulnerability regarding un-normalized request paths using a different approach:
instead of mutating `req.URL.Path` before forwarding to the origin, we compute a cleaned path used only for
ingress rule selection, leaving the original request completely untouched. This separates the security classification decision from request proxying.
Since this can be a breaking change, we are adding it disabled by default and will only enable it in future releases.
Synchronize websocket writes before asserting so test failures stay
on the test goroutine. Also use the context-aware SOCKS dialer in
the origin connection test.
Redact recipient rules, improve protected tunnel startup output, and reject --allowed-mail for named tunnels.
Use state-specific cookies so concurrent login flows remain independent, with callbacks selecting and clearing only the matching state cookie.
This prevents users from abusing `--allowed-mail` policies by limiting the nr. of rules to 100 and defining a max per rule of 320 bytes. Validation errors now identify only the rule position and category, preventing recipient values from appearing in logs. Parsing also stops once limits are exceeded while preserving normalisation and deduplication.
Ensures protected response headers are filtered before `Flush` commits them. This prevents origins from bypassing reserved-cookie and cache-control protections through early flushing.
Raw response bodies were being logged by quick tunnel provisioner. This avoids logging the raw body, which could contain sensitive data, while preserving structured Worker errors.
To properly secure authorized requests, we need to strip any process-local session and authentication-state cookies before forwarding them. On protected responses, we also enforce `Cache-Control: private, no-store` and `Referrer-Policy: no-referrer` directives, to prevent sensitive data from being cached locally or by intermediaries and to block referrer URLs from leaking across requests.
Response headers and cookies for Public Quick Tunnel are preserved.
This MR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| gcr.io/distroless/base-debian13 | final | digest | `d199d20` → `0896741` |
---
### Configuration
📅 **Schedule**: (UTC)
- Branch creation
- At 12:00 AM through 04:59 AM and 10:00 PM through 11:59 PM, Monday through Friday (`* 0-4,22-23 * * 1-5`)
- Only on Sunday and Saturday (`* * * * 0,6`)
- Automerge
- At any time (no schedule defined)
🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.
♻ **Rebasing**: Whenever MR is behind base branch, or you tick the rebase/retry checkbox.
🔕 **Ignore**: Close this MR and you won't be reminded about this update again.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box
---
This MR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC44Mi4wIiwidXBkYXRlZEluVmVyIjoiNDQuODIuMCIsInRhcmdldEJyYW5jaCI6Im1hc3RlciIsImxhYmVscyI6W119-->
This MR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| gcr.io/distroless/base-debian13 | final | digest | `1411d9a` → `65795e7` |
---
### Configuration
📅 **Schedule**: (UTC)
- Branch creation
- At 12:00 AM through 04:59 AM and 10:00 PM through 11:59 PM, Monday through Friday (`* 0-4,22-23 * * 1-5`)
- Only on Sunday and Saturday (`* * * * 0,6`)
- Automerge
- At any time (no schedule defined)
🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.
♻ **Rebasing**: Whenever MR is behind base branch, or you tick the rebase/retry checkbox.
🔕 **Ignore**: Close this MR and you won't be reminded about this update again.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box
---
This MR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC44Mi4wIiwidXBkYXRlZEluVmVyIjoiNDQuODIuMCIsInRhcmdldEJyYW5jaCI6Im1hc3RlciIsImxhYmVscyI6W119-->
This adds a shared authorization boundary for protected Quick Tunnels before ingress selection. Unauthenticated requests are redirected or rejected, while requests with a valid local session continue to the origin after the internal authentication cookie is removed.
Before these changes, we could already login and validate sessions, but the request handler could not allow authenticated requests to continue to the origin. This change adds that authorization decision.
Public Quick Tunnels remain unchanged.
Validates callback-bound broker assertions, enforces recipient rules, and issues local sessions before redirecting with HTTP 303. Callback replay is bounded by the short-lived signed artifacts.
## Summary
- generate a cryptographically random process-local HMAC-SHA256 session signing key
- issue host-only, Secure, HttpOnly, SameSite=Lax Quick Tunnel session cookies
- cap sessions at the earlier of 4 hours or the Access identity expiry
- validate canonical cookie encoding, signatures, nonces, and expiry
- invalidate sessions across cloudflared restarts through process-local signing keys
- use `github.com/cloudflare/backoff` for tunnel retry jitter while preserving retry limits, retry-forever caps, and grace-period resets
## Security
- session payloads contain only a random nonce and expiry; no visitor identity or recipient rules
- malformed, tampered, expired, and previous-process sessions fail closed
- HMAC signatures are compared in constant time
## Validation
- `make test lint`
- `go test -race ./retry ./quicktunnelauth`
This reverts merge request !1952
Reverting this change. This was an unbuffered channel before, which does not mean "unbounded". The channel was effectively blocking until a receiver read the value.
For the refresh JWKS operation, a worker fiber was reading from a notification channel that had no buffering.
This has two issues. The first issue has to do with memory growth, but the second is more subtle. When we don't supply any bounds, the code needs to be capable to handle concurrent requests and the worker must be able to dedup notifications. While this is what happens now, a future iteration might introduce a bugthat removes the dedup logic.
## Summary
Validates callback-bound Quick Tunnel broker assertions using the verification keys cached in !1937. The validator accepts only ES256 JWTs from the fixed broker issuer and audience, verifies signatures before claims, enforces the two-minute handoff lifetime and Access identity expiry, binds the callback hostname and browser state, validates and normalizes the verified email, and returns a trusted identity for recipient authorization.
The temporary hostname validator mirrors TUN-10800 and is documented for consolidation after that stack merges.
## Stack
- Depends on: !1937
- Part 2/2: this MR
## Validation
- `make test lint`
- focused `go test -race ./connection` coverage for JWT headers, signatures, claims, and identity binding
## Summary
Adds the process-local broker JWKS cache used by protected Quick Tunnel assertion validation. Fetches keys only from the fixed broker endpoint with a five-second timeout, refuses redirects, bounds and validates responses, accepts only public P-256 ES256 signing keys, reuses cached keys, and refreshes once for an unknown key ID. Concurrent cache misses and refreshes are deduplicated.
## Stack
- Part 1/2: this MR
- Followed by: `afrigerio/TUN-10801-assertion-validation`
## Validation
- `make test lint`
- focused `go test -race ./connection` coverage for JWKS caching and refresh
Wires protected Quick Tunnel authentication into the request path so unauthenticated requests are intercepted before ingress selection. Broker callbacks are handled locally and fail closed
## Summary
- Updates `google.golang.org/grpc` from `v1.83.0` to `v1.83.1`.
- Fixes [GO-2026-6348](https://pkg.go.dev/vuln/GO-2026-6348), reported by the `vulncheck-boring-make` validation job.
This adds callback handling for the browser-bound Quick Tunnel authentication state, consuming the POST to /.cloudflared/qt-auth/callback that the broker sends after login. The handler accepts a bounded application/x-www-form-urlencoded body containing exactly one state and one assertion, verifies the HMAC-signed state cookie, and binds it to the submitted state.
It fails closed and clears the state cookie once consumption succeeds. On success, the handler returns the broker assertion and the validated return path for later use.
This adds QuickTunnelAuthStateManager, which issues browser-bound authentication states for protected Quick Tunnels. Each login generates a random state and a signed, host-only cookie that binds the browser to that state. The cookie is Secure, scoped to the callback path, valid for 10 minutes, and uses SameSite=None because the broker's callback is a cross-site POST from login.trycloudflare.com back to .trycloudflare.com.
The design is stateless: the cookie is signed with a process-local HMAC key rather than tracked server-side, so there's nothing to store or clean up, and state values are verified rather than looked up. Return-path validation rejects unsafe schemes, hosts, and paths to prevent open redirects.
## Summary
macOS runners are facing issues with bandwidth, therefore, in order to unblock the work of the team we will temporarily disable those builds.
Instead of removing the --transport-loglevel flag, we will mark it
as deprecated. It was no longer used in any logging capacity but
removing the flag will cause existing cloudflared clients who set
the flag to fail to start.
The dedicated transport logger was never used for
log emission, so remove its construction and plumbing through tunnel
configuration, the supervisor, and the connection observer.
Remove the unused --transport-loglevel flag and its legacy aliases and
environment variables. The dedicated transport logger was never used for
log emission, so remove its construction and plumbing through tunnel
configuration, the supervisor, and the connection observer.
QUIC previously replaced control-stream registration failures with generic
service errors, preventing the supervisor from identifying edge responses such
as EDUPCONN and retryable registration failures. This could repeatedly select
the same edge address or terminate initial registration unexpectedly.
Preserve underlying causes through QUIC service wrappers, including failures
opening the control stream, while returning context cancellation directly.
Represent duplicate registration with a sentinel and use errors.Is and
errors.AsType to classify wrapped errors consistently.
Keep registration permanence available to the supervisor, rotate edge
addresses after EDUPCONN, and classify QUIC dial errors before nested idle
timeouts so connectivity retries can still trigger protocol fallback. Remove
duplicate connection-error logging and suppress expected shutdown noise.
Add coverage for duplicate registration mapping, QUIC error propagation,
cancellation, startup retry policy, edge-address rotation, and wrapped QUIC
dial timeouts.
This MR contains the following updates:
| Package | Type | Update | Change |
|---------|------|--------|--------|
| gcr.io/distroless/base-debian13 | final | digest | `97b9d04` → `d199d20` |
---
### Configuration
📅 **Schedule**: (UTC)
- Branch creation
- At 12:00 AM through 04:59 AM and 10:00 PM through 11:59 PM, Monday through Friday (`* 0-4,22-23 * * 1-5`)
- Only on Sunday and Saturday (`* * * * 0,6`)
- Automerge
- At any time (no schedule defined)
🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.
♻️ **Rebasing**: Whenever MR is behind base branch, or you tick the rebase/retry checkbox.
🔕 **Ignore**: Close this MR and you won't be reminded about this update again.
---
- [ ] If you want to rebase/retry this MR, check this box
---
This MR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC40Ni4yIiwidXBkYXRlZEluVmVyIjoiNDQuNDYuMiIsInRhcmdldEJyYW5jaCI6Im1hc3RlciIsImxhYmVscyI6W119-->
This MR contains the following updates:
| Package | Type | Update | Change |
|---------|------|--------|--------|
| gcr.io/distroless/base-debian13 | final | digest | `a9fb022` → `1411d9a` |
---
### Configuration
📅 **Schedule**: (UTC)
- Branch creation
- At 12:00 AM through 04:59 AM and 10:00 PM through 11:59 PM, Monday through Friday (`* 0-4,22-23 * * 1-5`)
- Only on Sunday and Saturday (`* * * * 0,6`)
- Automerge
- At any time (no schedule defined)
🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.
♻️ **Rebasing**: Whenever MR is behind base branch, or you tick the rebase/retry checkbox.
🔕 **Ignore**: Close this MR and you won't be reminded about this update again.
---
- [ ] If you want to rebase/retry this MR, check this box
---
This MR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC40Ni4yIiwidXBkYXRlZEluVmVyIjoiNDQuNDYuMiIsInRhcmdldEJyYW5jaCI6Im1hc3RlciIsImxhYmVscyI6W119-->
Update CI and standard container builders to Go 1.26.8.
Add coverage for Go 1.26's stricter net/url host parsing, rejecting
unbracketed IPv6 addresses and repeated port separators in origin URLs.
Retain existing Access bare-IPv6 normalization behavior.
Closes TUN-10834
## Summary
Lands the request and data plumbing needed for protected Quick Tunnel provisioning.
This is intentionally staged dead code: `--allowed-mail` is not registered yet, so protected provisioning is not user-reachable in this MR. The existing public Quick Tunnel path remains unchanged. CLI registration and release notes will land when the feature becomes user-reachable.
## Changes
- Add `auth_mode` constants for protected quick tunnels
- Add `buildQuickTunnelRequestBody` helper to construct the OTP request body
- Read `--allowed-mail` values and compute `isProtected` once the flag is registered
- Send `{"auth_mode":"otp"}` when protected mode is requested
- Set `TunnelProperties.IsProtected` for downstream auth handlers
- Preserve structured Worker errors for non-2xx and unsuccessful responses
- Add unit tests for public and protected request bodies
- Add a TODO for CLI-level provisioning-error coverage under TUN-10791
## Validation
- `make test lint`
- Built the `cloudflared` binary locally
- Confirmed `--allowed-mail` is currently rejected as an undefined flag
- Confirmed a public invocation sends a bodyless provisioning request to a mock service
- Confirmed the mock Worker's `503` / `[1234] expected test failure` response is shown by the CLI
## Acceptance criteria
- [x] Protected request construction sends only the supported authentication mode
- [x] Public provisioning request remains bodyless
- [x] Unit tests cover public and protected request bodies
- [x] Current staged CLI behavior manually verified
- [x] Register `--allowed-mail` and make protected mode user-reachable (follow-up)
- [ ] Full CLI/integration coverage under TUN-10791
## Related
- Epic: RM-25152
- Previous/foundation work: TUN-10798
- End-to-end coverage: TUN-10791
Remove the tracked vendor directory and build cloudflared from the locked
Go module graph instead.
- Use read-only module mode for build, test, vet, formatting, and linting.
- Configure CI and Docker builders to use Athens with public-proxy and VCS
fallback.
- Download modules in a separate Docker layer for reusable image builds.
- Remove obsolete module checksums and add the missing shell interpreter to
the FIPS verification script.
Closes TUN-10829
cloudflared access tcp can fetch an Access token, receive a later Access 302 during WebSocket setup, remove the app token, and immediately fetch again in the same process. The token lock files were intentionally left on disk for stale-PID recovery, but that meant the second fetch saw the first fetch’s lock, recognized its own PID as alive, and waited until lock timeout.
This change restores normal lock release without losing crash recovery. Lock acquisition now returns an ownership handle with a per-acquisition ID, and getToken() releases both app and org token locks on return. Release only removes the lock if PID, process start time, and acquisition agree.
I tested this by manually building and running through a few scenarios on my local machine. So far I haven't seen any lockfiles get left behind and the normal CLI flows are working as expected.
Closes AUTH-8883
## Problem
Windows CI jobs frequently fail during the `get_sources` phase with hundreds of `Permission denied` warnings while GitLab tries to clean read-only files from `.cache/go/pkg/mod/` left by previous builds. The failure occurs before the build script runs.
Example failed job: https://gitlab.cfdata.org/cloudflare/tun/cloudflared/-/jobs/40007572
## Fix
Set `GIT_STRATEGY: clone` on the shared `.windows-build-defaults` anchor so Windows jobs always start from a fresh clone, bypassing the broken cleanup.
## Related
- MR !1927 (affected by this issue)
## Summary
- extract the existing recipient-rule parser and validation helpers from the OTP PoC
- normalize and deduplicate repeated or comma-separated exact email and `*@domain` rules
- reject malformed addresses, wildcard domains, and empty rule sets
- keep the implementation as dead code with no CLI flag, provisioning, auth handler, or transport wiring
## Testing
- `make test`
- `make lint`
## Jira
- [TUN-10798](https://jira.cfdata.org/browse/TUN-10798)
Stop fetching protocol rollout percentages from DNS TXT records.
Remove the obsolete remote selector, percentage-fetching API, and
related tests, and simplify protocol selector construction.