Commit 4f55104
authored
Fix Sourcepoint first-party consent: iframe asset 404s + same-origin message guard (#822)
* 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.
* Rewrite root-absolute asset paths in proxied Sourcepoint iframe HTML
The privacy-manager iframe documents (e.g. us_pm/index.html) reference their
assets with root-absolute paths: `<script src="/PrivacyManagerUS.<hash>.js">`,
`/polyfills.<hash>.js`, `/manifest.json`. On cdn.privacy-mgmt.com those resolve
to the CDN; served first-party through Trusted Server the iframe origin is the
publisher, so `/PrivacyManagerUS.<hash>.js` resolves to the publisher root and
404s — the privacy-manager UI cannot load its code.
Add an HTML response rewrite (mirroring the existing JavaScript rewrite path)
that prefixes root-absolute `src`/`href` values with /integrations/sourcepoint/cdn
so the iframe assets load through the proxy. Protocol-relative (`//host`) and
absolute (`https://…`) URLs are left untouched. The proxy now also requests
identity encoding for likely-HTML paths so the body is readable, and a shared
finalize_rewritten_response helper sets the right content type for each.
Verified in a browser through the proxy: PrivacyManagerUS.*/polyfills.* go from
404 to 200 and the privacy-manager app renders.
* Accept same-origin messages in Sourcepoint wrapper guard
With assets loading, the consent dialog still never appeared and the page
stayed scroll-locked. Root cause: the wrapper validates messages from its own
message/privacy-manager iframe with
`e.origin === params.msgOrigin || e.origin === params.pmOrigin`, where
`msgOrigin` is `baseEndpoint` used verbatim. Under first-party proxying
`baseEndpoint` is a path (`/integrations/sourcepoint/cdn`), so `msgOrigin`
becomes `https://<publisher>/integrations/sourcepoint/cdn` — which never equals
the iframe's bare origin `https://<publisher>`. The guard rejects the iframe's
`sp.showMessage`/choice messages, so the wrapper adds `html.sp-message-open`
(locking scroll) but never shows the dialog or removes the lock: the page
renders but cannot scroll, behind an invisible consent gate.
An absolute `baseEndpoint` can't fix this — `msgOrigin` keeps the path prefix
and still never equals the bare origin — and serving Sourcepoint under a bare
origin would require a dedicated subdomain or claiming colliding root paths.
Since the message iframe is genuinely same-origin when proxied first-party, add
a third script rewrite that lets the guard also accept
`e.origin === location.origin`. This only additionally trusts a same-origin
frame (which already has full page access), so it adds no attack surface; it
adapts an origin check written for a cross-origin CDN to first-party serving.
The rewrite is anchored on the semantic `.pmOrigin)` guard close and captures
the minified event identifier.
Verified in a browser through the proxy: the consent dialog renders,
"Agree & Continue" dismisses it, `sp-message-open` is removed, and the page
scrolls.
* Add tests covering arbitrary minified identifiers in the origin guard rewrite
* Address PR review: HTML cache policy and standalone msgOrigin guard
Split rewritten-response finalization so HTML iframe documents no longer
inherit the versioned-JS public cache policy. Rewritten JavaScript bundles
keep the fixed `public, max-age=<ttl>` policy (static, hashed filenames);
rewritten HTML (e.g. `us_pm/index.html`) is unversioned, so it now defers to
`apply_cache_headers`, which preserves an upstream `Cache-Control` when present
and otherwise applies the cookie-aware default (private when cookies were
forwarded).
Generalize the message-origin guard rewrite to also match a standalone
`<event>.origin===<obj>.msgOrigin)` guard (the
`wrapperMessagingWithoutDetection.js` shape, which compares only against the
single baseEndpoint-derived `msgOrigin`), in addition to the combined
`…msgOrigin || …pmOrigin)` form. Both anchors are Sourcepoint-specific field
names, so generic origin checks are untouched and the combined form is still
rewritten exactly once.
Add tests for HTML cache preservation/privacy, the standalone msgOrigin guard,
and the no-double-match invariant on the combined guard.1 parent bc54caa commit 4f55104
1 file changed
Lines changed: 416 additions & 23 deletions
0 commit comments