Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
9a8c8af
A deployment using 51Degrees gets client hints from one middleware
jwrosewell Oct 14, 2026
6bb883e
The lock lists the two 51Degrees crates
jwrosewell Oct 14, 2026
c7d3e90
Merge 43-no-modules-crate into 44-client-hints
jwrosewell Oct 14, 2026
34afbc9
The probe's misspelt cache test validates with the builders startup l…
jwrosewell Oct 14, 2026
cea5605
Merge 43-no-modules-crate into 44-client-hints
jwrosewell Oct 15, 2026
1f6b83d
Merge 43-no-modules-crate into 44-client-hints
jwrosewell Oct 15, 2026
126347b
The client hints design states what the filter runs on, what it sends…
jwrosewell Oct 15, 2026
93ec08d
The client hints design takes its round 2 corrections
jwrosewell Oct 15, 2026
7220e42
Merge 43-no-modules-crate into 44-client-hints
jwrosewell Oct 15, 2026
d930632
Merge 43-no-modules-crate into 44-client-hints
jwrosewell Oct 15, 2026
7730f58
Client hints are not asked for again when the browser already sends them
jwrosewell Oct 15, 2026
281e07f
Merge 43-no-modules-crate into 44-client-hints
jwrosewell Oct 15, 2026
b9f6802
The client hints filter asks the service only where an entry places it
jwrosewell Oct 15, 2026
57e6935
The client hints call sends the User-Agent and Sec-CH-UA hints alone
jwrosewell Oct 15, 2026
7d48db6
Every line of a client hints header the response sends is kept
jwrosewell Oct 15, 2026
029bbdf
The client hints design and docs state the call's scope, evidence and…
jwrosewell Oct 15, 2026
c48009f
The client hints header comment wraps as one paragraph
jwrosewell Oct 15, 2026
3febacb
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 16, 2026
e35fc08
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 16, 2026
4cf39a6
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 16, 2026
3e7dc94
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 16, 2026
b578f45
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 16, 2026
2f8923d
The unknown setting test names a key for each secret setting a module…
jwrosewell Oct 16, 2026
eadde7e
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 16, 2026
1bd62ec
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 16, 2026
f6e6351
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 16, 2026
1bf24bb
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 16, 2026
3ca3736
The design header names the pull request that carries it
jwrosewell Oct 16, 2026
1bc34e6
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 16, 2026
fd26355
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 16, 2026
16de8b1
The 51Degrees answer cache builds its spec without detachable
jwrosewell Oct 16, 2026
c216a1f
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 16, 2026
391718a
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 16, 2026
0a432bf
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 16, 2026
d623c8d
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 16, 2026
880264d
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 18, 2026
b1a6ba3
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 18, 2026
748429b
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 18, 2026
8ba72b2
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 18, 2026
aecc81d
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 18, 2026
6867b6c
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 18, 2026
c600fbf
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 19, 2026
b7041f2
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 19, 2026
e29e3d6
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 19, 2026
8b28937
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 19, 2026
6fcd32a
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 19, 2026
98b6a48
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 28, 2026
eb38f7d
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 28, 2026
017ab34
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 28, 2026
9fe88ff
The 51Degrees crates say answers are held only where a cache is linked
jwrosewell Oct 28, 2026
19734b1
The 51Degrees guide says a stock build asks the service for every page
jwrosewell Oct 28, 2026
f1284a3
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 28, 2026
4eacb48
The 51Degrees test services carry the sample policy
jwrosewell Oct 28, 2026
523a1a2
Row 44 takes the final 51Degrees client hints design
jwrosewell Oct 28, 2026
db688c7
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 28, 2026
c5843f5
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 28, 2026
13d939b
Row 44's design copy says it describes the stack from row 52 up
jwrosewell Oct 28, 2026
304197b
Merge split/43-no-modules-crate into split/44-client-hints
jwrosewell Oct 28, 2026
9f0a74b
Row 44's design copy carries the date it is proposed, 2026-10-27
jwrosewell Oct 28, 2026
63b6bb3
Merge split/43-no-modules-crate into split/44-client-hints
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
4 changes: 2 additions & 2 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ test-fastly-reuse = "test -p trusted-server-adapter-fastly --target wasm32-wasip
# run with the Axum adapter in CI.
build-axum = "build -p trusted-server-adapter-axum"
check-axum = "check -p trusted-server-adapter-axum"
clippy-axum = "clippy -p trusted-server-adapter-axum -p trusted-server-permission-signal-gpc -p trusted-server-permission-signal-gpp -p trusted-server-permission-signal-mtm -p trusted-server-permission-signal-tcf -p trusted-server-permission-signal-us-privacy -p trusted-server-cmp-osano -p trusted-server-identity-lockr -p trusted-server-audience-permutive -p trusted-server-cmp-sourcepoint -p trusted-server-cmp-didomi -p trusted-server-cmp-inmobi -p trusted-server-ads-txt-iabtl -p trusted-server-headers-geo -p trusted-server-loading-cache-51degrees -p trusted-server-asset-cache-51degrees -p trusted-server-tag-google-tag -p trusted-server-tag-google-tag-manager -p trusted-server-testing-testlight -p trusted-server-framework-nextjs -p trusted-server-ad-tag-google -p trusted-server-bot-protection-datadome -p trusted-server-ad-server-iabtl -p trusted-server-auction-aps -p trusted-server-auction-prebid -p trusted-server-auction-prebid-server -p trusted-server-auction-protocol-openrtb --all-targets --all-features -- -D warnings"
test-axum = "test -p trusted-server-adapter-axum -p trusted-server-permission-signal-gpc -p trusted-server-permission-signal-gpp -p trusted-server-permission-signal-mtm -p trusted-server-permission-signal-tcf -p trusted-server-permission-signal-us-privacy -p trusted-server-cmp-osano -p trusted-server-identity-lockr -p trusted-server-audience-permutive -p trusted-server-cmp-sourcepoint -p trusted-server-cmp-didomi -p trusted-server-cmp-inmobi -p trusted-server-ads-txt-iabtl -p trusted-server-headers-geo -p trusted-server-loading-cache-51degrees -p trusted-server-asset-cache-51degrees -p trusted-server-tag-google-tag -p trusted-server-tag-google-tag-manager -p trusted-server-testing-testlight -p trusted-server-framework-nextjs -p trusted-server-ad-tag-google -p trusted-server-bot-protection-datadome -p trusted-server-ad-server-iabtl -p trusted-server-auction-aps -p trusted-server-auction-prebid -p trusted-server-auction-prebid-server -p trusted-server-auction-protocol-openrtb"
clippy-axum = "clippy -p trusted-server-adapter-axum -p trusted-server-permission-signal-gpc -p trusted-server-permission-signal-gpp -p trusted-server-permission-signal-mtm -p trusted-server-permission-signal-tcf -p trusted-server-permission-signal-us-privacy -p trusted-server-cmp-osano -p trusted-server-identity-lockr -p trusted-server-audience-permutive -p trusted-server-cmp-sourcepoint -p trusted-server-cmp-didomi -p trusted-server-cmp-inmobi -p trusted-server-ads-txt-iabtl -p trusted-server-headers-geo -p trusted-server-loading-cache-51degrees -p trusted-server-asset-cache-51degrees -p trusted-server-shared-51degrees -p trusted-server-middleware-51degrees -p trusted-server-tag-google-tag -p trusted-server-tag-google-tag-manager -p trusted-server-testing-testlight -p trusted-server-framework-nextjs -p trusted-server-ad-tag-google -p trusted-server-bot-protection-datadome -p trusted-server-ad-server-iabtl -p trusted-server-auction-aps -p trusted-server-auction-prebid -p trusted-server-auction-prebid-server -p trusted-server-auction-protocol-openrtb --all-targets --all-features -- -D warnings"
test-axum = "test -p trusted-server-adapter-axum -p trusted-server-permission-signal-gpc -p trusted-server-permission-signal-gpp -p trusted-server-permission-signal-mtm -p trusted-server-permission-signal-tcf -p trusted-server-permission-signal-us-privacy -p trusted-server-cmp-osano -p trusted-server-identity-lockr -p trusted-server-audience-permutive -p trusted-server-cmp-sourcepoint -p trusted-server-cmp-didomi -p trusted-server-cmp-inmobi -p trusted-server-ads-txt-iabtl -p trusted-server-headers-geo -p trusted-server-loading-cache-51degrees -p trusted-server-asset-cache-51degrees -p trusted-server-shared-51degrees -p trusted-server-middleware-51degrees -p trusted-server-tag-google-tag -p trusted-server-tag-google-tag-manager -p trusted-server-testing-testlight -p trusted-server-framework-nextjs -p trusted-server-ad-tag-google -p trusted-server-bot-protection-datadome -p trusted-server-ad-server-iabtl -p trusted-server-auction-aps -p trusted-server-auction-prebid -p trusted-server-auction-prebid-server -p trusted-server-auction-protocol-openrtb"

# --- Cloudflare adapter (native host + wasm32-unknown-unknown) ---
# Build/check target the WASM runtime (requires the `cloudflare` feature);
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- 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`.
- `[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
43 changes: 43 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ members = [
"crates/ads-txt/iabtl",
"crates/headers/geo",
"crates/loading-cache/51degrees",
"crates/shared/51degrees",
"crates/middleware/51degrees",
"crates/asset-cache/51degrees",
"crates/tag/google-tag",
"crates/tag/google-tag-manager",
Expand Down Expand Up @@ -172,6 +174,8 @@ trusted-server-cmp-inmobi = { path = "crates/cmp/inmobi" }
trusted-server-ads-txt-iabtl = { path = "crates/ads-txt/iabtl" }
trusted-server-headers-geo = { path = "crates/headers/geo" }
trusted-server-loading-cache-51degrees = { path = "crates/loading-cache/51degrees" }
trusted-server-shared-51degrees = { path = "crates/shared/51degrees" }
trusted-server-middleware-51degrees = { path = "crates/middleware/51degrees" }
trusted-server-asset-cache-51degrees = { path = "crates/asset-cache/51degrees" }
trusted-server-tag-google-tag = { path = "crates/tag/google-tag" }
trusted-server-tag-google-tag-manager = { path = "crates/tag/google-tag-manager" }
Expand Down
38 changes: 38 additions & 0 deletions crates/middleware/51degrees/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[package]
name = "trusted-server-middleware-51degrees"
description = "The 51Degrees client hints middleware: asks the service which hints a reader's browser can send, and writes Accept-CH, Critical-CH, Permissions-Policy and the Delegate-CH tag on each page."
authors = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
publish = { workspace = true }
version = { workspace = true }

[lib]
doctest = false

[lints]
workspace = true

[dependencies]
trusted-server-core = { workspace = true }
trusted-server-shared-51degrees = { workspace = true }
async-trait = { workspace = true }
edgezero-core = { workspace = true }
error-stack = { workspace = true }
http = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
url = { workspace = true }
validator = { workspace = true }

[dev-dependencies]
tokio = { workspace = true, features = ["rt", "macros"] }
trusted-server-core = { workspace = true, features = ["test-utils"] }
trusted-server-loading-cache-51degrees = { workspace = true }
trusted-server-shared-51degrees = { workspace = true, features = ["test-utils"] }

# The visible owner of this vendor crate, the way Prebid.js requires a named
# maintainer of every adapter.
[package.metadata.maintainers]
owner = "51Degrees"
status = "vendor owned"
20 changes: 20 additions & 0 deletions crates/middleware/51degrees/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# 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.

`[shared.51degrees]` runs it, and `critical` in
`[middleware.51degrees.client_hints]` turns the retry of the first navigation
off. What it shares with the other 51Degrees modules is in
`crates/shared/51degrees`.

The [51Degrees guide](../../../docs/guide/integrations/51degrees.md) says
how to place and configure it.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[shared.51degrees]
endpoint = "https://cloud.example.com/api/v4/json"
license_key = "a-license-key-for-the-recorded-page"

[[serve]]
media_type = "text/html"
middleware = ["51degrees.client_hints"]
Loading
Loading