diff --git a/crates/common/README.md b/crates/common/README.md index 35bb7850..eb8500f5 100644 --- a/crates/common/README.md +++ b/crates/common/README.md @@ -49,10 +49,10 @@ Behavior is covered by an extensive test suite in `crates/common/src/creative.rs ## Synthetic Identifier Propagation -- `synthetic.rs` generates a deterministic synthetic identifier per user request and exposes helpers: - - `generate_synthetic_id` — creates a fresh HMAC-based ID using request signals. - - `get_synthetic_id` — extracts an existing ID from the `x-psid-ts` header or `synthetic_id` cookie. +- `synthetic.rs` generates a synthetic identifier per user request and exposes helpers: + - `generate_synthetic_id` — creates a fresh HMAC-based ID using request signals and appends a short random suffix (format: `64hex.6alnum`). + - `get_synthetic_id` — extracts an existing ID from the `x-synthetic-id` header or `synthetic_id` cookie. - `get_or_generate_synthetic_id` — reuses the existing ID when present, otherwise creates one. -- `publisher.rs::handle_publisher_request` stamps proxied origin responses with `X-Synthetic-Fresh`, `x-psid-ts`, and (when absent) issues the `synthetic_id` cookie so the browser keeps the identifier on subsequent requests. +- `publisher.rs::handle_publisher_request` stamps proxied origin responses with `x-synthetic-id`, and (when absent) issues the `synthetic_id` cookie so the browser keeps the identifier on subsequent requests. - `proxy.rs::handle_first_party_proxy` replays the identifier to third-party creative origins by appending `synthetic_id=` to the reconstructed target URL, follows redirects (301/302/303/307/308) up to four hops, and keeps downstream fetches linked to the same user scope. - `proxy.rs::handle_first_party_click` adds `synthetic_id=` to outbound click redirect URLs so analytics endpoints can associate clicks with impressions without third-party cookies. diff --git a/crates/common/src/constants.rs b/crates/common/src/constants.rs index 775ed113..4ccb77f9 100644 --- a/crates/common/src/constants.rs +++ b/crates/common/src/constants.rs @@ -1,9 +1,9 @@ use http::header::HeaderName; -pub const HEADER_SYNTHETIC_FRESH: HeaderName = HeaderName::from_static("x-synthetic-fresh"); -pub const HEADER_SYNTHETIC_PUB_USER_ID: HeaderName = HeaderName::from_static("x-pub-user-id"); +pub const COOKIE_SYNTHETIC_ID: &str = "synthetic_id"; + pub const HEADER_X_PUB_USER_ID: HeaderName = HeaderName::from_static("x-pub-user-id"); -pub const HEADER_SYNTHETIC_TRUSTED_SERVER: HeaderName = HeaderName::from_static("x-psid-ts"); +pub const HEADER_X_SYNTHETIC_ID: HeaderName = HeaderName::from_static("x-synthetic-id"); pub const HEADER_X_CONSENT_ADVERTISING: HeaderName = HeaderName::from_static("x-consent-advertising"); pub const HEADER_X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for"); diff --git a/crates/common/src/cookies.rs b/crates/common/src/cookies.rs index de78b109..88216bd5 100644 --- a/crates/common/src/cookies.rs +++ b/crates/common/src/cookies.rs @@ -8,6 +8,7 @@ use error_stack::{Report, ResultExt}; use fastly::http::header; use fastly::Request; +use crate::constants::COOKIE_SYNTHETIC_ID; use crate::error::TrustedServerError; use crate::settings::Settings; @@ -64,8 +65,8 @@ pub fn handle_request_cookies( /// for storing the synthetic ID. pub fn create_synthetic_cookie(settings: &Settings, synthetic_id: &str) -> String { format!( - "synthetic_id={}; Domain={}; Path=/; Secure; SameSite=Lax; Max-Age={}", - synthetic_id, settings.publisher.cookie_domain, COOKIE_MAX_AGE, + "{}={}; Domain={}; Path=/; Secure; SameSite=Lax; Max-Age={}", + COOKIE_SYNTHETIC_ID, synthetic_id, settings.publisher.cookie_domain, COOKIE_MAX_AGE, ) } @@ -157,8 +158,8 @@ mod tests { assert_eq!( result, format!( - "synthetic_id=12345; Domain={}; Path=/; Secure; SameSite=Lax; Max-Age={}", - settings.publisher.cookie_domain, COOKIE_MAX_AGE, + "{}=12345; Domain={}; Path=/; Secure; SameSite=Lax; Max-Age={}", + COOKIE_SYNTHETIC_ID, settings.publisher.cookie_domain, COOKIE_MAX_AGE, ) ); } diff --git a/crates/common/src/integrations/prebid.rs b/crates/common/src/integrations/prebid.rs index 692edae4..5fde6cb1 100644 --- a/crates/common/src/integrations/prebid.rs +++ b/crates/common/src/integrations/prebid.rs @@ -12,7 +12,7 @@ use url::Url; use validator::Validate; use crate::backend::ensure_backend_from_url; -use crate::constants::{HEADER_SYNTHETIC_FRESH, HEADER_SYNTHETIC_TRUSTED_SERVER}; +use crate::constants::HEADER_X_SYNTHETIC_ID; use crate::creative; use crate::error::TrustedServerError; use crate::geo::GeoInfo; @@ -23,7 +23,7 @@ use crate::integrations::{ use crate::openrtb::{Banner, Format, Imp, ImpExt, OpenRtbRequest, PrebidImpExt, Site}; use crate::request_signing::RequestSigner; use crate::settings::{IntegrationConfig, Settings}; -use crate::synthetic::{generate_synthetic_id, get_or_generate_synthetic_id}; +use crate::synthetic::get_or_generate_synthetic_id; const PREBID_INTEGRATION_ID: &str = "prebid"; const ROUTE_FIRST_PARTY_AD: &str = "/first-party/ad"; @@ -445,21 +445,10 @@ async fn handle_prebid_auction( )?; let synthetic_id = get_or_generate_synthetic_id(settings, &req)?; - let fresh_id = generate_synthetic_id(settings, &req)?; - log::info!( - "Using synthetic ID: {}, fresh ID: {}", - synthetic_id, - fresh_id - ); + log::info!("Using synthetic_id: {}", synthetic_id); - enhance_openrtb_request( - &mut openrtb_request, - &synthetic_id, - &fresh_id, - settings, - &req, - )?; + enhance_openrtb_request(&mut openrtb_request, &synthetic_id, settings, &req)?; let mut pbs_req = Request::new( Method::POST, @@ -497,9 +486,7 @@ async fn handle_prebid_auction( Ok(Response::from_status(StatusCode::OK) .with_header(header::CONTENT_TYPE, "application/json") - .with_header("X-Synthetic-ID", &synthetic_id) - .with_header(HEADER_SYNTHETIC_FRESH, &fresh_id) - .with_header(HEADER_SYNTHETIC_TRUSTED_SERVER, &synthetic_id) + .with_header(HEADER_X_SYNTHETIC_ID, &synthetic_id) .with_body(transformed_body)) } Err(_) => Ok(Response::from_status(pbs_response.get_status()) @@ -514,7 +501,6 @@ async fn handle_prebid_auction( fn enhance_openrtb_request( request: &mut Json, synthetic_id: &str, - fresh_id: &str, settings: &Settings, req: &Request, ) -> Result<(), Report> { @@ -526,7 +512,6 @@ fn enhance_openrtb_request( if !request["user"]["ext"].is_object() { request["user"]["ext"] = json!({}); } - request["user"]["ext"]["synthetic_fresh"] = json!(fresh_id); if req.get_header("Sec-GPC").is_some() { if !request["regs"].is_object() { @@ -884,15 +869,13 @@ mod tests { }); let synthetic_id = "synthetic-123"; - let fresh_id = "fresh-456"; let mut req = Request::new(Method::POST, "https://edge.example/third-party/ad"); req.set_header("Sec-GPC", "1"); - enhance_openrtb_request(&mut request_json, synthetic_id, fresh_id, &settings, &req) + enhance_openrtb_request(&mut request_json, synthetic_id, &settings, &req) .expect("should enhance request"); assert_eq!(request_json["user"]["id"], synthetic_id); - assert_eq!(request_json["user"]["ext"]["synthetic_fresh"], fresh_id); assert_eq!( request_json["regs"]["ext"]["us_privacy"], "1YYN", "GPC header should map to US privacy flag" diff --git a/crates/common/src/integrations/testlight.rs b/crates/common/src/integrations/testlight.rs index 29ecb599..58509283 100644 --- a/crates/common/src/integrations/testlight.rs +++ b/crates/common/src/integrations/testlight.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; use validator::Validate; -use crate::constants::{HEADER_SYNTHETIC_FRESH, HEADER_SYNTHETIC_TRUSTED_SERVER}; +use crate::constants::HEADER_X_SYNTHETIC_ID; use crate::error::TrustedServerError; use crate::integrations::{ AttributeRewriteAction, IntegrationAttributeContext, IntegrationAttributeRewriter, @@ -16,7 +16,7 @@ use crate::integrations::{ }; use crate::proxy::{proxy_request, ProxyRequestConfig}; use crate::settings::{IntegrationConfig, Settings}; -use crate::synthetic::{generate_synthetic_id, get_or_generate_synthetic_id}; +use crate::synthetic::get_or_generate_synthetic_id; use crate::tsjs; const TESTLIGHT_INTEGRATION_ID: &str = "testlight"; @@ -142,8 +142,6 @@ impl IntegrationProxy for TestlightIntegration { let synthetic_id = get_or_generate_synthetic_id(settings, &req) .change_context(Self::error("Failed to fetch or mint synthetic ID"))?; - let fresh_id = generate_synthetic_id(settings, &req) - .change_context(Self::error("Failed to mint fresh synthetic ID"))?; payload.user.id = Some(synthetic_id.clone()); @@ -177,8 +175,7 @@ impl IntegrationProxy for TestlightIntegration { } } - response.set_header(HEADER_SYNTHETIC_TRUSTED_SERVER, &synthetic_id); - response.set_header(HEADER_SYNTHETIC_FRESH, &fresh_id); + response.set_header(HEADER_X_SYNTHETIC_ID, &synthetic_id); Ok(response) } } @@ -221,7 +218,7 @@ fn default_shim_src() -> String { } fn default_enabled() -> bool { - true + false } impl Default for TestlightRequestBody { diff --git a/crates/common/src/proxy.rs b/crates/common/src/proxy.rs index be878bb2..35b10ec4 100644 --- a/crates/common/src/proxy.rs +++ b/crates/common/src/proxy.rs @@ -1177,10 +1177,7 @@ mod tests { sig ), ); - req.set_header( - crate::constants::HEADER_SYNTHETIC_TRUSTED_SERVER, - "synthetic-123", - ); + req.set_header(crate::constants::HEADER_X_SYNTHETIC_ID, "synthetic-123"); let resp = handle_first_party_click(&settings, req) .await diff --git a/crates/common/src/publisher.rs b/crates/common/src/publisher.rs index 5d3b8a08..bf7045f1 100644 --- a/crates/common/src/publisher.rs +++ b/crates/common/src/publisher.rs @@ -5,7 +5,7 @@ use fastly::{Body, Request, Response}; use crate::backend::ensure_backend_from_url; use crate::http_util::serve_static_with_etag; -use crate::constants::{HEADER_SYNTHETIC_TRUSTED_SERVER, HEADER_X_COMPRESS_HINT}; +use crate::constants::{COOKIE_SYNTHETIC_ID, HEADER_X_COMPRESS_HINT, HEADER_X_SYNTHETIC_ID}; use crate::cookies::create_synthetic_cookie; use crate::error::TrustedServerError; use crate::integrations::IntegrationRegistry; @@ -264,9 +264,11 @@ pub fn handle_publisher_request( .get_header(header::COOKIE) .and_then(|h| h.to_str().ok()) .map(|cookies| { - cookies - .split(';') - .any(|cookie| cookie.trim_start().starts_with("synthetic_id=")) + cookies.split(';').any(|cookie| { + cookie + .trim_start() + .starts_with(&format!("{}=", COOKIE_SYNTHETIC_ID)) + }) }) .unwrap_or(false); @@ -367,7 +369,7 @@ pub fn handle_publisher_request( ); } - response.set_header(HEADER_SYNTHETIC_TRUSTED_SERVER, synthetic_id.as_str()); + response.set_header(HEADER_X_SYNTHETIC_ID, synthetic_id.as_str()); if !has_synthetic_cookie { response.set_header( header::SET_COOKIE, diff --git a/crates/common/src/synthetic.rs b/crates/common/src/synthetic.rs index d84a95ef..dfd288b1 100644 --- a/crates/common/src/synthetic.rs +++ b/crates/common/src/synthetic.rs @@ -3,25 +3,62 @@ //! This module provides functionality for generating privacy-preserving synthetic IDs //! based on various request parameters and a secret key. +use std::net::IpAddr; + use error_stack::{Report, ResultExt}; use fastly::http::header; use fastly::Request; use handlebars::Handlebars; use hmac::{Hmac, Mac}; +use rand::Rng; use serde_json::json; use sha2::Sha256; +use uuid::Uuid; -use crate::constants::{HEADER_SYNTHETIC_PUB_USER_ID, HEADER_SYNTHETIC_TRUSTED_SERVER}; +use crate::constants::{COOKIE_SYNTHETIC_ID, HEADER_X_SYNTHETIC_ID}; use crate::cookies::handle_request_cookies; use crate::error::TrustedServerError; use crate::settings::Settings; type HmacSha256 = Hmac; +const ALPHANUMERIC_CHARSET: &[u8] = + b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + +/// Normalizes an IP address for stable synthetic ID generation. +/// +/// For IPv6 addresses, masks to /64 prefix to handle Privacy Extensions +/// where devices rotate their interface identifier (lower 64 bits). +/// IPv4 addresses are returned unchanged. +fn normalize_ip(ip: IpAddr) -> String { + match ip { + IpAddr::V4(ipv4) => ipv4.to_string(), + IpAddr::V6(ipv6) => { + let segments = ipv6.segments(); + // Keep only the first 4 segments (64 bits) for /64 prefix + format!( + "{:x}:{:x}:{:x}:{:x}::", + segments[0], segments[1], segments[2], segments[3] + ) + } + } +} + +/// Generates a random alphanumeric string of the specified length. +fn generate_random_suffix(length: usize) -> String { + let mut rng = rand::thread_rng(); + (0..length) + .map(|_| { + let idx = rng.gen_range(0..ALPHANUMERIC_CHARSET.len()); + ALPHANUMERIC_CHARSET[idx] as char + }) + .collect() +} + /// Generates a fresh synthetic ID based on request parameters. /// -/// Creates a deterministic ID using HMAC-SHA256 with the configured secret key -/// and various request attributes including IP, user agent, cookies, and headers. +/// Creates an HMAC-SHA256-based ID using the configured secret key and request +/// attributes, then appends a random suffix for additional uniqueness. /// /// # Errors /// @@ -31,33 +68,26 @@ pub fn generate_synthetic_id( settings: &Settings, req: &Request, ) -> Result> { + let client_ip = req.get_client_ip_addr().map(normalize_ip); let user_agent = req .get_header(header::USER_AGENT) .map(|h| h.to_str().unwrap_or("unknown")); - let first_party_id = handle_request_cookies(req).ok().flatten().and_then(|jar| { - jar.get("pub_userid") - .map(|cookie| cookie.value().to_string()) - }); - let auth_user_id = req - .get_header(HEADER_SYNTHETIC_PUB_USER_ID) - .map(|h| h.to_str().unwrap_or("anonymous")); - let publisher_domain = req - .get_header(header::HOST) - .map(|h| h.to_str().unwrap_or("unknown")); - let client_ip = req.get_client_ip_addr().map(|ip| ip.to_string()); let accept_language = req .get_header(header::ACCEPT_LANGUAGE) .and_then(|h| h.to_str().ok()) .map(|lang| lang.split(',').next().unwrap_or("unknown")); + let accept_encoding = req + .get_header(header::ACCEPT_ENCODING) + .and_then(|h| h.to_str().ok()); + let random_uuid = Uuid::new_v4().to_string(); let handlebars = Handlebars::new(); let data = &json!({ "client_ip": client_ip.unwrap_or("unknown".to_string()), "user_agent": user_agent.unwrap_or("unknown"), - "first_party_id": first_party_id.unwrap_or("anonymous".to_string()), - "auth_user_id": auth_user_id.unwrap_or("anonymous"), - "publisher_domain": publisher_domain.unwrap_or("unknown.com"), - "accept_language": accept_language.unwrap_or("unknown") + "accept_language": accept_language.unwrap_or("unknown"), + "accept_encoding": accept_encoding.unwrap_or("unknown"), + "random_uuid": random_uuid }); let input_string = handlebars @@ -73,17 +103,21 @@ pub fn generate_synthetic_id( message: "Failed to create HMAC instance".to_string(), })?; mac.update(input_string.as_bytes()); - let fresh_id = hex::encode(mac.finalize().into_bytes()); + let hmac_hash = hex::encode(mac.finalize().into_bytes()); + + // Append random 6-character alphanumeric suffix for additional uniqueness + let random_suffix = generate_random_suffix(6); + let synthetic_id = format!("{}.{}", hmac_hash, random_suffix); - log::info!("Generated fresh ID: {}", fresh_id); + log::info!("Generated fresh ID: {}", synthetic_id); - Ok(fresh_id) + Ok(synthetic_id) } /// Gets or creates a synthetic ID from the request. /// /// Attempts to retrieve an existing synthetic ID from: -/// 1. The `x-psid-ts` header +/// 1. The `x-synthetic-id` header /// 2. The `synthetic_id` cookie /// /// If neither exists, generates a new synthetic ID. @@ -94,7 +128,7 @@ pub fn generate_synthetic_id( /// - [`TrustedServerError::SyntheticId`] if ID generation fails pub fn get_synthetic_id(req: &Request) -> Result, Report> { if let Some(synthetic_id) = req - .get_header(HEADER_SYNTHETIC_TRUSTED_SERVER) + .get_header(HEADER_X_SYNTHETIC_ID) .and_then(|h| h.to_str().ok()) { let id = synthetic_id.to_string(); @@ -104,7 +138,7 @@ pub fn get_synthetic_id(req: &Request) -> Result, Report { - if let Some(cookie) = jar.get("synthetic_id") { + if let Some(cookie) = jar.get(COOKIE_SYNTHETIC_ID) { let id = cookie.value().to_string(); log::info!("Using existing Trusted Server ID from cookie: {}", id); return Ok(Some(id)); @@ -127,22 +161,49 @@ pub fn get_or_generate_synthetic_id( } // If no existing Synthetic ID found, generate a fresh one - let fresh_id = generate_synthetic_id(settings, req)?; - log::info!( - "No existing Synthetic ID found, using fresh ID: {}", - fresh_id - ); - Ok(fresh_id) + let synthetic_id = generate_synthetic_id(settings, req)?; + log::info!("No existing synthetic_id, generated: {}", synthetic_id); + Ok(synthetic_id) } #[cfg(test)] mod tests { use super::*; use fastly::http::{HeaderName, HeaderValue}; + use std::net::{Ipv4Addr, Ipv6Addr}; - use crate::constants::HEADER_X_PUB_USER_ID; use crate::test_support::tests::create_test_settings; + #[test] + fn test_normalize_ip_ipv4_unchanged() { + let ipv4 = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 100)); + assert_eq!(normalize_ip(ipv4), "192.168.1.100"); + } + + #[test] + fn test_normalize_ip_ipv6_masks_to_64() { + // Full IPv6 address with interface identifier + let ipv6 = IpAddr::V6(Ipv6Addr::new( + 0x2001, 0x0db8, 0x85a3, 0x0000, 0x8a2e, 0x0370, 0x7334, 0x1234, + )); + assert_eq!(normalize_ip(ipv6), "2001:db8:85a3:0::"); + } + + #[test] + fn test_normalize_ip_ipv6_different_suffix_same_prefix() { + // Two IPv6 addresses with same /64 prefix but different interface identifiers + // (simulating Privacy Extensions rotation) + let ipv6_a = IpAddr::V6(Ipv6Addr::new( + 0x2001, 0x0db8, 0xabcd, 0x0001, 0x1111, 0x2222, 0x3333, 0x4444, + )); + let ipv6_b = IpAddr::V6(Ipv6Addr::new( + 0x2001, 0x0db8, 0xabcd, 0x0001, 0xaaaa, 0xbbbb, 0xcccc, 0xdddd, + )); + // Both should normalize to the same /64 prefix + assert_eq!(normalize_ip(ipv6_a), normalize_ip(ipv6_b)); + assert_eq!(normalize_ip(ipv6_a), "2001:db8:abcd:1::"); + } + fn create_test_request(headers: Vec<(HeaderName, &str)>) -> Request { let mut req = Request::new("GET", "http://example.com"); for (key, value) in headers { @@ -155,33 +216,89 @@ mod tests { req } + fn is_synthetic_id_format(value: &str) -> bool { + let mut parts = value.split('.'); + let hmac_part = match parts.next() { + Some(part) => part, + None => return false, + }; + let suffix_part = match parts.next() { + Some(part) => part, + None => return false, + }; + if parts.next().is_some() { + return false; + } + if hmac_part.len() != 64 || suffix_part.len() != 6 { + return false; + } + if !hmac_part.chars().all(|c| c.is_ascii_hexdigit()) { + return false; + } + if !suffix_part.chars().all(|c| c.is_ascii_alphanumeric()) { + return false; + } + true + } + #[test] fn test_generate_synthetic_id() { let settings: Settings = create_test_settings(); let req = create_test_request(vec![ (header::USER_AGENT, "Mozilla/5.0"), - (header::COOKIE, "pub_userid=12345"), - (HEADER_X_PUB_USER_ID, "67890"), - (header::HOST, settings.publisher.domain.as_str()), (header::ACCEPT_LANGUAGE, "en-US,en;q=0.9"), + (header::ACCEPT_ENCODING, "gzip, deflate, br"), ]); let synthetic_id = generate_synthetic_id(&settings, &req).expect("should generate synthetic ID"); log::info!("Generated synthetic ID: {}", synthetic_id); - assert_eq!( - synthetic_id, - "a1748067b3908f2c9e0f6ea30a341328ba4b84de45448b13d1007030df14a98e" - ) + assert!( + is_synthetic_id_format(&synthetic_id), + "should match synthetic ID format" + ); + } + + #[test] + fn test_is_synthetic_id_format_accepts_valid_value() { + let value = format!("{}.{}", "a".repeat(64), "Ab12z9"); + assert!( + is_synthetic_id_format(&value), + "should accept a valid synthetic ID format" + ); + } + + #[test] + fn test_is_synthetic_id_format_rejects_invalid_values() { + let missing_suffix = "a".repeat(64); + assert!( + !is_synthetic_id_format(&missing_suffix), + "should reject missing suffix" + ); + + let invalid_hex = format!("{}.{}", "a".repeat(63) + "g", "Ab12z9"); + assert!( + !is_synthetic_id_format(&invalid_hex), + "should reject non-hex HMAC content" + ); + + let invalid_suffix = format!("{}.{}", "a".repeat(64), "ab-129"); + assert!( + !is_synthetic_id_format(&invalid_suffix), + "should reject non-alphanumeric suffix" + ); + + let extra_segment = format!("{}.{}.{}", "a".repeat(64), "Ab12z9", "zz"); + assert!( + !is_synthetic_id_format(&extra_segment), + "should reject extra segments" + ); } #[test] fn test_get_synthetic_id_with_header() { let settings = create_test_settings(); - let req = create_test_request(vec![( - HEADER_SYNTHETIC_TRUSTED_SERVER, - "existing_synthetic_id", - )]); + let req = create_test_request(vec![(HEADER_X_SYNTHETIC_ID, "existing_synthetic_id")]); let synthetic_id = get_synthetic_id(&req).expect("should get synthetic ID"); assert_eq!(synthetic_id, Some("existing_synthetic_id".to_string())); @@ -194,7 +311,10 @@ mod tests { #[test] fn test_get_synthetic_id_with_cookie() { let settings = create_test_settings(); - let req = create_test_request(vec![(header::COOKIE, "synthetic_id=existing_cookie_id")]); + let req = create_test_request(vec![( + header::COOKIE, + &format!("{}=existing_cookie_id", COOKIE_SYNTHETIC_ID), + )]); let synthetic_id = get_synthetic_id(&req).expect("should get synthetic ID"); assert_eq!(synthetic_id, Some("existing_cookie_id".to_string())); diff --git a/trusted-server.toml b/trusted-server.toml index 1abf398c..5a37211b 100644 --- a/trusted-server.toml +++ b/trusted-server.toml @@ -16,11 +16,10 @@ secret_key = "trusted-server" # Possible values # - "client_ip" # - "user_agent" -# - "first_party_id" -# - "auth_user_id" -# - "publisher_domain" # - "accept_language" -template = "{{ client_ip }}:{{ user_agent }}:{{ first_party_id }}:{{ auth_user_id }}:{{ publisher_domain }}:{{ accept_language }}" +# - "accept_encoding" +# - "random_uuid" +template = "{{ client_ip }}:{{ user_agent }}:{{ accept_language }}:{{ accept_encoding }}" # Custom headers to be included in every response [response_headers]