Skip to content

Commit b08256f

Browse files
committed
Make first-party integration URLs root-relative
The GPT, DataDome, Permutive, Lockr, and Sourcepoint attribute rewriters rewrote a publisher's `<script src>` / `<link href>` to an absolute first-party URL built from `request_host`, e.g. `https://{request_host}/integrations/gpt/script`. When the host that reaches the program isn't the page's host — behind a host-rewriting dev proxy, or any deployment where `request_host` resolves to the routing host rather than the production domain — those URLs leak the wrong host into the page (e.g. `https://ts.example.com/integrations/...` while the address bar shows `www.example.com`). Emit them root-relative (`/integrations/...`) instead, so the browser resolves them against the page's own host. This is correct both in production and behind a proxy, removes the dependency on `request_host`/`X-Forwarded-Host` derivation, and matches what GTM, Didomi, Testlight, and these integrations' own inline-script rewriters already do. Audited every integration: these five were the only page-facing first-party URLs built from a host. NextJS RSC/flight payload rewriting stays absolute (those URLs are intentional). The DataDome protection module's raw-`Host` read is server-side (validation API body), not page-facing, and is left as-is. Tests updated to assert the root-relative form.
1 parent 5447648 commit b08256f

5 files changed

Lines changed: 29 additions & 60 deletions

File tree

crates/trusted-server-core/src/integrations/datadome.rs

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -803,7 +803,7 @@ impl IntegrationAttributeRewriter for DataDomeIntegration {
803803
&self,
804804
_attr_name: &str,
805805
attr_value: &str,
806-
ctx: &IntegrationAttributeContext<'_>,
806+
_ctx: &IntegrationAttributeContext<'_>,
807807
) -> AttributeRewriteAction {
808808
// Check if this is a DataDome script URL
809809
let is_datadome =
@@ -814,10 +814,8 @@ impl IntegrationAttributeRewriter for DataDomeIntegration {
814814
}
815815

816816
let path = Self::extract_datadome_path(attr_value);
817-
let new_url = format!(
818-
"{}://{}/integrations/datadome{}",
819-
ctx.request_scheme, ctx.request_host, path
820-
);
817+
// Root-relative so the browser resolves it against the page host.
818+
let new_url = format!("/integrations/datadome{path}");
821819

822820
log::info!(
823821
"[datadome] Rewriting script src from {} to {}",
@@ -1328,10 +1326,7 @@ mod tests {
13281326
let action = integration.rewrite("src", "https://js.datadome.co/tags.js", &ctx);
13291327
match action {
13301328
AttributeRewriteAction::Replace(new_url) => {
1331-
assert_eq!(
1332-
new_url,
1333-
"https://publisher.com/integrations/datadome/tags.js"
1334-
);
1329+
assert_eq!(new_url, "/integrations/datadome/tags.js");
13351330
}
13361331
_ => panic!("Expected Replace action"),
13371332
}
@@ -1340,10 +1335,7 @@ mod tests {
13401335
let action = integration.rewrite("href", "https://js.datadome.co/tags.js", &ctx);
13411336
match action {
13421337
AttributeRewriteAction::Replace(new_url) => {
1343-
assert_eq!(
1344-
new_url,
1345-
"https://publisher.com/integrations/datadome/tags.js"
1346-
);
1338+
assert_eq!(new_url, "/integrations/datadome/tags.js");
13471339
}
13481340
_ => panic!("Expected Replace action for href"),
13491341
}
@@ -1368,10 +1360,7 @@ mod tests {
13681360
let action = integration.rewrite("src", "https://js.datadome.co/js/check", &ctx);
13691361
match action {
13701362
AttributeRewriteAction::Replace(new_url) => {
1371-
assert_eq!(
1372-
new_url,
1373-
"https://publisher.com/integrations/datadome/js/check"
1374-
);
1363+
assert_eq!(new_url, "/integrations/datadome/js/check");
13751364
}
13761365
_ => panic!("Expected Replace action"),
13771366
}
@@ -1380,10 +1369,7 @@ mod tests {
13801369
let action = integration.rewrite("href", "//js.datadome.co/js/signal", &ctx);
13811370
match action {
13821371
AttributeRewriteAction::Replace(new_url) => {
1383-
assert_eq!(
1384-
new_url,
1385-
"https://publisher.com/integrations/datadome/js/signal"
1386-
);
1372+
assert_eq!(new_url, "/integrations/datadome/js/signal");
13871373
}
13881374
_ => panic!("Expected Replace action for protocol-relative URL"),
13891375
}
@@ -1392,10 +1378,7 @@ mod tests {
13921378
let action = integration.rewrite("src", "https://js.datadome.co", &ctx);
13931379
match action {
13941380
AttributeRewriteAction::Replace(new_url) => {
1395-
assert_eq!(
1396-
new_url,
1397-
"https://publisher.com/integrations/datadome/tags.js"
1398-
);
1381+
assert_eq!(new_url, "/integrations/datadome/tags.js");
13991382
}
14001383
_ => panic!("Expected Replace action for bare domain"),
14011384
}

crates/trusted-server-core/src/integrations/gpt.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -435,17 +435,15 @@ impl IntegrationAttributeRewriter for GptIntegration {
435435
&self,
436436
_attr_name: &str,
437437
attr_value: &str,
438-
ctx: &IntegrationAttributeContext<'_>,
438+
_ctx: &IntegrationAttributeContext<'_>,
439439
) -> AttributeRewriteAction {
440440
if !self.config.rewrite_script {
441441
return AttributeRewriteAction::keep();
442442
}
443443

444444
if Self::is_gpt_script_url(attr_value) {
445-
AttributeRewriteAction::replace(format!(
446-
"{}://{}/integrations/gpt/script",
447-
ctx.request_scheme, ctx.request_host
448-
))
445+
// Root-relative so the browser resolves it against the page host.
446+
AttributeRewriteAction::replace("/integrations/gpt/script".to_string())
449447
} else {
450448
AttributeRewriteAction::keep()
451449
}
@@ -587,8 +585,8 @@ mod tests {
587585
match result {
588586
AttributeRewriteAction::Replace(url) => {
589587
assert_eq!(
590-
url, "https://edge.example.com/integrations/gpt/script",
591-
"should rewrite to first-party script endpoint"
588+
url, "/integrations/gpt/script",
589+
"should rewrite to root-relative first-party script endpoint"
592590
);
593591
}
594592
other => panic!("Expected Replace action, got {:?}", other),

crates/trusted-server-core/src/integrations/lockr.rs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -412,17 +412,15 @@ impl IntegrationAttributeRewriter for LockrIntegration {
412412
&self,
413413
_attr_name: &str,
414414
attr_value: &str,
415-
ctx: &IntegrationAttributeContext<'_>,
415+
_ctx: &IntegrationAttributeContext<'_>,
416416
) -> AttributeRewriteAction {
417417
if !self.config.rewrite_sdk {
418418
return AttributeRewriteAction::Keep;
419419
}
420420

421421
if self.is_lockr_sdk_url(attr_value) {
422-
let replacement = format!(
423-
"{}://{}/integrations/lockr/sdk",
424-
ctx.request_scheme, ctx.request_host
425-
);
422+
// Root-relative so the browser resolves it against the page host.
423+
let replacement = "/integrations/lockr/sdk".to_string();
426424
log::debug!("Rewriting Lockr SDK URL to {}", replacement);
427425
AttributeRewriteAction::Replace(replacement)
428426
} else {
@@ -532,10 +530,8 @@ mod tests {
532530

533531
assert_eq!(
534532
result,
535-
AttributeRewriteAction::Replace(
536-
"https://edge.example.com/integrations/lockr/sdk".to_string()
537-
),
538-
"should rewrite Lockr SDK URL to first-party proxy"
533+
AttributeRewriteAction::Replace("/integrations/lockr/sdk".to_string()),
534+
"should rewrite Lockr SDK URL to root-relative first-party proxy"
539535
);
540536
}
541537

crates/trusted-server-core/src/integrations/permutive.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -425,18 +425,16 @@ impl IntegrationAttributeRewriter for PermutiveIntegration {
425425
&self,
426426
_attr_name: &str,
427427
attr_value: &str,
428-
ctx: &IntegrationAttributeContext<'_>,
428+
_ctx: &IntegrationAttributeContext<'_>,
429429
) -> AttributeRewriteAction {
430430
if !self.config.rewrite_sdk {
431431
return AttributeRewriteAction::keep();
432432
}
433433

434434
if self.is_permutive_sdk_url(attr_value) {
435-
// Rewrite to first-party SDK endpoint
436-
AttributeRewriteAction::replace(format!(
437-
"{}://{}/integrations/permutive/sdk",
438-
ctx.request_scheme, ctx.request_host
439-
))
435+
// Rewrite to first-party SDK endpoint.
436+
// Root-relative so the browser resolves it against the page host.
437+
AttributeRewriteAction::replace("/integrations/permutive/sdk".to_string())
440438
} else {
441439
AttributeRewriteAction::keep()
442440
}
@@ -547,7 +545,7 @@ mod tests {
547545

548546
assert!(matches!(rewritten, AttributeRewriteAction::Replace(_)));
549547
if let AttributeRewriteAction::Replace(url) = rewritten {
550-
assert_eq!(url, "https://edge.example.com/integrations/permutive/sdk");
548+
assert_eq!(url, "/integrations/permutive/sdk");
551549
}
552550
}
553551

crates/trusted-server-core/src/integrations/sourcepoint.rs

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -285,11 +285,7 @@ impl SourcepointIntegration {
285285
Ok(target.to_string())
286286
}
287287

288-
fn build_first_party_url(
289-
&self,
290-
source_url: &str,
291-
ctx: &IntegrationAttributeContext<'_>,
292-
) -> Option<String> {
288+
fn build_first_party_url(&self, source_url: &str) -> Option<String> {
293289
let parsed = parse_sourcepoint_url(source_url)?;
294290
if parsed.host_str()? != SOURCEPOINT_CDN_HOST {
295291
return None;
@@ -301,10 +297,8 @@ impl SourcepointIntegration {
301297
.map(|value| format!("?{value}"))
302298
.unwrap_or_default();
303299

304-
Some(format!(
305-
"{}://{}{}{}{}",
306-
ctx.request_scheme, ctx.request_host, SOURCEPOINT_CDN_PREFIX, path, query
307-
))
300+
// Root-relative so the browser resolves it against the page host.
301+
Some(format!("{SOURCEPOINT_CDN_PREFIX}{path}{query}"))
308302
}
309303

310304
fn copy_headers(
@@ -851,11 +845,11 @@ impl IntegrationAttributeRewriter for SourcepointIntegration {
851845
&self,
852846
_attr_name: &str,
853847
attr_value: &str,
854-
ctx: &IntegrationAttributeContext<'_>,
848+
_ctx: &IntegrationAttributeContext<'_>,
855849
) -> AttributeRewriteAction {
856850
// `handles_attribute()` already gates on `rewrite_sdk`, so this
857851
// method is only called when rewriting is enabled.
858-
if let Some(rewritten) = self.build_first_party_url(attr_value, ctx) {
852+
if let Some(rewritten) = self.build_first_party_url(attr_value) {
859853
return AttributeRewriteAction::replace(rewritten);
860854
}
861855

@@ -986,7 +980,7 @@ mod tests {
986980
assert_eq!(
987981
rewritten,
988982
AttributeRewriteAction::replace(
989-
"https://edge.example.com/integrations/sourcepoint/cdn/mms/v2/get_site_data?account_id=821",
983+
"/integrations/sourcepoint/cdn/mms/v2/get_site_data?account_id=821",
990984
)
991985
);
992986
}

0 commit comments

Comments
 (0)