From 550ae738ac80a37146d7fcbf80bef33e01da4e20 Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Fri, 12 Jun 2026 09:21:49 +0100 Subject: [PATCH 1/2] Apply neutral language framing across documentation and code comments The documentation and code comments carried framing that presented privacy as a property of the technology itself. Examples include "privacy-preserving" identifiers, "built-in GDPR compliance", "privacy-first" design choices, and text that treated one consent framework as the assumed default. Wording of that kind overstates what software can promise, understates the role of the deployer, and asks the reader to accept a policy position in order to accept a technical description. This change rewrites that material around four propositions: 1. Privacy is a spectrum, not a binary. Jurisdictions, publishers, and use cases sit at different points on it. 2. Technology should be neutral. The software provides mechanisms and does not enact a policy on the deployer's behalf. 3. Deployers decide based on their laws and their policy. They bring their own legal counsel, jurisdictions, and business model. The software's job is to let them implement that posture cleanly. 4. Trust comes from flexibility that is respected, not from constraint. What changed: - Documentation: loaded terms are replaced with factual descriptions of behavior. Compliance capability claims become descriptions of the consent signals read (TCF v2 format, GPP, US Privacy, GPC) and the configuration the publisher controls. TCF is described as one framework among others, and "TCF v2 format" is used when the encoded string schema is meant. - Code comments and string literals receive the same treatment, covering the ec module, integration modules, auction types, settings, and TOML comments. Client TLS and H2 values are named for what they are (probabilistic identifier, JA4 string, H2 SETTINGS string). Third-party SDK function names are unchanged. - Statements that no longer matched the code are corrected. The architecture page claimed user data is never persisted while EC identity state is persisted in KV stores. The edge cookie page described x-ts-ec as an inbound transport when it is a response header. The placeholder secret causes a settings load failure rather than a logged warning. Consent gating is built in per jurisdiction, and configuration tunes jurisdiction lists, GPC interpretation, and conflict resolution. - An audit worksheet and a followups plan under docs/superpowers/plans record the findings, the rewrites, and the items deferred to a code cycle (behavior questions and symbol renames that are out of scope for a wording change). No runtime behavior changes. Code edits are limited to comments, doc comments, and test assertion message strings. --- CLAUDE.md | 5 +- crates/js/lib/src/core/render.ts | 2 +- .../js/lib/src/integrations/creative/click.ts | 2 +- .../trusted-server-adapter-fastly/src/main.rs | 2 +- .../trusted-server-core/src/auction/README.md | 2 +- .../trusted-server-core/src/auction/types.rs | 4 +- .../trusted-server-core/src/consent_config.rs | 6 +- crates/trusted-server-core/src/constants.rs | 2 +- crates/trusted-server-core/src/ec/device.rs | 26 +- .../trusted-server-core/src/ec/generation.rs | 13 +- crates/trusted-server-core/src/ec/kv_types.rs | 12 +- crates/trusted-server-core/src/ec/mod.rs | 2 +- .../src/integrations/aps.rs | 6 +- .../src/integrations/google_tag_manager.rs | 2 +- .../src/integrations/gpt.rs | 4 +- .../src/integrations/lockr.rs | 2 +- .../src/integrations/permutive.rs | 2 +- crates/trusted-server-core/src/proxy.rs | 2 +- crates/trusted-server-core/src/settings.rs | 4 +- docs/.vitepress/config.mts | 2 +- docs/business-use-cases.md | 50 +-- docs/epics/revenue-operations-dashboard.md | 18 +- docs/guide/ad-serving.md | 16 +- docs/guide/architecture.md | 26 +- docs/guide/auction-orchestration.md | 4 +- docs/guide/collective-sync.md | 8 +- docs/guide/configuration.md | 6 +- docs/guide/creative-processing.md | 17 +- docs/guide/ec-setup-guide.md | 2 +- docs/guide/edge-cookies.md | 79 +++-- docs/guide/first-party-proxy.md | 12 +- docs/guide/gdpr-compliance.md | 124 +++++--- docs/guide/getting-started.md | 2 +- docs/guide/integration-guide.md | 2 +- docs/guide/integrations-overview.md | 14 +- docs/guide/integrations/aps.md | 10 +- docs/guide/integrations/didomi.md | 22 +- docs/guide/integrations/gam.md | 2 +- docs/guide/integrations/google_tag_manager.md | 14 +- docs/guide/integrations/gpt.md | 2 +- docs/guide/integrations/kargo.md | 2 +- docs/guide/integrations/lockr.md | 14 +- docs/guide/integrations/nextjs.md | 2 +- docs/guide/integrations/permutive.md | 6 +- docs/guide/integrations/prebid.md | 10 +- docs/guide/key-rotation.md | 2 +- docs/guide/what-is-trusted-server.md | 39 ++- docs/index.md | 16 +- docs/roadmap.md | 8 +- .../2026-05-22-doc-neutral-privacy-audit.md | 295 ++++++++++++++++++ ...26-05-22-privacy-neutral-code-followups.md | 225 +++++++++++++ trusted-server.toml | 20 +- 52 files changed, 892 insertions(+), 279 deletions(-) create mode 100644 docs/superpowers/plans/2026-05-22-doc-neutral-privacy-audit.md create mode 100644 docs/superpowers/plans/2026-05-22-privacy-neutral-code-followups.md diff --git a/CLAUDE.md b/CLAUDE.md index f37a1ac30..1c83662e2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,8 +6,9 @@ ## Project Overview Rust-based edge computing application targeting **Fastly Compute**. Handles -privacy-preserving Edge Cookie (EC) ID generation, ad serving with GDPR compliance, -real-time bidding integration, and publisher-side JavaScript injection. +Edge Cookie (EC) ID generation, ad serving with consent signal extraction +and enforcement, real-time bidding integration, and publisher-side +JavaScript injection. ## Workspace Layout diff --git a/crates/js/lib/src/core/render.ts b/crates/js/lib/src/core/render.ts index ee08ef288..61d3adad3 100644 --- a/crates/js/lib/src/core/render.ts +++ b/crates/js/lib/src/core/render.ts @@ -7,7 +7,7 @@ import NORMALIZE_CSS from './styles/normalize.css?inline'; import IFRAME_TEMPLATE from './templates/iframe.html?raw'; // Sandbox permissions granted to creative iframes. -// Ad creatives routinely contain scripts for tracking, click handling, and +// Ad creatives routinely contain scripts for impression reporting, click handling, and // viewability measurement, so allow-scripts and allow-same-origin are required // for creatives to render correctly. Server-side sanitization is the primary // defense against malicious markup; the sandbox provides defense-in-depth. diff --git a/crates/js/lib/src/integrations/creative/click.ts b/crates/js/lib/src/integrations/creative/click.ts index a505c03c5..15733a3fc 100644 --- a/crates/js/lib/src/integrations/creative/click.ts +++ b/crates/js/lib/src/integrations/creative/click.ts @@ -1,4 +1,4 @@ -// Click guard runtime: detects mutated tracking URLs and rebuilds signed first-party clicks. +// Click guard runtime: detects mutated click URLs and rebuilds signed first-party clicks. import { log } from '../../core/log'; import { creativeGlobal } from '../../shared/globals'; import { delay, queueTask } from '../../shared/async'; diff --git a/crates/trusted-server-adapter-fastly/src/main.rs b/crates/trusted-server-adapter-fastly/src/main.rs index 741ae8ff7..ffc1f7279 100644 --- a/crates/trusted-server-adapter-fastly/src/main.rs +++ b/crates/trusted-server-adapter-fastly/src/main.rs @@ -709,7 +709,7 @@ mod tests { ); assert!( body.contains("h2_fp: unavailable"), - "should include H2 fingerprint fallback" + "should include H2 probabilistic identifier fallback" ); assert!( body.contains("cipher: unavailable"), diff --git a/crates/trusted-server-core/src/auction/README.md b/crates/trusted-server-core/src/auction/README.md index e92c4dbb6..388f77772 100644 --- a/crates/trusted-server-core/src/auction/README.md +++ b/crates/trusted-server-core/src/auction/README.md @@ -247,7 +247,7 @@ The orchestrator collects all bids and creates an OpenRTB response: } ``` -Note that creative HTML is rewritten to use the first-party proxy (`/first-party/proxy`) for privacy and security. +Note that creative HTML is rewritten to use the first-party proxy (`/first-party/proxy`) for security. ## Route Registration & Endpoints diff --git a/crates/trusted-server-core/src/auction/types.rs b/crates/trusted-server-core/src/auction/types.rs index 9b74d89ec..a4f889e2e 100644 --- a/crates/trusted-server-core/src/auction/types.rs +++ b/crates/trusted-server-core/src/auction/types.rs @@ -18,7 +18,7 @@ pub struct AuctionRequest { pub slots: Vec, /// Publisher information pub publisher: PublisherInfo, - /// User information (privacy-preserving) + /// User information (consent-aware) pub user: UserInfo, /// Device information pub device: Option, @@ -67,7 +67,7 @@ pub struct PublisherInfo { pub page_url: Option, } -/// Privacy-preserving user information. +/// Consent-aware user information. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UserInfo { /// Stable EC ID (from cookie or freshly generated). diff --git a/crates/trusted-server-core/src/consent_config.rs b/crates/trusted-server-core/src/consent_config.rs index e5fed1a9c..62e4b3fbf 100644 --- a/crates/trusted-server-core/src/consent_config.rs +++ b/crates/trusted-server-core/src/consent_config.rs @@ -167,7 +167,7 @@ impl ConsentForwardingMode { /// The `applies_in` list is used for **observability and logging only** — it /// does NOT cause consent to be synthesized. When a user's country appears in /// this list, the system logs that GDPR applies, enabling publishers to -/// monitor compliance coverage. +/// monitor jurisdiction coverage. #[derive(Debug, Clone, Deserialize, Serialize)] pub struct GdprConfig { /// ISO 3166-1 alpha-2 country codes where GDPR applies. @@ -272,11 +272,11 @@ impl Default for ConflictResolutionConfig { #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] #[serde(rename_all = "lowercase")] pub enum ConflictMode { - /// Deny consent when signals disagree (most privacy-safe). + /// Deny consent when signals disagree (most restrictive). Restrictive, /// Use the newer signal based on timestamps. Newest, - /// Grant consent when signals disagree (requires legal review). + /// Grant consent when signals disagree (least restrictive). Permissive, } diff --git a/crates/trusted-server-core/src/constants.rs b/crates/trusted-server-core/src/constants.rs index 0dec1daec..5521e4174 100644 --- a/crates/trusted-server-core/src/constants.rs +++ b/crates/trusted-server-core/src/constants.rs @@ -43,7 +43,7 @@ pub const HEADER_REFERER: HeaderName = HeaderName::from_static("referer"); /// /// These headers are used internally by Trusted Server for identity, geo-enrichment, /// debugging, and compression hints. Leaking them to external origins could expose -/// user tracking data and internal implementation details. +/// user identity, geo data, and internal implementation details. /// /// Uses `&str` slices because `HeaderName` has interior mutability and cannot appear /// in `const` context. diff --git a/crates/trusted-server-core/src/ec/device.rs b/crates/trusted-server-core/src/ec/device.rs index 7e540bcef..fbefa9586 100644 --- a/crates/trusted-server-core/src/ec/device.rs +++ b/crates/trusted-server-core/src/ec/device.rs @@ -33,7 +33,7 @@ pub struct DeviceSignals { /// Coarse OS family: `"mac"`, `"windows"`, `"ios"`, `"android"`, /// `"linux"`. pub platform_class: Option, - /// SHA256 prefix (12 hex chars) of raw H2 SETTINGS fingerprint. + /// SHA256 prefix (12 hex chars) of the raw H2 SETTINGS string. pub h2_fp_hash: Option, /// `true` = known browser, `false` = known bot, `None` = unknown. pub known_browser: Option, @@ -65,8 +65,8 @@ impl DeviceSignals { /// Returns `true` when the request looks like a real browser. /// /// Checks for the presence of recognizable signals rather than matching - /// against a hardcoded fingerprint allowlist. Real browsers always - /// produce a valid TLS fingerprint (`ja4_class`) and a recognizable UA + /// against a hardcoded signal allowlist. Real browsers always + /// produce a valid TLS probabilistic identifier (`ja4_class`) and a recognizable UA /// platform string (`platform_class`). Raw HTTP clients (curl, Python /// requests, Go net/http, headless scrapers) typically lack one or both. /// @@ -146,11 +146,11 @@ fn parse_platform_class(ua: &str) -> Option { None } -/// Extracts Section 1 from a full JA4 fingerprint. +/// Extracts Section 1 from a full JA4 string. /// /// JA4 format: `section1_section2_section3` separated by underscores. /// Section 1 identifies browser family (cipher count, extension count, -/// ALPN) without uniquely fingerprinting a device. +/// ALPN) without uniquely identifying a device. /// /// Returns `None` if the input is empty or has no underscore-delimited /// section. @@ -164,7 +164,7 @@ fn extract_ja4_section1(full_ja4: &str) -> Option { } /// Computes a 12-hex-char prefix of the SHA256 hash of the raw H2 -/// SETTINGS fingerprint string. +/// SETTINGS string. /// /// The raw string looks like `"1:65536;2:0;4:6291456;6:262144"`. #[must_use] @@ -175,7 +175,7 @@ fn compute_h2_fp_hash(raw_h2_fp: &str) -> String { hex::encode(&digest[..6]) } -/// Known browser fingerprint allowlist. +/// Known browser signal allowlist. /// /// Each entry is `(ja4_class, h2_fp_prefix, known_browser)`. /// `h2_fp_prefix` is the raw H2 SETTINGS string (not the hash) — we @@ -191,7 +191,7 @@ const KNOWN_BROWSERS: &[(&str, &str, bool)] = &[ ("t13d1717h2", "1:65536;2:0;4:131072;5:16384", true), ]; -/// Returns H2 fingerprint hashes for the known browser allowlist. +/// Returns H2 SETTINGS hashes for the known browser allowlist. /// /// Computed once on first call and cached via `OnceLock`. fn known_browser_h2_hashes() -> &'static Vec<(&'static str, String, bool)> { @@ -366,7 +366,7 @@ mod tests { assert_eq!( evaluate_known_browser(Some(ja4), Some(&h2_hash)), Some(true), - "Chrome fingerprint should be recognized" + "Chrome signals should be recognized" ); } @@ -377,7 +377,7 @@ mod tests { assert_eq!( evaluate_known_browser(Some(ja4), Some(&h2_hash)), Some(true), - "Safari fingerprint should be recognized" + "Safari signals should be recognized" ); } @@ -388,7 +388,7 @@ mod tests { assert_eq!( evaluate_known_browser(Some(ja4), Some(&h2_hash)), Some(true), - "Firefox fingerprint should be recognized" + "Firefox signals should be recognized" ); } @@ -538,7 +538,7 @@ mod tests { ); assert!( signals.looks_like_browser(), - "unknown fingerprint with valid JA4 + platform should pass" + "unknown signal combination with valid JA4 + platform should pass" ); assert_eq!(signals.known_browser, None, "should not match allowlist"); } @@ -554,7 +554,7 @@ mod tests { #[test] fn looks_like_browser_rejects_missing_ja4() { - // Real UA but no TLS fingerprint (e.g. HTTP/1.1 or missing SDK support) + // Real UA but no JA4 value (e.g. HTTP/1.1 or missing SDK support) let signals = DeviceSignals::derive(CHROME_MAC_UA, None, Some("1:65536")); assert!( !signals.looks_like_browser(), diff --git a/crates/trusted-server-core/src/ec/generation.rs b/crates/trusted-server-core/src/ec/generation.rs index 480682927..2730e93d4 100644 --- a/crates/trusted-server-core/src/ec/generation.rs +++ b/crates/trusted-server-core/src/ec/generation.rs @@ -1,7 +1,10 @@ //! Edge Cookie (EC) ID generation using HMAC. //! -//! This module provides functionality for generating privacy-preserving EC IDs -//! based on the client IP address and a secret key. +//! This module generates EC IDs from the client IP address and a configured +//! secret key. Trusted Server is technology. It is neutral on policy. +//! Whether the resulting cookie is set is gated by the consent +//! evaluation for the detected jurisdiction. How the value is used by +//! downstream integrations is determined by integration configuration. use std::net::IpAddr; @@ -65,9 +68,9 @@ fn generate_random_suffix(length: usize) -> String { /// Generates a fresh EC ID from a pre-captured client IP string. /// /// Uses only the client IP (not user-agent or other headers) intentionally: -/// EC IDs are meant to be simple, privacy-preserving identifiers — not -/// high-entropy fingerprints. The random suffix provides per-cookie -/// uniqueness for users behind the same NAT/proxy. +/// EC IDs are deterministic identifiers (HMAC base plus random suffix), +/// not high-entropy probabilistic identifiers. The random suffix provides +/// per-cookie uniqueness for users behind the same NAT or proxy. /// /// Creates an HMAC-SHA256-based ID using the configured secret key and /// the client IP address, then appends a random suffix for additional diff --git a/crates/trusted-server-core/src/ec/kv_types.rs b/crates/trusted-server-core/src/ec/kv_types.rs index 5f2c43007..5a521435b 100644 --- a/crates/trusted-server-core/src/ec/kv_types.rs +++ b/crates/trusted-server-core/src/ec/kv_types.rs @@ -51,7 +51,7 @@ pub struct KvEntry { /// Creation-time publisher property metadata. #[serde(default, skip_serializing_if = "Option::is_none")] pub pub_properties: Option, - /// Device class signals (TLS fingerprint, UA platform). + /// Device class signals (TLS handshake, UA platform). /// Written once on creation — never updated after. #[serde(default, skip_serializing_if = "Option::is_none")] pub device: Option, @@ -92,7 +92,7 @@ pub struct KvGeo { #[serde(default, skip_serializing_if = "Option::is_none")] pub asn: Option, /// DMA/metro code (e.g. `807` = San Francisco). - /// Market-level targeting signal; not personal data. + /// Market-level targeting signal. #[serde(default, skip_serializing_if = "Option::is_none")] pub dma: Option, } @@ -143,7 +143,7 @@ where } } -/// Coarse, non-PII device signals derived from TLS handshake and UA. +/// Coarse device signals derived from TLS handshake and UA. /// /// Used by the `/_ts/api/v1/identify` endpoint for cross-suffix propagation decisions /// and buyer-facing device quality scoring. Written once on @@ -151,7 +151,7 @@ where /// /// **Privacy:** `ja4_class` (Section 1 only) and `platform_class` are /// category signals, not unique device identifiers. The full JA4 -/// fingerprint (Sections 2–3) is never stored. +/// string (Sections 2–3) is never stored. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct KvDevice { /// Mobile signal: `0` = confirmed desktop, `1` = confirmed mobile, @@ -160,14 +160,14 @@ pub struct KvDevice { pub is_mobile: u8, /// JA4 Section 1 only — browser family class identifier. /// e.g. `"t13d1516h2"` = Chrome, `"t13d2013h2"` = Safari. - /// Never stores the full JA4 fingerprint. + /// Never stores the full JA4 string. #[serde(default, skip_serializing_if = "Option::is_none")] pub ja4_class: Option, /// Coarse OS family from UA: `"mac"`, `"windows"`, `"ios"`, /// `"android"`, `"linux"`. #[serde(default, skip_serializing_if = "Option::is_none")] pub platform_class: Option, - /// SHA256 prefix (12 hex chars) of the HTTP/2 SETTINGS fingerprint. + /// SHA256 prefix (12 hex chars) of the HTTP/2 SETTINGS string. /// Used alongside `ja4_class` for browser confirmation and bot detection. #[serde(default, skip_serializing_if = "Option::is_none")] pub h2_fp_hash: Option, diff --git a/crates/trusted-server-core/src/ec/mod.rs b/crates/trusted-server-core/src/ec/mod.rs index 8fb9f5b79..86e862b98 100644 --- a/crates/trusted-server-core/src/ec/mod.rs +++ b/crates/trusted-server-core/src/ec/mod.rs @@ -19,7 +19,7 @@ //! - [`cookies`] — `Set-Cookie` header creation and expiration helpers //! - [`kv`] — KV Store identity graph operations (CAS, tombstones, debounce) //! - [`kv_types`] — Schema types for KV identity graph entries -//! - [`device`] — Device signal derivation (UA, JA4, H2 fingerprinting) +//! - [`device`]: Device signal derivation (UA, JA4, H2 SETTINGS) //! - [`partner`] — Partner validation helpers (ID format, pull sync config) //! - [`registry`] — In-memory partner registry built from config //! - [`rate_limiter`] — Rate limiting abstraction (Fastly Edge Rate Limiting) diff --git a/crates/trusted-server-core/src/integrations/aps.rs b/crates/trusted-server-core/src/integrations/aps.rs index 1c966e717..75f98021a 100644 --- a/crates/trusted-server-core/src/integrations/aps.rs +++ b/crates/trusted-server-core/src/integrations/aps.rs @@ -99,7 +99,7 @@ struct ApsContextual { #[serde(default)] slots: Vec, - /// Event tracking host + /// Event collection endpoint #[serde(skip_serializing_if = "Option::is_none")] host: Option, @@ -111,7 +111,7 @@ struct ApsContextual { #[serde(skip_serializing_if = "Option::is_none")] cfe: Option, - /// Event tracking enabled + /// Event collection enabled #[serde(skip_serializing_if = "Option::is_none")] ev: Option, @@ -123,7 +123,7 @@ struct ApsContextual { #[serde(skip_serializing_if = "Option::is_none")] cb: Option, - /// Campaign tracking URL + /// Campaign attribution URL #[serde(skip_serializing_if = "Option::is_none")] cmp: Option, } diff --git a/crates/trusted-server-core/src/integrations/google_tag_manager.rs b/crates/trusted-server-core/src/integrations/google_tag_manager.rs index 698cd23f3..5cbd589cf 100644 --- a/crates/trusted-server-core/src/integrations/google_tag_manager.rs +++ b/crates/trusted-server-core/src/integrations/google_tag_manager.rs @@ -1,7 +1,7 @@ //! Google Tag Manager integration for first-party tag delivery. //! //! Proxies GTM scripts and Google Analytics beacons through the publisher's -//! domain, improving tracking accuracy and ad-blocker resistance. +//! domain, improving measurement accuracy and ad-blocker resistance. //! //! # Endpoints //! diff --git a/crates/trusted-server-core/src/integrations/gpt.rs b/crates/trusted-server-core/src/integrations/gpt.rs index 40bcf7f2c..e6afebb6d 100644 --- a/crates/trusted-server-core/src/integrations/gpt.rs +++ b/crates/trusted-server-core/src/integrations/gpt.rs @@ -1,8 +1,8 @@ //! Google Publisher Tags (GPT) integration for first-party ad serving. //! //! This module provides transparent proxying for Google's entire GPT script -//! chain, enabling first-party ad tag delivery while maintaining privacy -//! controls. GPT loads scripts in a cascade: +//! chain, enabling first-party ad tag delivery. +//! GPT loads scripts in a cascade: //! //! 1. `gpt.js` – the thin bootstrap loader //! 2. `pubads_impl.js` – the main GPT implementation (~640 KB) diff --git a/crates/trusted-server-core/src/integrations/lockr.rs b/crates/trusted-server-core/src/integrations/lockr.rs index 8e63345fe..2bddb41e4 100644 --- a/crates/trusted-server-core/src/integrations/lockr.rs +++ b/crates/trusted-server-core/src/integrations/lockr.rs @@ -1,7 +1,7 @@ //! Lockr integration for identity resolution and advertising tokens. //! //! This module provides transparent proxying for Lockr's SDK and API, -//! enabling first-party identity resolution while maintaining privacy controls. +//! enabling first-party identity resolution. //! //! Lockr provides a dedicated trust-server SDK (`identity-lockr-trust-server.js`) //! that is pre-configured to route API calls through the first-party proxy, diff --git a/crates/trusted-server-core/src/integrations/permutive.rs b/crates/trusted-server-core/src/integrations/permutive.rs index 41d7e3bf0..4ea50e662 100644 --- a/crates/trusted-server-core/src/integrations/permutive.rs +++ b/crates/trusted-server-core/src/integrations/permutive.rs @@ -1,7 +1,7 @@ //! Permutive integration for first-party data collection and audience management. //! //! This module provides transparent proxying for Permutive's API and SDK, -//! enabling first-party data collection while maintaining privacy controls. +//! enabling first-party data collection. use std::sync::Arc; diff --git a/crates/trusted-server-core/src/proxy.rs b/crates/trusted-server-core/src/proxy.rs index e27f7d5ad..c2ce83adc 100644 --- a/crates/trusted-server-core/src/proxy.rs +++ b/crates/trusted-server-core/src/proxy.rs @@ -504,7 +504,7 @@ fn finalize_proxied_response( beresp.set_header(header::CONTENT_TYPE, "image/*"); } - // Heuristics to log likely tracking pixels without altering response + // Heuristics to log likely pixel images without altering response let mut is_pixel = false; if let Some(cl) = beresp .get_header(header::CONTENT_LENGTH) diff --git a/crates/trusted-server-core/src/settings.rs b/crates/trusted-server-core/src/settings.rs index 4c6950fce..b73d9930d 100644 --- a/crates/trusted-server-core/src/settings.rs +++ b/crates/trusted-server-core/src/settings.rs @@ -1644,10 +1644,10 @@ impl Proxy { /// Debug-only features. All flags default to `false` (off in production). #[derive(Debug, Default, Clone, Deserialize, Serialize)] pub struct DebugConfig { - /// Expose the JA4/TLS fingerprint debug endpoint at `GET /_ts/debug/ja4`. + /// Expose the JA4/TLS probabilistic identifier debug endpoint at `GET /_ts/debug/ja4`. /// /// When `false` (the default), the endpoint returns 404. Enable only for - /// intentional Fastly/browser TLS investigation — the endpoint reflects + /// intentional Fastly/browser TLS investigation. The endpoint reflects /// Fastly-observed TLS details that browser JS cannot normally read. #[serde(default)] pub ja4_endpoint_enabled: bool, diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index ec0830abf..d98b48eb5 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -36,7 +36,7 @@ export default withMermaid( defineConfig({ title: 'Trusted Server', description: - 'Privacy-preserving edge computing for ad serving and edge cookie (EC) generation', + 'Edge computing for ad serving, consent signal handling, and edge cookie (EC) generation', base: '/trusted-server', // Replace version placeholders like {{NODEJS_VERSION}} with values from .tool-versions diff --git a/docs/business-use-cases.md b/docs/business-use-cases.md index 67c7b51d3..4e78a2e51 100644 --- a/docs/business-use-cases.md +++ b/docs/business-use-cases.md @@ -1,6 +1,8 @@ # Business Use Cases -Trusted Server delivers measurable business value across revenue generation, compliance, performance, and user experience. This page outlines real-world scenarios where Trusted Server provides significant ROI. +Scenarios where publishers can realize measurable value with Trusted +Server across revenue, consent handling, performance, and user +experience. --- @@ -8,9 +10,9 @@ Trusted Server delivers measurable business value across revenue generation, com ### 1. Increased Ad Revenue in Cookieless Environments -**Problem**: Safari and Firefox block third-party cookies, fragmenting user identity and reducing addressable inventory CPMs by 30-50%. +**Problem**: Safari and Firefox restrict third-party cookie scope, reducing cross-site identifier continuity and addressable inventory CPMs by 30-50%. -**Solution**: Trusted Server's Edge Cookie (EC) system maintains user recognition across cookieless browsers through first-party identifiers. +**Solution**: Trusted Server's Edge Cookie (EC) system provides first-party identifiers for user recognition in restricted cookie environments. **Business Impact**: @@ -47,10 +49,10 @@ Trusted Server delivers measurable business value across revenue generation, com **Key Differentiators**: -- Granular consent controls (GDPR compliant) +- Granular consent controls (TCF v2 format Purpose/vendor scope, GPC) - Real-time segment activation - First-party data ownership -- Privacy-preserving sharing +- Consent-based sharing --- @@ -104,16 +106,16 @@ Trusted Server delivers measurable business value across revenue generation, com ## Compliance & Risk Mitigation -### 5. GDPR Fine Prevention +### 5. GDPR Fine Risk Reduction -**Problem**: Third-party ad tech vendors inject non-compliant tracking scripts, exposing publishers to multi-million dollar GDPR fines. +**Problem**: Third-party ad tech vendors can introduce scripts that conflict with the publisher's consent policy, exposing publishers to multi-million dollar GDPR fines. -**Solution**: Trusted Server's Creative Forensics Engine detects and blocks GDPR violations before they reach users. +**Solution**: Trusted Server's planned Creative Forensics Engine (see the [roadmap](/roadmap)) is designed to detect and block creative behavior that conflicts with the publisher's configured consent policy before it reaches users. **Business Impact**: -- **Fine Avoidance**: Prevent €2.3M+ fines (4% of global revenue) -- **Regulatory Risk Reduction**: Automated compliance monitoring +- **Fine Exposure Reduction**: Reduce exposure to €2.3M+ fines (4% of global revenue) +- **Regulatory Risk Reduction**: Automated policy monitoring - **Brand Protection**: Avoid public regulatory actions **Real-World Example**: @@ -123,12 +125,12 @@ Trusted Server delivers measurable business value across revenue generation, com - Trusted Server cost: €100K annually - **ROI**: 20:1 (risk mitigation value) -**Compliance Features**: +**Planned Capabilities**: - Real-time creative scanning -- Unauthorized pixel detection -- GDPR consent validation -- Automated blocking of violations +- Detection of pixels outside the publisher's domain allowlist +- Consent signal validation (TCF v2 format, GPP, GPC) +- Automated blocking per configured policy --- @@ -248,7 +250,7 @@ Trusted Server delivers measurable business value across revenue generation, com **Problem**: Reliance on 10+ third-party vendors creates operational complexity, security risks, and hidden costs. -**Solution**: Trusted Server consolidates identity, ad serving, privacy, and analytics into a single edge platform. +**Solution**: Trusted Server consolidates identity, ad serving, consent management, and analytics into a single edge platform. **Business Impact**: @@ -267,17 +269,17 @@ Trusted Server delivers measurable business value across revenue generation, com ## Competitive Differentiation -### 11. Privacy-First Publisher Positioning +### 11. Publisher Positioning on Transparency -**Problem**: Users increasingly distrust ad-supported content due to invasive tracking. +**Problem**: Some user groups disengage from ad-supported content where data flows are opaque to them. -**Solution**: Trusted Server enables transparent, consent-based advertising that rebuilds user trust. +**Solution**: Trusted Server provides transparent, consent-based advertising mechanisms. Trust is a user outcome that depends on the publisher's choices and on what users do with the controls available to them. **Business Impact**: -- **User Loyalty**: Privacy-conscious users stay engaged -- **Premium Positioning**: Differentiate from surveillance-based competitors -- **Regulatory Advantage**: Early GDPR/CCPA compliance = competitive moat +- **User Engagement**: Privacy-conscious users have visible controls +- **Positioning**: Differentiate via consent transparency +- **Regulatory Advantage**: Early consent infrastructure adoption = competitive moat **Brand Value**: @@ -291,12 +293,12 @@ Trusted Server delivers measurable business value across revenue generation, com **Problem**: Privacy regulations (GDPR, CCPA, etc.) and browser changes (cookie deprecation) create constant technical debt. -**Solution**: Trusted Server's architecture is built for a cookieless, privacy-first future. +**Solution**: Trusted Server's architecture is built for a cookieless future and evolving regulatory requirements. **Business Impact**: - **Reduced Technical Debt**: No emergency migrations when browsers change -- **Regulatory Readiness**: Built-in compliance for new regulations +- **Regulatory Readiness**: Configurable consent handling as new regulations emerge - **Competitive Advantage**: Move faster than competitors stuck on legacy tech **Long-Term Value**: @@ -338,7 +340,7 @@ Trusted Server delivers measurable business value across revenue generation, com **Risk Mitigation**: -1. GDPR fine avoidance: +$2.3M (risk value) +1. GDPR fine risk reduction: +$2.3M (risk value) 2. Malvertising prevention: +$1.2M (user retention) **Net Impact**: +$3.68M annually diff --git a/docs/epics/revenue-operations-dashboard.md b/docs/epics/revenue-operations-dashboard.md index c1907d25e..0a233500e 100644 --- a/docs/epics/revenue-operations-dashboard.md +++ b/docs/epics/revenue-operations-dashboard.md @@ -7,7 +7,7 @@ A real-time publisher transparency dashboard providing visibility into ad moneti ## Business Value - **Revenue Optimization**: Identify underperforming exchanges, timeout issues, and bid density gaps -- **Compliance Assurance**: Detect unauthorized calls, consent violations, and policy breaches +- **Policy Monitoring**: Detect calls outside the configured allowlist, consent mismatches, and policy breaches - **Operational Visibility**: Debug issues faster with real-time data on all ad-related activity - **Vendor Accountability**: Hold partners accountable with data on their behavior @@ -74,7 +74,7 @@ A real-time publisher transparency dashboard providing visibility into ad moneti - [ ] Instrument proxy request handlers to emit domain call events - [ ] Add session/page_id correlation (via EC ID or request header) - [ ] Create domain allowlist configuration in settings.toml -- [ ] Emit `unauthorized_domain` alert event when unknown domain called +- [ ] Emit `non_allowlisted_domain` alert event when a domain outside the configured allowlist is called - [ ] Track request timing (DNS, connect, TTFB, total) #### Data Schema @@ -112,7 +112,7 @@ A real-time publisher transparency dashboard providing visibility into ad moneti #### Technical Tasks - [ ] Parse request paths to categorize endpoint types -- [ ] Build SDK fingerprinting logic (match known SDK URL patterns) +- [ ] Build SDK detection logic (match known SDK URL patterns) - [ ] Emit `sdk_call` events with SDK attribution - [ ] Create SDK registry configuration for known vendors @@ -138,21 +138,21 @@ A real-time publisher transparency dashboard providing visibility into ad moneti **As a** publisher **I want** to track consent state and detect compliance violations -**So that** I can ensure GDPR/CCPA compliance and avoid regulatory risk +**So that** I can monitor my GDPR/CCPA posture and reduce regulatory risk #### Acceptance Criteria - [ ] Log consent state (TCF string, USP string) per request -- [ ] Flag calls made to tracking domains when consent not granted +- [ ] Flag calls made to consent-gated domains when consent not granted - [ ] Track consent rate by geography -- [ ] Alert on potential violations (call to tracking domain without consent) +- [ ] Alert on potential violations (call to a consent-gated domain without consent) #### Technical Tasks - [ ] Parse TCF/GPP consent strings in request flow -- [ ] Build tracking domain classification (analytics, advertising, functional) +- [ ] Build domain purpose classification (analytics, advertising, functional) - [ ] Cross-reference consent state with domain calls -- [ ] Emit `compliance_violation` events when unauthorized tracking detected +- [ ] Emit `policy_check_failed` events when a call reaches a consent-gated domain without the consent signal the deployer's policy requires - [ ] Add geo detection for consent rate reporting #### Data Schema @@ -165,7 +165,7 @@ A real-time publisher transparency dashboard providing visibility into ad moneti "consent_tcf": "CO...", "consent_usp": "1YNN", "geo_country": "DE", - "tracking_authorized": true, + "consent_authorized": true, "violation_detected": false, "violation_domain": null } diff --git a/docs/guide/ad-serving.md b/docs/guide/ad-serving.md index 44449b128..9486821f5 100644 --- a/docs/guide/ad-serving.md +++ b/docs/guide/ad-serving.md @@ -1,10 +1,10 @@ # Ad Serving -Learn how Trusted Server handles privacy-compliant ad serving. +Learn how Trusted Server handles consent-aware ad serving. ## Overview -Trusted Server provides edge-based ad serving with built-in GDPR compliance and real-time bidding support. +Trusted Server provides edge-based ad serving with consent signal enforcement and real-time bidding support. ## Supported Integrations @@ -29,8 +29,8 @@ Real-time bidding integration: ## Ad Request Flow 1. Request validation -2. GDPR consent check -3. EC ID generation (if consented) +2. Consent signal check +3. EC ID generation (when the consent evaluation permits) 4. Ad server request 5. Response processing 6. Creative delivery @@ -59,7 +59,7 @@ Creatives can be proxied through Trusted Server for: - Security scanning - Content modification - Click tracking injection -- GDPR compliance +- Consent signal enforcement ### Direct Mode @@ -84,10 +84,10 @@ trustedServer.trackImpression({ ### Click Tracking -Click tracking with privacy preservation: +Click tracking via first-party context: -- No PII in URLs -- EC ID only (with consent) +- URLs carry no name, email, or account identifier fields supplied by the user +- EC ID when issued under the consent gate - Encrypted parameters ## Performance diff --git a/docs/guide/architecture.md b/docs/guide/architecture.md index 501e577c1..422e731b4 100644 --- a/docs/guide/architecture.md +++ b/docs/guide/architecture.md @@ -13,7 +13,7 @@ flowchart TD subgraph edge["Trusted Server"] direction TB - gdpr["GDPR Check"] + gdpr["Consent Check"] ids["EC IDs"] ads["Ad Serving"] gdpr --> ids --> ads @@ -32,7 +32,7 @@ Core library containing shared functionality: - Edge Cookie (EC) ID generation - Cookie handling - HTTP abstractions -- GDPR consent management +- Consent signal handling - Ad server integrations ### trusted-server-adapter-fastly @@ -63,18 +63,18 @@ pub trait RequestWrapper { External configuration via `trusted-server.toml` allows deployment-time customization without code changes. -### Privacy-First Design +### Consent-Aware Design -All tracking operations require explicit GDPR consent checks before execution. +Data collection operations are subject to available consent signals (TCF v2 format, GPP, GPC). Enforcement follows built-in per-jurisdiction rules, with publisher configuration tuning jurisdiction lists, signal interpretation, and conflict resolution. ## Data Flow -1. **Request Ingress** - Request arrives at Fastly edge -2. **Consent Validation** - GDPR consent checked -3. **ID Generation** - EC ID generated (if consented) -4. **Ad Request** - Backend ad server called -5. **Response Processing** - Creative processed and modified -6. **Response Egress** - Response sent to browser +1. **Request Ingress**: request arrives at Fastly edge +2. **Consent Signal Read**: any signals present on the request are decoded +3. **ID Generation**: EC ID generated when the consent evaluation permits +4. **Ad Request**: backend ad server called +5. **Response Processing**: creative processed and modified +6. **Response Egress**: response sent to browser ## Storage @@ -87,9 +87,9 @@ Used for: - Configuration cache - EC ID state -### No User Data Persistence +### Data Persistence -User data is not persisted in storage - only processed in-flight at the edge. +Page content and request bodies are processed in-flight and are not persisted. EC ID state and related metadata are stored in KV stores as configured. ## Performance Characteristics @@ -101,7 +101,7 @@ User data is not persisted in storage - only processed in-flight at the edge. ## Security - **HMAC-based IDs** - Cryptographically secure identifiers -- **No PII Storage** - Privacy by design +- **No Direct Identifiers Stored** - No name, email, or account fields are stored - **Request Signing** - Optional request authentication - **Content Security** - Creative scanning and modification diff --git a/docs/guide/auction-orchestration.md b/docs/guide/auction-orchestration.md index 3a55bc3de..86d6c73d9 100644 --- a/docs/guide/auction-orchestration.md +++ b/docs/guide/auction-orchestration.md @@ -161,7 +161,7 @@ sequenceDiagram rect rgb(239,246,255) Note over Client,Mock: Creative Rendering Client->>Client: Inject winning creative
Render iframe
Load creative through proxy - Note right of Client: iframe src="/first-party/proxy?tsurl=...&tstoken=sig"
Ensures first-party serving
Maintains privacy & security + Note right of Client: iframe src="/first-party/proxy?tsurl=...&tstoken=sig"
Ensures first-party serving
Applies signed URL validation deactivate Client end ``` @@ -553,7 +553,7 @@ EC identity is maintained with the `ts-ec` cookie; auction responses do not emit ## Creative Processing -Winning creatives are processed through a streaming HTML rewriter (`lol_html`) before being returned. This rewrites external resource URLs to first-party proxy paths, maintaining privacy and enabling security controls. +Winning creatives are processed through a streaming HTML rewriter (`lol_html`) before being returned. This rewrites external resource URLs to first-party proxy paths, applying signed URL validation and enabling security controls. **Elements rewritten:** diff --git a/docs/guide/collective-sync.md b/docs/guide/collective-sync.md index 4028062cc..6444c6f98 100644 --- a/docs/guide/collective-sync.md +++ b/docs/guide/collective-sync.md @@ -1,6 +1,6 @@ # Collective Sync Architecture -Trusted Server supports cross-publisher data sharing through a **Collective Sync** model. Publishers who share the same EC secret key can synchronize user data across their properties, enabling privacy-preserving audience insights without third-party cookies. +Trusted Server supports cross-publisher data sharing through a **Collective Sync** model. Publishers who share the same EC secret key can synchronize user data across their properties, enabling audience insights via a shared first-party identifier without cross-domain script execution. ## Overview @@ -191,8 +191,8 @@ The sync endpoint handles: ## Privacy Considerations -- **No PII**: EC IDs contain no personally identifiable information -- **Consent-gated**: Only users with GDPR consent are included +- **Identifier content**: EC IDs are one-way HMAC values with a random suffix and embed no readable personal fields +- **Consent-gated**: Inclusion follows the consent signals and policy the publisher configures - **Publisher control**: Each publisher controls what segments they share - **Audit trail**: Object Store maintains full history of data sources @@ -210,4 +210,4 @@ The sync endpoint handles: - [Edge Cookies](/guide/edge-cookies) - Understand ID generation - [Configuration Reference](/guide/configuration) - Full config options -- [GDPR Compliance](/guide/gdpr-compliance) - Privacy requirements +- [GDPR Compliance](/guide/gdpr-compliance) - Consent signal handling diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 41b996fad..406033140 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -291,7 +291,7 @@ Changing `proxy_secret` invalidates all existing signed URLs. Plan rotations car ## EC Configuration -Settings for generating privacy-preserving Edge Cookie identifiers. The `ec_store` KV store is the only KV-backed EC lifecycle store; it holds identity graph state, minimal consent metadata, source-domain keyed partner UIDs, and withdrawal tombstones. Consent configuration controls request-local interpretation and forwarding, not separate KV persistence. +Settings for Edge Cookie identifier generation. The `ec_store` KV store is the only KV-backed EC lifecycle store. It holds identity graph state, minimal consent metadata, source-domain keyed partner UIDs, and withdrawal tombstones. Consent configuration controls request-local interpretation and forwarding, not separate KV persistence. ### `[ec]` @@ -386,7 +386,7 @@ Individual env var keys like `TRUSTED_SERVER__RESPONSE_HEADERS__X_CUSTOM_HEADER` **Use Cases**: -- Custom tracking headers +- Custom measurement headers - Cache control overrides - Debugging identifiers - CORS headers (if needed) @@ -1296,5 +1296,5 @@ cat trusted-server.toml | npx toml-cli validate - Set up [Request Signing](/guide/request-signing) for secure API calls - Configure [First-Party Proxy](/guide/first-party-proxy) for URL proxying -- Learn about [Edge Cookies](/guide/edge-cookies) for privacy-preserving identification +- Learn about [Edge Cookies](/guide/edge-cookies) for first-party state management - Review [Integrations](/guide/integrations-overview) for partner support diff --git a/docs/guide/creative-processing.md b/docs/guide/creative-processing.md index 7a8977a99..88524797d 100644 --- a/docs/guide/creative-processing.md +++ b/docs/guide/creative-processing.md @@ -1,16 +1,19 @@ # Creative Processing -Learn how Trusted Server automatically rewrites ad creative HTML and CSS to route all resources through first-party domains. +Trusted Server rewrites ad creative HTML and CSS to route resources +through first-party domains. ## Overview -Creative processing transforms third-party ad creatives by rewriting URLs to go through your first-party domain. This provides: +Creative processing rewrites URLs in third-party ad creatives so +resources are fetched through the publisher's first-party domain. +This provides: -- **Privacy Control** - All resources load through your domain -- **First-Party Context** - Cookies and storage use your domain -- **EC ID Integration** - Automatic ID forwarding to trackers -- **Security** - Validated, signed URLs prevent tampering -- **GDPR Compliance** - Controlled data sharing +- **First-party delivery**: all creative resource URLs route through the publisher's domain +- **First-party context**: cookies and storage use the publisher's domain +- **EC ID integration**: configurable ID forwarding to downstream endpoints +- **Tamper resistance**: validated, signed URLs prevent in-flight URL substitution +- **Configurable data sharing**: forwarding to vendors is per the deployer's configuration ## How It Works diff --git a/docs/guide/ec-setup-guide.md b/docs/guide/ec-setup-guide.md index afd962b6b..a11a352a8 100644 --- a/docs/guide/ec-setup-guide.md +++ b/docs/guide/ec-setup-guide.md @@ -15,7 +15,7 @@ This guide covers: - Trusted Server deployed and reachable (example: `https://getpurpose.ai`) - Access to update `trusted-server.toml` / deployment configuration - Fastly CLI authenticated (for store verification) -- A valid TCF consent string (`euconsent-v2`) for consent-required requests +- A valid TCF v2 format string (`euconsent-v2`) for consent-required requests ## 1) Required Configuration diff --git a/docs/guide/edge-cookies.md b/docs/guide/edge-cookies.md index dd9cbbd9d..2ba9f2638 100644 --- a/docs/guide/edge-cookies.md +++ b/docs/guide/edge-cookies.md @@ -1,24 +1,44 @@ # Edge Cookies (EC) -Trusted Server's EC module maintains user recognition across all browsers through first-party identifiers. +Trusted Server persists a stable per-device identifier in a first-party +cookie on the publisher's domain. The cookie name is `ts-ec`. Trusted +Server also surfaces the current EC ID on the `x-ts-ec` response +header, and strips that header when the consent evaluation does not +permit EC use. -## What are Edge Cookies? +## Policy Posture -Edge Cookies (EC) are privacy-safe identifiers generated on a first site visit using HMAC-based hashing that allow tracking with user consent while protecting user privacy. Trusted Server derives a deterministic HMAC base from the client IP address and appends a short random suffix to reduce collision risk. They are passed in requests on subsequent visits and activity. - -Trusted Server surfaces the current EC ID via response headers and a first-party cookie. For the exact header and cookie names, see the [API Reference](/guide/api-reference). +Trusted Server is technology. It is neutral on policy. The Edge Cookie +gives the deployer a cookie slot and configuration over the surrounding +attributes. The deployer determines the policy posture based on the +laws and contractual arrangements that apply to their deployment. +Privacy outcomes follow from that configuration, not from the cookie +mechanism itself. For full operational onboarding (partner configuration, batch sync, identify, and auction verification), use the [EC Setup Guide](/guide/ec-setup-guide). ## How They Work -### HMAC-Based Generation +EC IDs are generated on first request using HMAC-SHA256 over the +normalized client IP and a configured secret, with a short random +suffix appended. On subsequent requests the value is read from the +`ts-ec` cookie and reused. + +**Format:** `64-hex-hmac`.`6-alphanumeric-suffix` -EC IDs use HMAC (Hash-based Message Authentication Code) to generate a deterministic base from the client IP address, then append a short random suffix. +**IP normalization:** IPv4 addresses pass through unchanged. IPv6 +addresses are masked to the /64 prefix before hashing, so a device +that rotates its interface identifier under Privacy Extensions still +maps to a stable base. -**Format**: `64-hex-hmac`.`6-alphanumeric-suffix` +### Determinism and Stability -**IP normalization**: IPv6 addresses are normalized to a /64 prefix before hashing. +| Scenario | Result | +| ----------------------------------------------------------- | ---------------------------------------------------- | +| Same client IP, same secret, no cookie | Same 64-hex base; fresh suffix each mint. | +| Same client IP, same secret, existing cookie | Existing cookie value reused; no fresh mint. | +| Same client IP, different secret | Different 64-hex base. Useful for rotating identity. | +| Multiple clients behind shared NAT, same secret, no cookies | Same 64-hex base; the suffix distinguishes them. | ### Request Lifecycle @@ -71,7 +91,7 @@ When consent cannot be verified for the current request — for example, unknown ## Consent Model -EC creation is gated by jurisdiction. The server detects jurisdiction from geolocation data attached to the request and applies the appropriate consent framework. Live consent comes from request-local signals (`euconsent-v2`, `__gpp`, `__gpp_sid`, `us_privacy`, `Sec-GPC`) plus geolocation and policy defaults; there is no separate consent KV fallback. +EC creation is gated by jurisdiction. The server detects jurisdiction from geolocation data attached to the request and applies the corresponding consent rules. Live consent comes from request-local signals (`euconsent-v2`, `__gpp`, `__gpp_sid`, `us_privacy`, `Sec-GPC`) plus geolocation and policy defaults; there is no separate consent KV fallback. ```mermaid flowchart TD @@ -232,20 +252,34 @@ Server-resolved EIDs and current-request Prebid EIDs are deduplicated by `source ## Configuration -Configure EC settings in `trusted-server.toml`. See the full [Configuration Reference](/guide/configuration) for the `[ec]` section and environment variable overrides. +Configure EC settings in the `[ec]` section of `trusted-server.toml`. See the [Configuration Reference](/guide/configuration) for the full surface and environment variable overrides. + +The shipped configuration carries a local-development passphrase, and +known placeholder values are rejected at startup with a settings load +error, because an HMAC computed with a known secret can be forged by +anyone who knows it. Replace the development passphrase before +running outside local development. + +## What Goes in the Cookie -## Privacy Considerations +The EC value is the deterministic HMAC base plus a random suffix. It +contains no name, email, account identifier, or other field supplied +by the user. The value is written back as `Set-Cookie` only when the +consent evaluation permits EC creation for the detected jurisdiction. +See [GDPR Compliance](/guide/gdpr-compliance) for how signals are +interpreted. -- EC IDs combine a deterministic HMAC base derived from the client IP with a random suffix for uniqueness. The cookie is only set when storage consent is present -- No personally identifiable information (PII) is stored in the ID -- The hash input is the client IP address only -- IDs can be rotated by changing the secret key +The value passes a base64url-compatible allowlist. The cookie envelope +sets `Path=/`, `Secure`, `HttpOnly`, `SameSite=Lax`, and a `Max-Age`. +`Domain` is computed as `.{publisher.domain}`. The separate +`cookie_domain` setting applies only to non-EC cookies. -## Best Practices +## Operational Notes -1. Always verify GDPR consent before generating IDs -2. Rotate secret keys periodically -3. Monitor ID collision rates +- Rotate the secret periodically. Rotation produces a new 64-hex base + for subsequent mints. +- Watch the logs for cookie value rejections, which happen when a + `ts-ec` cookie value carries characters outside the allowlist. ## Runtime Behavior Notes @@ -259,6 +293,7 @@ Configure EC settings in `trusted-server.toml`. See the full [Configuration Refe ## Next Steps - Follow the [EC Setup Guide](/guide/ec-setup-guide) -- Learn about [GDPR Compliance](/guide/gdpr-compliance) +- [Configuration Reference](/guide/configuration) +- [GDPR Compliance](/guide/gdpr-compliance) for consent signal handling - Configure [Ad Serving](/guide/ad-serving) -- Learn about [Collective Sync](/guide/collective-sync) for cross-publisher data sharing details and diagrams +- [Collective Sync](/guide/collective-sync) for cross-publisher data sharing details and diagrams diff --git a/docs/guide/first-party-proxy.md b/docs/guide/first-party-proxy.md index 8e479b953..b58a82bdb 100644 --- a/docs/guide/first-party-proxy.md +++ b/docs/guide/first-party-proxy.md @@ -1,12 +1,12 @@ # First-Party Proxy -Learn how Trusted Server proxies third-party assets through first-party domains to improve privacy, security, and ad performance. +Learn how Trusted Server proxies third-party assets through first-party domains to give the deployer control over routing, security, and ad performance. ## Overview The First-Party Proxy system rewrites third-party URLs in ad creatives to route through your domain, providing: -- **Privacy Protection** - No direct third-party cookies or tracking +- **First-Party Routing** - Rewritten asset requests route through your domain - **EC ID Forwarding** - Controlled identity propagation - **Creative Rewrites** - Automatic HTML/CSS URL transformation - **Click Tracking** - First-party click redirects @@ -387,7 +387,7 @@ EC IDs are re-applied on **every redirect hop**: → 200 response ``` -This ensures downstream trackers receive consistent IDs even through redirect chains. +This ensures downstream endpoints receive consistent IDs even through redirect chains. ### Click ID Forwarding @@ -404,10 +404,10 @@ User clicks → redirect includes ID: Location: https://advertiser.com?ts-ec=user123 ``` -::: tip Privacy Control +::: tip Forwarding Conditions EC IDs are only forwarded when: -1. User has given GDPR consent (if required) +1. Consent rules permitted the EC ID to be issued (issuance is gated per jurisdiction) 2. ID exists in request (header/cookie) 3. Integration hasn't disabled forwarding (`forward_ec_id: false`) ::: @@ -665,7 +665,7 @@ Trusted Server: **Automatic Protection**: -- HTML/CSS rewriting removes malicious URLs +- HTML/CSS rewriting routes URLs through signed first-party endpoints - Data URIs are skipped (`data:`, `javascript:`, `blob:`) - Protocol validation (only `http://` and `https://`) diff --git a/docs/guide/gdpr-compliance.md b/docs/guide/gdpr-compliance.md index 5897066f9..236711e73 100644 --- a/docs/guide/gdpr-compliance.md +++ b/docs/guide/gdpr-compliance.md @@ -1,32 +1,53 @@ # GDPR Compliance -Understanding GDPR compliance and consent management in Trusted Server. +Consent signal handling in Trusted Server. ## Overview -Trusted Server enforces GDPR compliance at the edge, ensuring all tracking and data collection activities require explicit user consent. +Trusted Server reads consent signals from each request, decodes them, +and applies built-in enforcement rules to consent-gated activities +such as EC creation and EID forwarding. The publisher configures how +signals are interpreted: which countries and US states map to each +jurisdiction's rules, how Global Privacy Control is read, how +conflicting signals are resolved, and when stored signals expire. +The per-activity gates and their fail-closed defaults are built in. + +## Policy Posture + +Trusted Server is technology. It is neutral on policy. Deployers +operate under different laws and policies, and each decides how to +configure the consent surface for their deployment. Trusted Server's +role is to provide the controls and respect them at request time. ## Consent Management ### Consent Validation -All requests are validated for proper GDPR consent before any tracking occurs: +Signals are read from the request and evaluated by built-in +per-activity gates. ```rust // Placeholder example -if !validate_gdpr_consent(&request) { - return reject_tracking(); +if !validate_consent(&request, &policy) { + return reject_activity(); } ``` ### Consent Sources -Trusted Server supports multiple consent frameworks: +Trusted Server can interoperate with multiple consent signal formats: -- TCF (Transparency & Consent Framework) -- Custom consent signals +- TCF v2 format (the IAB Transparency and Consent Framework encoded string) +- Global Privacy Platform (GPP) +- US Privacy String +- Global Privacy Control (GPC) request header +- Publisher-defined custom signals - First-party consent cookies +References to _TCF v2 format_ on this page refer to the encoded string +schema. The Transparency and Consent Framework as a policy framework +is one option a deployer can elect. It is not the assumed default. + ## Implementation ### Checking Consent @@ -41,58 +62,81 @@ const hasConsent = await trustedServer.checkConsent({ ### Consent Storage -Consent signals are: - -- Validated on every request -- Not persisted without explicit consent -- Respected across all operations +- Signals are read from the request on every transaction. +- A minimal consent snapshot is stored as EC entry metadata in the KV + identity graph. Request-time interpretation always uses the live + request signals. +- Signals are passed through to integrations the publisher has + configured to receive them. ## Privacy Controls ### User Rights -Trusted Server supports: - -- Right to access -- Right to erasure -- Right to data portability -- Right to object +Where the publisher's regime grants user rights (for example GDPR's +access, erasure, portability, objection), Trusted Server provides the +hooks the publisher uses to honor them at the edge. The shape depends +on the regime and the publisher's implementation. ### Data Minimization -Only essential data is collected: +Trusted Server collects only what the publisher has configured: -- EC IDs (with consent) -- Minimal request metadata -- No PII storage +- EC IDs (subject to the publisher's policy) +- Request metadata used by configured integrations +- No name, email, or account identifier fields supplied by the user ## Configuration -Configure GDPR settings in `trusted-server.toml`: +Configure consent handling in the `[consent]` section of +`trusted-server.toml`. The block below is illustrative. See the +[Configuration Reference](/guide/configuration) for the full surface. ```toml -[gdpr] -require_consent = true -tcf_version = "2.2" -default_action = "reject" +[consent] +mode = "interpreter" # or "proxy" (forward raw strings without decoding) +max_consent_age_days = 365 # expiration check for dated signals + +[consent.gdpr] +applies_in = ["DE", "FR"] # countries mapped to the GDPR rules + +[consent.us_states] +privacy_states = ["CA", "CO"] # US states mapped to the US state rules + +[consent.us_privacy_defaults] +gpc_implies_optout = true # how the Sec-GPC header is interpreted + +[consent.conflict_resolution] +mode = "restrictive" # or "newest" / "permissive" ``` -## Compliance Features +Each field tunes how signals are interpreted. The per-jurisdiction +gates and their fail-closed defaults are built in. + +## Operational Behavior -- Consent checks before ID generation -- Automatic rejection without consent -- Audit logging for compliance -- Regional enforcement rules +- Consent checks run before consent-gated activities (EC creation, + EID forwarding). +- Missing signals fail closed in regulated and unknown jurisdictions. + Resolution of conflicting signals is configurable (restrictive, + newest, or permissive). +- Audit logging records the consent decision per gated activity. +- Regional rules are applied per detected jurisdiction. ## Best Practices -1. Always require explicit consent -2. Respect user withdrawal of consent -3. Document consent mechanisms -4. Regular compliance audits -5. Keep consent records +1. Configure the consent surface to match the deployer's policy and + jurisdictional scope. +2. Document the mechanisms used so users, partners, and regulators can + see what is in effect. +3. Honor withdrawal of consent in the same configuration that + captured it. +4. Review the configured policy periodically. +5. Retain consent records to the extent required by applicable law and + the deployer's own policy. ## Next Steps -- Configure [Ad Serving](/guide/ad-serving) -- Review [Architecture](/guide/architecture) +- [Configuration Reference](/guide/configuration) +- [Edge Cookies](/guide/edge-cookies) +- [Architecture](/guide/architecture) diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index be43315f0..6852b25ce 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -60,7 +60,7 @@ Edit `trusted-server.toml` to configure: - Ad server integrations - KV store mappings - EC configuration -- GDPR settings +- Consent settings (`[gdpr]`) See [Configuration](/guide/configuration) for details. diff --git a/docs/guide/integration-guide.md b/docs/guide/integration-guide.md index 79576b8bd..8a2cb1f67 100644 --- a/docs/guide/integration-guide.md +++ b/docs/guide/integration-guide.md @@ -212,7 +212,7 @@ impl IntegrationScriptRewriter for MyIntegration { `html_processor.rs` calls these hooks after applying the standard origin→first-party rewrite, so you can simply swap URLs, append query parameters, or mutate inline JSON. Use this to point `