Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
6bfb8c1
The Axum, Cloudflare and Spin adapters run the request filters and th…
jwrosewell Oct 17, 2026
f77093a
A design for running the request filters and the device module on eve…
jwrosewell Oct 17, 2026
f778984
The changelog and the guides say the request filters run on every ada…
jwrosewell Oct 17, 2026
ee51ea3
Merge split/51-config-migrate into split/52-request-filters
jwrosewell Oct 18, 2026
f0edcd7
Merge split/51-config-migrate into split/52-request-filters
jwrosewell Oct 18, 2026
7aea7a7
The request cycle tests prove the privacy guards on Axum, Cloudflare …
jwrosewell Oct 18, 2026
4549047
The robots.txt comments say which of a crawler's cookies are read
jwrosewell Oct 18, 2026
a584b07
The guides and the changelog say what filters and device modules do o…
jwrosewell Oct 18, 2026
6b4eb0b
The client hints design says every adapter runs its filter
jwrosewell Oct 18, 2026
17136d0
The request filters design answers its first review round
jwrosewell Oct 18, 2026
7ae3235
The request cycle tests send every named route through the filters on…
jwrosewell Oct 18, 2026
8c9b914
The Edge Cookie context comments say which Cookie header it cannot read
jwrosewell Oct 18, 2026
233299a
The integration guide says what a device module is shown
jwrosewell Oct 18, 2026
52cfd9f
The geo design says every adapter writes a filter's response headers
jwrosewell Oct 18, 2026
e7417c8
The client hints design gives the wait on Axum and the targets its te…
jwrosewell Oct 18, 2026
ac04929
The client hints design logs its third review round
jwrosewell Oct 18, 2026
076f4ad
The request filters design answers its second review round
jwrosewell Oct 18, 2026
eab0621
Merge split/51-config-migrate into split/52-request-filters
jwrosewell Oct 18, 2026
585a93c
Merge split/51-config-migrate into split/52-request-filters
jwrosewell Oct 18, 2026
5b09455
Merge split/51-config-migrate into split/52-request-filters
jwrosewell Oct 18, 2026
9015b41
Merge split/51-config-migrate into split/52-request-filters
jwrosewell Oct 18, 2026
0e46443
Merge split/51-config-migrate into split/52-request-filters
jwrosewell Oct 19, 2026
13e92c0
Merge split/51-config-migrate into split/52-request-filters
jwrosewell Oct 19, 2026
a304a2a
Merge split/51-config-migrate into split/52-request-filters
jwrosewell Oct 19, 2026
54eeaa0
Merge split/51-config-migrate into split/52-request-filters
jwrosewell Oct 19, 2026
b5836c3
Merge split/51-config-migrate into split/52-request-filters
jwrosewell Oct 19, 2026
18e8765
Merge split/51-config-migrate into split/52-request-filters
jwrosewell Oct 19, 2026
4b88528
Merge split/51-config-migrate into split/52-request-filters
jwrosewell Oct 28, 2026
8a370ae
Merge split/51-config-migrate into split/52-request-filters
jwrosewell Oct 28, 2026
8dd9f54
Merge split/51-config-migrate into split/52-request-filters
jwrosewell Oct 28, 2026
34916b9
Merge split/51-config-migrate into split/52-request-filters
jwrosewell Oct 28, 2026
93134dd
Row 52 takes the final request filters on every adapter design
jwrosewell Oct 28, 2026
b69ae6f
The request cycle tests' settings carry the sample policy
jwrosewell Oct 28, 2026
6b43f07
Merge split/51-config-migrate into split/52-request-filters
jwrosewell Oct 28, 2026
4d1b109
Merge split/51-config-migrate into split/52-request-filters
jwrosewell Oct 28, 2026
35ee733
Merge split/51-config-migrate into split/52-request-filters
jwrosewell Oct 28, 2026
aaf0ff3
Merge split/51-config-migrate into split/52-request-filters
jwrosewell Oct 28, 2026
1c7518b
Merge split/51-config-migrate into split/52-request-filters
jwrosewell Oct 28, 2026
7f1d8e2
Merge split/51-config-migrate into split/52-request-filters
jwrosewell Oct 28, 2026
a475426
Row 52's design copy carries the date it is proposed, 2026-10-27
jwrosewell Oct 28, 2026
ddee9e2
Merge split/51-config-migrate into split/52-request-filters
jwrosewell Oct 28, 2026
86f77a1
Merge split/51-config-migrate into split/52-request-filters
jwrosewell Oct 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- The Axum, Cloudflare and Spin adapters run the integration request filters and classify each request with the device module `[device]` selects, as the Fastly adapter does. Before, they ran no filter and asked a selected device module only for a bid request's device attributes. Every named route but the inspection endpoints, and the publisher fallback, reads the request's Edge Cookie context with the device classification and then runs the filters before the route, so a filter that answers, such as DataDome's challenge, stands in for the route, and the response headers a filter names are written after the finalize headers, with the Fastly adapter's privacy guards applied after them. The 51Degrees client hints middleware and DataDome's protection therefore work on every adapter, through HTTP clients that apply no module's own timeout on Axum, Cloudflare and Spin, and DataDome's protection checks `/auction` and `GET` on both page-bids paths as well as the publisher's pages. On those adapters `[device] module = "platform"` classifies from the `User-Agent`, a response core marks private loses any `ETag`, `Last-Modified`, `Expires` or `Age` that `[response_headers]` writes, robots.txt is answered before the Edge Cookie steps and the filters with an empty Edge Cookie context, and on Spin the discovery, signature verification and first-party routes and the page-bids preflight now run the modules' preparers.
- **Breaking:** the platform's own device module is selected with `[device] module = "platform"`, as `[geo] module = "platform"` selects the platform's geo lookup, where it was `module = "fastly"`. A document that still writes `fastly` is refused at startup as naming a device module no running module supplies. **For crates outside core**, `build_device_module` takes a `build_platform` factory.
- **Breaking for crates outside core:** an adapter declares the request headers its platform sets. `platform::declare_platform_tls_headers` names a header the platform sets on a request that arrived over TLS, which core reads for the scheme and never forwards to an origin. `platform::declare_platform_spoofable_headers` names the platform's other headers a client could also send, which `sanitize_forwarded_headers` strips at entry with the standard forwarding headers, in an adapter that calls it. `platform::declare_platform_internal_headers` names headers that are never passed to a third party. The Fastly adapter declares `fastly-ssl`, `fastly-client-ip` and `x-debug-fastly-pop` before it serves. `SPOOFABLE_FORWARDED_HEADERS` holds the three standard forwarding headers, `HEADER_X_DEBUG_FASTLY_POP` leaves core, and `constants::is_internal_header` answers for the fixed list and the declared headers together. A `[trusted_client_ip]` `ip_header` may start with `x-` or end with `-client-ip`, so `fastly-client-ip` stays valid. On the Axum, Cloudflare and Spin adapters, which declare none of these headers, a client's `fastly-ssl` is no longer removed from the request to the publisher's origin, and on Spin a client's `fastly-ssl` and `fastly-client-ip` are no longer stripped at entry. On those adapters the `x-debug-fastly-pop` header from a client is also no longer kept from third parties, because `INTERNAL_HEADERS` no longer holds it, and is no longer refused as a `[trusted_client_ip]` header name. On the Axum and Cloudflare adapters a client's `fastly-ssl` also no longer makes the request HTTPS. On Fastly, `ts config validate`, which runs with no adapter's declarations, no longer refuses `x-debug-fastly-pop` as a `[trusted_client_ip]` header name, and the service refuses it when it loads the document.
- **Breaking for crates outside core:** an adapter tells core whether its platform runs the service in a staging environment with `platform::declare_platform_staging`, and a module asks `platform::platform_is_staging`. `ENV_FASTLY_IS_STAGING` leaves core, the Fastly adapter reads `FASTLY_IS_STAGING` itself, and the DataDome module's test bypass follows the declaration. On the Axum, Cloudflare and Spin adapters, which declare no staging environment, the bypass no longer follows a `FASTLY_IS_STAGING` variable.
Expand Down Expand Up @@ -131,7 +132,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- `ts config migrate --adapter <adapter> <file>` rewrites a settings document written for an earlier release, such as one written before the breaking changes above, into the shape this release reads, and prints the result. The host's location lookup the earlier release ran becomes `[geo] module = "platform"` on Fastly and Cloudflare and no geo module on Axum and Spin, which have no lookup. On stderr it lists each change it made, each setting it left for the operator because it needs a decision or a value the document does not hold, such as `[geo] assume_single_jurisdiction` for an Edge Cookie module with no geo module, and each change in behavior no setting expresses, then checks the result with the settings validation `ts config validate` runs. It keeps the comments and the order of the tables, comments out a module that was not enabled under its new name, never selects `headers.geo`, which writes the reader's location, never prints a value from the document, rewrites the file in place with `--write`, and with `--check` changes nothing and exits 1 when the document needs migrating. Migrating a migrated document changes nothing.
- A 51Degrees client hints middleware, `51degrees.client_hints`, from `crates/middleware/51degrees`, run by `[shared.51degrees]` and placed by a `[[serve]]` entry. On the Fastly adapter, the only one that runs request filters, it asks the 51Degrees service which client hints the reader's browser can send for each page navigation an entry covers, sending the `User-Agent` and `Sec-CH-UA` hints alone, and leaves the hint names in the request's state for the other 51Degrees modules, which keeps a page it names hints for out of the stored templates and sends it `private, no-store`. On each page an entry covers it writes `Accept-CH` and `Critical-CH` on the response unless the browser already sends every one of those hints, `Permissions-Policy` and the `Delegate-CH` tag that delegate those hints to the service's origin, each keeping every line the response already carries. `critical` in `[middleware.51degrees.client_hints]` turns the retry of the first navigation off. The service's answers load through the cache `51degrees-answers`, selected and sized in `[cache.51degrees-answers]`, and are held only where the build links a loading cache module, so a stock build asks the service for each page navigation an entry covers. What the 51Degrees modules share, being the table, the one call to the service, the evidence it sends and the cache its answer is held in, is the new `crates/shared/51degrees`.
- A 51Degrees client hints middleware, `51degrees.client_hints`, from `crates/middleware/51degrees`, run by `[shared.51degrees]` and placed by a `[[serve]]` entry. It asks the 51Degrees service which client hints the reader's browser can send for each page navigation an entry covers, sending the `User-Agent` and `Sec-CH-UA` hints alone, and leaves the hint names in the request's state for the other 51Degrees modules, which keeps a page it names hints for out of the stored templates and sends it `private, no-store`. On each page an entry covers it writes `Accept-CH` and `Critical-CH` on the response unless the browser already sends every one of those hints, `Permissions-Policy` and the `Delegate-CH` tag that delegate those hints to the service's origin, each keeping every line the response already carries. `critical` in `[middleware.51degrees.client_hints]` turns the retry of the first navigation off. The service's answers load through the cache `51degrees-answers`, selected and sized in `[cache.51degrees-answers]`, and are held only where the build links a loading cache module, so a stock build asks the service for each page navigation an entry covers. What the 51Degrees modules share, being the table, the one call to the service, the evidence it sends and the cache its answer is held in, is the new `crates/shared/51degrees`.
- `[middleware.<name>]`, a middleware's own settings table, written with the middleware's full name and read through `Settings::middleware_config`. A table holds values and arrays, a table within one is the longer name's table and never a setting, and a table at a name no running module supplies is refused at startup with the names that could be written.
- A serve middleware may set headers on a response the publisher path sends for a request path its entry covers, through `Middleware::response_headers`, which is given the response's status and headers and runs whatever the response's media type and whether or not it has a body, so a redirect with no body reaches a middleware that rewrites its `Location`. A header it sets replaces one of the same name. The method is not called for a refusal made before the origin is asked, such as the `405` for a method a signed origin does not take, for a page served from the template cache or the refusal of such a page, or for an answer made from an error.
- A publisher's site may be served from a folder of object storage through the publisher proxy, so its pages pass through the page pipeline, which an asset route skips. The folder is the path of `origin_url`, and two optional `[publisher]` settings do the rest, each off unless set. `origin_auth` takes the table an asset route's `auth` takes and signs every origin request with it, through the same signer. `directory_index` names a file added to a request path ending in `/`, for the origin request only. A signed origin is only read, so a method other than `GET` or `HEAD` is answered `405`, and a signed request carries none of the reader's cookies, credentials, referrer, conditional validators or range. A malformed `origin_auth` or `directory_index` is refused when the settings load, with the setting named.
Expand Down
17 changes: 8 additions & 9 deletions crates/middleware/51degrees/README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
# 51Degrees client hints middleware

`trusted-server-middleware-51degrees` supplies the `51degrees.client_hints`
serve middleware. On the Fastly adapter, the only adapter that runs request
filters, it asks the 51Degrees service which client hints the reader's
browser can send for each page navigation a `[[serve]]` entry naming it
covers, sending the `User-Agent` and `Sec-CH-UA` hints alone, and leaves the
hint names in the request's state for the other 51Degrees modules. On each
page a `[[serve]]` entry names it for, it writes `Accept-CH` and `Critical-CH`
on the response unless the browser already sends every one of those hints,
and `Permissions-Policy` and the `Delegate-CH` tag that delegate those hints
to the service.
serve middleware. It asks the 51Degrees service which client hints the
reader's browser can send for each page navigation a `[[serve]]` entry
naming it covers, sending the `User-Agent` and `Sec-CH-UA` hints alone, and
leaves the hint names in the request's state for the other 51Degrees
modules. On each page a `[[serve]]` entry names it for, it writes
`Accept-CH` and `Critical-CH` on the response unless the browser already
sends every one of those hints, and `Permissions-Policy` and the
`Delegate-CH` tag that delegate those hints to the service.

`[shared.51degrees]` runs it, and `critical` in
`[middleware.51degrees.client_hints]` turns the retry of the first navigation
Expand Down
Loading
Loading