Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
b740265
Add a pluggable Edge Cookie provider seam with the built-in HMAC prov…
jwrosewell Aug 18, 2026
d2ad2a9
Accept the provider-code envelope on the partner-facing identifier paths
jwrosewell Aug 27, 2026
b2623cb
Rename the legacy passphrase migration so CodeQL stops tainting Settings
jwrosewell Aug 27, 2026
918f96c
Stop serving without identity when a selected provider is unavailable
jwrosewell Aug 28, 2026
61b7cf1
Hold the deprecated EC passphrase to the same rules as the new block
jwrosewell Aug 28, 2026
5b01f98
Reject unknown keys in the built-in HMAC provider block
jwrosewell Aug 28, 2026
7424932
Stop rather than run stateless when the hmac block is missing
jwrosewell Aug 28, 2026
d6dc848
Restore the missing line continuation in the mint rejection message
jwrosewell Aug 28, 2026
45ec829
Give EdgeCookieProvider its own doc comment back
jwrosewell Aug 28, 2026
c4c584c
Delete the unused ec::get_ec_id helper
jwrosewell Aug 28, 2026
5203c2d
Correct the provider module docs about when evidence arrives
jwrosewell Aug 28, 2026
e44ff38
Replace the scattered EC provider key strings with a typed selector
jwrosewell Aug 28, 2026
afdb7be
Reserve core's own response surface against provider effects
jwrosewell Aug 29, 2026
e90b471
Dispatch partner-path identifier checks by provider code
jwrosewell Aug 29, 2026
3b74d74
Let each provider decide whether it needs the client IP
jwrosewell Aug 29, 2026
29a3a7f
State a real retirement condition for the legacy bare-identifier reader
jwrosewell Aug 29, 2026
cb62786
Key identity-graph reads and write-backs by the canonical form
jwrosewell Aug 29, 2026
fe23dcc
Egress only an Edge Cookie identifier the provider recognizes
jwrosewell Aug 29, 2026
e3ba579
Record the cluster-count gap the identifier envelope opens
jwrosewell Aug 29, 2026
292df1f
Consume the refused Report in the testlight egress tests
jwrosewell Aug 29, 2026
a3a5d57
Drop the request-evidence accessors that have no caller
jwrosewell Aug 29, 2026
b70ddc0
Collapse the EC provider selector to statelessness and a named provider
jwrosewell Aug 29, 2026
5da52c8
Accumulate provider response headers instead of replacing the origin's
jwrosewell Aug 29, 2026
67c3b0b
State what a provider switch really does to existing identities
jwrosewell Aug 29, 2026
1f62e1a
Name the design documents rather than their paths in doc comments
jwrosewell Aug 30, 2026
c7464bc
Restore the line continuations missed in the neighbouring files
jwrosewell Aug 30, 2026
ef81e88
Correct the two provider doc comments the earlier pass missed
jwrosewell Aug 30, 2026
acb416f
Stop a provider code from panicking when a vendor builds one at run time
jwrosewell Aug 30, 2026
8eb0a9b
Refuse two Edge Cookie providers claiming the same name
jwrosewell Aug 30, 2026
f2b1825
Build the internal header list from the Edge Cookie response headers
jwrosewell Aug 30, 2026
52e2692
Resolve the Edge Cookie provider once per request instead of twice
jwrosewell Aug 30, 2026
1c7df60
Load Spin settings from the config store instead of a baked template
jwrosewell Aug 30, 2026
3234092
Stop exposing an inbound Edge Cookie identifier nothing has vouched for
jwrosewell Aug 30, 2026
1df8bf7
Keep the whole request-evidence interface
jwrosewell Aug 30, 2026
8a29373
Documentation and house-style cleanup for the Edge Cookie provider PR
jwrosewell Aug 31, 2026
147a37b
Name the caching reason accurately in the reserved-header rejection
jwrosewell Aug 31, 2026
7dda9dd
Collapse Edge Cookie provider injection to a single seam
jwrosewell Aug 31, 2026
c16c99d
Remove the orphaned no-client-IP test fixture
jwrosewell Sep 1, 2026
99b1acd
Add device and geo provider selection with the host-signal Edge Cooki…
jwrosewell Aug 19, 2026
20fee6a
Omit an unset provider selector from the serialized config
jwrosewell Aug 30, 2026
74047c1
Read the Cloudflare visitor region so US state opt-outs are honored
jwrosewell Aug 30, 2026
cf8d098
Reunite the request-info builders with their own documentation
jwrosewell Aug 31, 2026
de98934
Documentation and house-style cleanup for the device and geo provider PR
jwrosewell Aug 31, 2026
d3b4b06
Drop the unused HeaderMap import from the device tests
jwrosewell Sep 1, 2026
dcdb493
Give each vendor crate a visible maintainers declaration
jwrosewell Sep 2, 2026
5467c53
Add the permission model with the Privacy Taxonomy vocabulary
jwrosewell Aug 19, 2026
3edfb66
Correct the permission-model docs to what the code does
jwrosewell Aug 27, 2026
a558939
Give the remaining test fixtures the now-required default country
jwrosewell Aug 29, 2026
8f8e5cc
Say which geo lookups can reach the requires-signal floor, and prove it
jwrosewell Aug 29, 2026
d002c9c
Scope the permission-gating claim to the provider it is true of
jwrosewell Aug 30, 2026
8798e88
State that evidence is not rationed and use is
jwrosewell Aug 30, 2026
1f64eb8
Name the IAB Tech Lab Privacy Taxonomy as the source of the vocabulary
jwrosewell Aug 30, 2026
deb9c9f
Build the test provider code through the macro that cannot fail
jwrosewell Aug 30, 2026
4ce55ce
Documentation and house-style cleanup for the permission model PR
jwrosewell Aug 31, 2026
6d150d8
Complete the mint and fingerprint house-style sweep after rebase
jwrosewell Aug 31, 2026
a36db64
Document how the resolved permissions reach filters and the page
jwrosewell Sep 1, 2026
9d3c753
Expose the request permission state on the tsjs core API
jwrosewell Sep 1, 2026
d1cd60f
Pass the resolved permission state to integration request filters
jwrosewell Sep 1, 2026
0af6509
Carry the resolved permission state to the page
jwrosewell Sep 1, 2026
d3ed330
Document the permission rules tree and retire the default country
jwrosewell Sep 1, 2026
0ab0dda
Match the guide's jurisdiction values to the policy parser
jwrosewell Sep 1, 2026
bea39b8
Document jurisdiction as a per-node inherited attribute
jwrosewell Sep 1, 2026
6fe991e
Note the unknown jurisdiction on a failed lookup and the DE codes
jwrosewell Sep 1, 2026
3c44407
Point the GDPR page at the rules tree for jurisdiction assignment
jwrosewell Sep 1, 2026
d118ff2
Add the permission model guide to the site navigation
jwrosewell Sep 1, 2026
727f785
Restructure the permission policy into a tree of places
jwrosewell Sep 1, 2026
31fbe46
Give the integration environment a real place through Viceroy geoloca…
jwrosewell Sep 1, 2026
7df640c
Give the template-cache harness a real place through Viceroy geolocation
jwrosewell Sep 1, 2026
60458a5
Anchor the harness's geo selector uncomment to the whole line
jwrosewell Sep 1, 2026
f9c9632
Repair the harness's geo selector uncomment
jwrosewell Sep 1, 2026
e43095e
Model a realistic demo policy in permissions.yaml
jwrosewell Sep 2, 2026
36a28a7
State that no policy ever ships with Trusted Server
jwrosewell Sep 2, 2026
444d908
Move the sample permissions into config/permissions
jwrosewell Sep 2, 2026
1f0d443
Add the permissions inspector, driven by the real code as WebAssembly
jwrosewell Sep 2, 2026
5fa76c9
Mark the inspector build script executable
jwrosewell Sep 2, 2026
ae055e1
Limit the inspector workflow token to reading contents
jwrosewell Sep 2, 2026
d87711f
Point the compiled-in rules doc at config/permissions
jwrosewell Sep 2, 2026
32b8d2e
Add the client-set Edge Cookie value path
jwrosewell Aug 19, 2026
657a0ac
Tidy the client-cycle spec prose and state the no-store rule precisely
jwrosewell Aug 27, 2026
3fa2ed0
Hold the resolve endpoint to the reserved response-surface contract
jwrosewell Aug 29, 2026
749abbc
Accumulate provider response headers on the resolve endpoint too
jwrosewell Aug 29, 2026
3fdfb9e
Build the resolve test provider code through the macro that cannot fail
jwrosewell Aug 31, 2026
b09c7c5
Documentation and house-style cleanup for the client-resolve PR
jwrosewell Aug 31, 2026
0e4bfb8
Align the header-accumulation test with the reserved cache-control su…
jwrosewell Aug 31, 2026
77ab44a
Reach the resolve endpoint's provider through the single seam
jwrosewell Aug 31, 2026
eab1359
Authorize the client resolve endpoint by exact origin, and restart th…
jwrosewell Sep 1, 2026
0050c2c
Compare resolve origins by the RFC 6454 same-origin test
jwrosewell Sep 1, 2026
d9c2439
Make the demo page script check its required permission before posting
jwrosewell Sep 1, 2026
8dfe29c
Add the provider documentation set and finish the decomposition
jwrosewell Aug 19, 2026
fd43545
Correct the documentation set against the code
jwrosewell Aug 27, 2026
181db1b
Remove the test fixture and import the evidence rewrite left behind
jwrosewell Aug 31, 2026
e8d899a
Put back the check on what a provider is handed with no client IP
jwrosewell Aug 31, 2026
ba99d8c
Run the core library tests natively in CI as well as on WebAssembly
jwrosewell Aug 31, 2026
4d7a1c6
Documentation and house-style cleanup for the response-hook and docs PR
jwrosewell Aug 31, 2026
2127ec6
Drop the retired default_country from the configuration guide
jwrosewell Sep 1, 2026
8608f73
Correct the configuration guide's permissions file location
jwrosewell Sep 2, 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
8 changes: 4 additions & 4 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ test_details = "test --target aarch64-apple-darwin"
# native crate needs no change here. Axum (native), Cloudflare
# (wasm32-unknown-unknown), Spin, the CLI (native), and integration-tests
# (native) are simply not listed.
build-fastly = "build -p trusted-server-core -p trusted-server-adapter-fastly -p trusted-server-js -p trusted-server-openrtb --target wasm32-wasip1"
check-fastly = "check -p trusted-server-core -p trusted-server-adapter-fastly -p trusted-server-js -p trusted-server-openrtb --target wasm32-wasip1"
clippy-fastly = "clippy -p trusted-server-core -p trusted-server-adapter-fastly -p trusted-server-js -p trusted-server-openrtb --all-targets --all-features --target wasm32-wasip1 -- -D warnings"
test-fastly = "test -p trusted-server-core -p trusted-server-adapter-fastly -p trusted-server-js -p trusted-server-openrtb --target wasm32-wasip1"
build-fastly = "build -p trusted-server-core -p trusted-server-adapter-fastly -p trusted-server-device-fastly -p trusted-server-geo-fastly -p trusted-server-js -p trusted-server-openrtb --target wasm32-wasip1"
check-fastly = "check -p trusted-server-core -p trusted-server-adapter-fastly -p trusted-server-device-fastly -p trusted-server-geo-fastly -p trusted-server-js -p trusted-server-openrtb --target wasm32-wasip1"
clippy-fastly = "clippy -p trusted-server-core -p trusted-server-adapter-fastly -p trusted-server-device-fastly -p trusted-server-geo-fastly -p trusted-server-js -p trusted-server-openrtb --all-targets --all-features --target wasm32-wasip1 -- -D warnings"
test-fastly = "test -p trusted-server-core -p trusted-server-adapter-fastly -p trusted-server-device-fastly -p trusted-server-geo-fastly -p trusted-server-js -p trusted-server-openrtb --target wasm32-wasip1"

# --- Axum adapter (native dev server) ---
build-axum = "build -p trusted-server-adapter-axum"
Expand Down
26 changes: 26 additions & 0 deletions .github/workflows/inspector.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: "Permissions Inspector"

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

jobs:
build-inspector-wasm:
name: build inspector wasm
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Retrieve Rust version
id: rust-version
run: echo "rust-version=$(grep -oP 'channel = "\K[^"]+' rust-toolchain.toml)" >> "$GITHUB_OUTPUT"
- name: Set up Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: ${{ steps.rust-version.outputs.rust-version }}
target: wasm32-unknown-unknown
- name: Build the inspector engine
run: ./scripts/build-inspector-wasm.sh
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
25 changes: 21 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,12 @@ jobs:
run: BID_DELAY=3 ./scripts/template-cache-local-test.sh inline

test-axum:
name: cargo test (axum native)
runs-on: ubuntu-latest
name: cargo test (axum native, ${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v4

Expand Down Expand Up @@ -103,12 +107,25 @@ jobs:
run: |
cargo test --package trusted-server-openrtb-codegen --target "$(rustc -vV | sed -n 's/host: //p')"

# The core library's unit tests otherwise run only on the WebAssembly
# targets, which build with panic=abort, so their harness stops at the
# first failing test and reports every later one as never run. A run that
# looks like a single failure can hide many more. This native run reports
# them all at once, which is what makes a red build readable.
- name: Run host-target core library tests
run: |
cargo test --package trusted-server-core --target "$(rustc -vV | sed -n 's/host: //p')"

- name: Verify Fastly WASM release build
run: cargo build --package trusted-server-adapter-fastly --release --target wasm32-wasip1

test-cloudflare:
name: cargo check (cloudflare native + wasm32-unknown-unknown)
runs-on: ubuntu-latest
name: cargo check (cloudflare native + wasm32-unknown-unknown, ${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v4

Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
/spin
/spin.sig

# logs
*.log

# EdgeZero local KV store (created by edgezero-adapter-axum framework)
.edgezero/
/dist/prebid/
Expand Down
95 changes: 93 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ crates/
trusted-server-adapter-cloudflare/ # Cloudflare Workers entry point (wasm32-unknown-unknown binary)
trusted-server-adapter-spin/ # Fermyon Spin entry point (wasm32-wasip1 component)
trusted-server-cli/ # Host-target `ts` operator CLI
device/
fastly/ # trusted-server-device-fastly (opt-in TLS/H2 device provider)
edgecookie/ # vendor Edge Cookie provider crates (built-in HMAC provider is in core)
geo/ # vendor geo provider crates (host geo is injected by the adapter)
trusted-server-js/ # TypeScript/JS build — per-integration IIFE bundles
lib/ # TS source, Vitest tests, esbuild pipeline
```
Expand Down Expand Up @@ -58,7 +62,9 @@ fastly compute serve
# Deploy to Fastly
fastly compute publish

# Run Axum dev server (native — no Viceroy)
# Run Axum dev server (native — no Viceroy). Settings load at runtime from the
# platform config store on every adapter; publish an operator config with
# `ts config push` (see trusted-server.example.toml for the template).
cargo run -p trusted-server-adapter-axum

# Test Axum adapter only
Expand Down Expand Up @@ -142,6 +148,20 @@ cd crates/trusted-server-js/lib && node build-all.mjs
cargo install viceroy --version 0.17.0 --locked --force
```

### Windows (use WSL for the Linux-only tests)

The Rust adapter tests run natively on Windows through the cargo aliases
(`cargo test-fastly` via Viceroy, `cargo test-axum`, `cargo test-cloudflare`),
and CI runs these on both `ubuntu-latest` and `windows-latest`.

The Docker-based integration suite (`scripts/integration-tests.sh`) and the
Cloudflare worker build (`crates/trusted-server-adapter-cloudflare/build.sh`,
which uses `worker-build` + `wrangler dev`) are Linux tools. On Windows run them
inside WSL (Ubuntu) with Docker Desktop's WSL integration enabled. Provision the
WSL distro with the same toolchain as `.tool-versions` (rustup + the
`wasm32-wasip1` / `wasm32-unknown-unknown` targets, Node, Viceroy, wrangler), then
run the scripts from a clone on the WSL native filesystem for fast builds.

---

## Coding Conventions
Expand Down Expand Up @@ -269,12 +289,48 @@ impl core::error::Error for MyError {}

## Other guidelines

- Use US English spelling everywhere: code, identifiers, comments,
documentation, tests, commit messages, and configuration. For example, write
`color`, `behavior`, and `optimize`, not `colour`, `behaviour`, or `optimise`.
Where a term comes from an external source (for example the IAB TCF purpose
names), match that source's spelling even when it is not US English.
- Use only example or fictional information in comments, tests, docs, examples,
and similar non-runtime materials. (eg. for urls use: example.com domains only)
- Do not write or commit real domains, customer names, credentials,
configuration values, or other potentially sensitive real-world information in
comments, tests, docs, or examples.

### Permission model terminology

Permissions are the primitive. A provider declares the permissions it requires
(`required_permissions`) and the system decides whether each is _set_. Consent
is only one of many ways a permission may be established. Country or
jurisdiction rules (a `Granted` group baseline), legitimate interest, or
configuration can set a permission with no consent at all.

- A provider that needs nothing **requires no permission**. Never write that it
"runs without any consent".
- A gated provider **runs once its required permissions are set**, by whatever
method.

**Evidence is not rationed, use is.** Every provider and every integration sees
all the evidence available for a request, including host signals such as the TLS
JA4 and HTTP/2 signals. The core never decides which vendor may see what,
because withholding a signal from one vendor and not another discriminates
between them, and the core stays neutral. What a vendor may *do* with the
evidence is governed by the permissions it declares and the system sets. Access
is universal, use is gated.

The practical consequence: never "fix" a vendor's access to a signal by hiding
the signal. If a use needs controlling, express it as a permission. A change
that removes evidence from a provider's reach is working against the
architecture, not protecting it.

- Reserve "consent" for the consent subsystem (`consent/`, `ConsentContext`,
GDPR and TCF strings) where it genuinely means a consent signal. In the
permission layer prefer "permission", "set" / "unset", and "signal" (consent
is one kind of signal, alongside privacy and opt-out signals).

---

## Git Commit Conventions
Expand All @@ -291,6 +347,41 @@ Bad: `"fix: added feature flags"`

---

## Provider Architecture

Each vendor-differentiated capability is pluggable behind its own trait, so a
deployment selects an implementation and the core stays neutral:

| Capability | Trait | Selector | Built-in (core) | Vendor / host crates |
| --------------------- | ---------------------------------------- | ------------------- | --------------------------------------- | ---------------------------- |
| Edge Cookie identity | `EdgeCookieProvider` (`ec/provider.rs`) | `[ec] provider` | HMAC, client-fixed (opt-in, no default) | `crates/edgecookie/<vendor>` |
| Device detection | `DeviceProvider` (`ec/device.rs`) | `[device] provider` | User-Agent only (default) | `crates/device/<vendor>` |
| Geo / IP intelligence | `PlatformGeo` (`platform/traits.rs`) | `[geo] provider` | Disabled, no location (default) | `crates/geo/<vendor>` |

Principles for adding or changing a provider:

- **Core stays neutral.** The trait and the host-neutral default live in
`trusted-server-core`. Host-specific and vendor implementations live in their
own crates and are injected by the adapter (for example `build_device_provider`
and `build_geo_provider`), so core never depends on a host SDK or a vendor, and
the default request path makes no host-specific calls.
- **Providers read request evidence, not a fixed parameter set.** A provider must
be able to see everything about the request it needs (User-Agent, headers, and
host signals such as the TLS JA4 and HTTP/2 signals) through an evidence
abstraction rather than a hard-coded struct of fields. Host signals come from
the host (the Fastly SDK) and are opt-in, so a neutral provider triggers no
host signal calls.
- **Providers are separated by capability but composed per request, and one may
need another's output.** Geo resolves the country and region the permission
model uses, and the permission model gates whether the Edge Cookie provider
runs. Device signals gate Edge Cookie writes (the browser / bot gate). When
multiple vendor providers share a backend (for example a vendor's Edge Cookie,
geo, and device provider on one cloud pipeline) they share a single call per
request rather than calling independently. Give a provider the inputs and
upstream results it needs explicitly, rather than having it reach into globals.

---

## Integration System

Integrations register in Rust via:
Expand Down Expand Up @@ -324,7 +415,7 @@ IntegrationRegistration::builder(ID)
| --------------------- | ---------------------------------------------------------- |
| `edgezero.toml` | EdgeZero app/platform manifest and logical stores |
| `fastly.toml` | Fastly service configuration and build settings |
| `trusted-server.example.toml` | Source-controlled Trusted Server app-config template |
| `trusted-server.example.toml` | Source-controlled app-config template (includes the `[ec]` / `[geo]` / `[device]` provider selectors) |
| `trusted-server.toml` | Operator-owned app config; gitignored; `ts config push` publishes it as an EdgeZero blob envelope |
| `rust-toolchain.toml` | Pins Rust version to 1.95.0 |
| `.env.dev` | Local development environment variables |
Expand Down
39 changes: 39 additions & 0 deletions Cargo.lock

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

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
[workspace]
resolver = "2"
members = [
"crates/device/fastly",
"crates/geo/fastly",
"crates/trusted-server-adapter-axum",
"crates/trusted-server-adapter-cloudflare",
"crates/trusted-server-adapter-fastly",
Expand Down Expand Up @@ -95,6 +97,7 @@ rustls-pemfile = "2"
scraper = "0.24.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.149"
serde_yaml_ng = "0.10"
sha2 = "0.10.9"
simple_logger = "5"
spin-sdk = { version = "~6.0", default-features = false, features = ["http", "key-value", "variables"] }
Expand All @@ -109,6 +112,8 @@ toml = "1.1"
toml_edit = "0.23.10"
tower = "0.4"
trusted-server-core = { path = "crates/trusted-server-core" }
trusted-server-device-fastly = { path = "crates/device/fastly" }
trusted-server-geo-fastly = { path = "crates/geo/fastly" }
trusted-server-js = { path = "crates/trusted-server-js" }
trusted-server-openrtb = { path = "crates/trusted-server-openrtb" }
url = "2.5.8"
Expand Down
Loading
Loading