Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Security

- Didomi SDK requests now derive `X-Geo-Country`, `X-Geo-Region`, and `CloudFront-Viewer-Country` only from trusted platform geo (#1151). Caller-supplied `FastlyGeo-CountryCode` and `FastlyGeo-Region` headers previously let callers choose the geography sent to Didomi. **Breaking** for deployments that injected those headers: configure CDN-fronted client IP instead. Without a complete trusted country/region pair (currently always on Cloudflare, Axum, and Spin), the geo header group is omitted. Noncanonical loaders and SDK POSTs bypass the outbound cache and return `no-store, private`; other SDK GETs retain Didomi's cache policy. Purge cached SDK entries before rollout. Geo-less loaders risk Didomi throttling and disablement; production Fastly deployments should validate geo coverage and enable `geo_query_parameters = true`. Other adapters currently have no compliant loader-caching mode through this proxy; use direct SDK delivery instead. Audit-generated configurations leave detected Didomi disabled for manual review.
Comment thread
prk-Jr marked this conversation as resolved.
Resolved
- `ts prebid client` now keeps the config file's mode bits when it writes `external_bundle_sha256` and `external_bundle_sri`; previously a `0600` config was reset to the umask default (usually `0644`). Configs already rewritten by an earlier version keep the widened mode, so restrict them again (for example `chmod 600 trusted-server.toml`). The rewrite is fsynced and leaves no temporary copy behind on failure. TOML parse errors from `ts prebid client` and `ts audit generate` now name the file, line and column without quoting the offending config line (#1202).
- Publisher and page-bids processing now reject malformed effective request hosts with 400 before inserting request-derived bytes into rewrite output. `/first-party/sign` also returns 400 for a malformed effective host when the request URI has no scheme and request metadata is needed to determine it. Host selection remains `Forwarded` (`host=`), then `X-Forwarded-Host`, then `Host`; a malformed or explicitly empty selected host is rejected without fallback. Valid local hostnames, underscore labels, ports, and bracketed IPv6 remain supported. Forwarded-header trust policy is unchanged (#1206, #1207, #1208).
- `/first-party/sign` now rejects valid targets outside `proxy.allowed_domains` before minting a proxy token. The creative runtime keeps image and iframe assignments blocked after this `403` policy response instead of loading the rejected URL directly; fetch-time checks still cover the initial target and every redirect.
Expand Down
49 changes: 42 additions & 7 deletions crates/trusted-server-cli/src/commands/audit/generate/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -425,9 +425,6 @@ fn build_draft_config_with_generator(
if detected.contains("gpt") {
draft = replace_key_in_section(&draft, "integrations.gpt", "enabled", "enabled = true")?;
}
if detected.contains("didomi") {
draft = replace_key_in_section(&draft, "integrations.didomi", "enabled", "enabled = true")?;
}
if detected.contains("datadome") {
draft =
replace_key_in_section(&draft, "integrations.datadome", "enabled", "enabled = true")?;
Expand Down Expand Up @@ -457,10 +454,7 @@ fn build_draft_config_with_generator(
}

for integration in detected {
if !matches!(
integration,
"gpt" | "didomi" | "datadome" | "google_tag_manager"
) {
if !matches!(integration, "gpt" | "datadome" | "google_tag_manager") {
manual_review.push(integration);
}
}
Expand Down Expand Up @@ -2663,6 +2657,47 @@ mod tests {
toml::from_str::<toml::Value>(&draft).expect("draft should parse as TOML");
}

#[test]
fn build_draft_config_requires_manual_review_for_didomi() {
let url = Url::parse("https://publisher.example.com").expect("should parse URL");
let artifact = AuditArtifact {
audited_url: url.to_string(),
page_title: None,
js_asset_count: 0,
third_party_asset_count: 0,
detected_integrations: ["didomi", "gpt", "datadome"]
.into_iter()
.map(|id| DetectedIntegration {
id: id.to_string(),
evidence: format!("detected {id} in page"),
})
.collect(),
assets: Vec::new(),
warnings: Vec::new(),
};

let draft = build_draft_config(&url, &artifact, &gpt_slots::DiscoveredSlots::default())
.expect("should build draft config");
let config = toml::from_str::<toml::Value>(&draft).expect("should parse draft config");

assert_eq!(
config["integrations"]["didomi"]["enabled"].as_bool(),
Some(false),
"should keep Didomi disabled until its deployment requirements are reviewed"
);
assert!(
draft.contains("# - Detected didomi; review the corresponding [integrations.didomi] section before enabling it."),
"should flag detected Didomi for manual review"
);
for integration in ["gpt", "datadome"] {
assert_eq!(
config["integrations"][integration]["enabled"].as_bool(),
Some(true),
"should preserve automatic enablement for {integration}"
);
}
}

#[test]
fn build_draft_config_extracts_gtm_container_id_from_url_evidence() {
let url = Url::parse("https://publisher.example.com").expect("should parse URL");
Expand Down
Loading
Loading