You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add pluggable Edge Cookie, device, and geo providers gated by a permission model
Replace three hard-wired request-time decisions with
configuration-selected providers behind small traits: Edge Cookie
identity (EdgeCookieProvider), the device classification the bot gate
uses (DeviceProvider), and geolocation (PlatformGeo). Gate a provider's
execution on a technical permission model that separates legal policy
from the core, and remove the country-based allows_ec_creation
branching.
Permissions are the single currency every service and provider reads,
and none reads consent directly. Consent is one of several sources (the
country and region baseline, a consent signal, a configuration decision,
or data from elsewhere) that set a request's permissions. Feature and
provider code works against a clean, stable set that does not change as
laws or consent frameworks change; a source needing a new distinction
adds a permission to the model rather than leaking a branch into
consumers. The country and region rules live in a human-editable
permissions.yaml embedded into the build (include_str! in
permissions.rs), so a policy owner changes them in version control
rather than in code. It defines named groups that each spell out every
permission flag (gdpr-eu, gdpr-uk, us-opt-out) and rules mapping a
country or country/state to a group, with optional +permission (granted)
or -permission (denied) overrides. Rule keys use the ISO 3166-1 alpha-2
country and ISO 3166-2 subdivision codes a geo provider returns, matched
case-insensitively, so any geo provider feeds the same rules without
translation. PermissionMaps::standard() parses the file once into a
static cache and hands out a shared reference (no per-request clone). A
request that matches no rule, or whose country the geo provider cannot
resolve, uses the deployer's [geo] default_country. Trusted Server does
not assume a jurisdiction, so that default is required and validated at
startup: startup fails when it is unset or does not resolve to a known
rule. The shipped example uses the most protective baseline (FR,
GDPR-EU, where every permission requires a signal); a deployer sets the
jurisdiction that governs its traffic. Because policy is a declarative
map rather than branching code, a third party can read permissions.yaml
and see exactly what each permission resolves to for any country or
region.
Control stays in the core. A provider advertises the permissions its
data use requires (required_permissions()), and the core, not the
provider, decides whether it runs: it resolves the request's
PermissionState once (in EcContext) and refuses to execute a provider
whose required permissions are not set, so generate_if_needed returns
before the provider is even built.
Route every Trusted Server data decision through the resolved
permissions, not Edge Cookie creation alone. Bidstream EID transmission
gates on the resolved store-on-device and select-personalised-ads
permissions (gate_eids_by_permissions) instead of re-inspecting TCF and
a raw gdpr_applies flag. A request whose permissions do not allow the
Edge Cookie always has its EC response headers stripped, but the
destructive step (expire the browser cookie, delete the consent KV
entry, write the identity-graph tombstone) fires only on an explicit
withdrawal signal (ec_storage_withdrawn: a TCF record refusing storage,
or a US-style opt-out), never on a merely not-permitted state. This
holds on every path: ec_finalize_response, the publisher proxy
(apply_ec_headers), and both adapter bot gates. A pre-consent or
fail-closed request therefore suppresses the Edge Cookie for that
response but does not destroy an already-issued identifier before the
user has had the chance to consent. The jurisdiction-gated
has_explicit_ec_withdrawal and ec_consent_withdrawn are gone.
Map consent to a per-permission signal in one place (permission_signal).
A TCF record is authoritative wherever present, so the EU defers to the
CMP; a US-style opt-out (GPC, GPP sale opt-out, US Privacy opt-out)
revokes a granted baseline, and the map decides where a revoke has an
effect. Precedence is explicit: a present TCF record wins over a
US-style opt-out. Downstream RTB partners (Prebid, APS) still receive
the full regulatory context forwarded verbatim and run their own
enforcement; the permission model governs only Trusted Server's own
actions.
Wire providers by dependency injection. A provider reads request
evidence through the RequestInfo and HostSignals traits (evidence.rs)
rather than a fixed field struct, borrowed at call time so no
per-request HeaderMap is cloned. RequestInfo carries request data any
host can supply (client IP, User-Agent, headers); HostSignals carries
the host-computed TLS JA4 and HTTP/2 fingerprints and is opt-in, so a
neutral provider triggers no host fingerprint call. The adapter is the
composition root: it captures host signals once at the entry point and
supplies them, and a provider that needs a service the host cannot
supply cannot be built, so the request stops rather than minting a
degraded identifier. Core defines the traits and the neutral built-in
defaults and never calls a host SDK.
Support two Edge Cookie provider shapes behind the one trait. A
server-side provider mints at the edge: the built-in HMAC provider (over
the client IP), or a host-signal provider that derives the identifier
from the host fingerprints and client IP on any host that supplies a
HostSignals. A client-side provider defers on the page request, lets the
browser do the work, and posts the result to a new POST
/_ts/api/v1/ec/resolve endpoint that mints the cookie from the verified
value on its own response; verifying the posted value is the provider's
responsibility, so a client cannot forge an Edge Cookie. A neutral
client-fixed demo provider (client and server share one fixed known word
the server verifies) exercises the path end to end with a page script
shipped in the tsjs bundle when it is selected, and is for testing only.
Move the opt-in Fastly device and host geo providers into their own
crates (crates/device/fastly, crates/geo/fastly), selected and injected
by the adapter, so core keeps only the DeviceProvider and PlatformGeo
traits and the neutral defaults. The Fastly device provider reads the
User-Agent from the borrowed RequestInfo and the fingerprints from a
FastlyHostSignals built from the live request, and the adapter shares
that FastlyHostSignals as the host-signal service so the host-signal
Edge Cookie provider can use it too. A placeholder crates/edgecookie
directory marks where vendor Edge Cookie provider crates will live.
Restructure the [ec] configuration section (breaking change). The flat
[ec] passphrase field is removed and [ec] rejects unknown fields, so a
config carrying it fails to parse; the HMAC key moves to
[ec.providers.hmac] passphrase. Edge Cookie identity is off by default:
EC runs only when [ec] provider names a configured provider, so a config
that adds [ec.providers.hmac] but omits provider = "hmac" parses cleanly
and runs statelessly. Migration for an existing deployment:
[ec]
provider = "hmac"
[ec.providers.hmac]
passphrase = "your-existing-passphrase"
The provider selectors ([ec]/[device]/[geo] provider) and the mandatory
[geo] default_country are validated inside
Settings::finalize_deserialized, the single point every deserialization
path shares (TOML file, the runtime config-store blob, and the ts CLI
config push), so no load path can bypass the checks.
trusted-server.example.toml is the checked-in template and shows the new
sections. The default jurisdiction baseline is logged once per settings
load so an operator can see which permissions the unmatched-request
default grants without a signal.
Add an IntegrationResponseMutator hook for outbound response headers. No
built-in integration registers one yet; the registry carries the hook so
an integration crate (for example bot protection emitting Accept-CH) can
register a mutator without a core change.
Standardize spelling on US English, recorded as the convention in
CLAUDE.md, keeping external-source terms such as the IAB TCF purpose
names as their source spells them. Add the provider-architecture and
permission-model sections to CLAUDE.md, and document the model, the
provider selectors, and the permission sources in the guides.
Cover Windows in CI. The Rust adapter test jobs run on both
ubuntu-latest and windows-latest. The Docker-based integration suite and
the Cloudflare worker build remain Linux tools, run through WSL on
Windows as CLAUDE.md documents. The integration app-config fixture
selects the HMAC provider and sets [geo] default_country = US/CA, so a
request with no resolvable geolocation under Viceroy maps to a US
opt-out baseline and the GPC-withdrawal scenario exercises
permission-driven cookie expiry.
0 commit comments