Skip to content

Fix Sourcepoint first-party consent: iframe asset 404s + same-origin message guard#822

Open
aram356 wants to merge 3 commits into
fix-integration-relative-urlsfrom
fix-sourcepoint-publicpath-404
Open

Fix Sourcepoint first-party consent: iframe asset 404s + same-origin message guard#822
aram356 wants to merge 3 commits into
fix-integration-relative-urlsfrom
fix-sourcepoint-publicpath-404

Conversation

@aram356

@aram356 aram356 commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Closes #821

Stacked on #819 — base branch is fix-integration-relative-urls. Review/merge after #819 (or rebase onto main once #819 lands). (Branch name is legacy — the fix is HTML asset paths + the message-origin guard, not webpack publicPath.)

Makes Sourcepoint's CMP actually work through Trusted Server's first-party proxy: the consent dialog renders and the page scrolls. Two distinct first-party-proxy breakages, both verified in a real browser through the proxy.

1. Privacy-manager iframe assets 404

The privacy-manager iframe documents (us_pm/index.html) reference assets root-absolute: <script src="/PrivacyManagerUS.<hash>.js">, /polyfills.<hash>.js, /manifest.json. On cdn.privacy-mgmt.com those hit the CDN; served first-party the iframe origin is the publisher, so they resolve to the publisher root → 404, and the privacy-manager UI can't load.

Fix: an HTML response rewrite that prefixes root-absolute src/href with /integrations/sourcepoint/cdn (protocol-relative/absolute URLs untouched). Verified 404 → 200.

2. Consent dialog never shows / page stays scroll-locked

Even with assets at 200, the dialog stayed hidden and <body> stayed position:fixed. The wrapper validates messages from its own iframe with e.origin === params.msgOrigin || e.origin === params.pmOrigin, where msgOrigin is baseEndpoint used verbatim. First-party, baseEndpoint is a path (/integrations/sourcepoint/cdn), so msgOrigin = https://<publisher>/integrations/sourcepoint/cdn — which never equals the iframe's bare origin https://<publisher>. So the wrapper adds html.sp-message-open (locks scroll) but never shows the dialog or removes the lock.

Verified dead ends: an absolute baseEndpoint keeps the path → still no match; a bare-origin baseEndpoint needs a dedicated subdomain (not available) or claiming colliding site-root paths (/manifest.json, /polyfills.*).

Fix: since the iframe is genuinely same-origin when proxied first-party, a third script rewrite 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. Anchored on the semantic .pmOrigin) close, capturing the minified event identifier; it only adds an OR branch (existing checks untouched).

Verification (in-browser, through the proxy)

  • Assets PrivacyManagerUS.* / polyfills.* → 200.
  • Consent dialog ("PRIVACY AND LEGAL TERMS / Agree & Continue") renders.
  • Clicking Agree & Continuesp-message-open removed → body back to staticpage scrolls (verified scrollTop moves, was stuck at 0).

Tests

rewrites_root_absolute_asset_paths_in_html, html_rewrite_preserves_absolute_and_protocol_relative_urls, is_likely_html_path_matches_iframe_documents, rewrites_message_origin_guard_to_accept_same_origin, message_origin_guard_rewrite_handles_minified_identifiers, message_origin_guard_rewrite_leaves_unrelated_origin_checks_untouched. All 48 Sourcepoint tests pass; fmt/clippy/check clean.

Note on fragility

Both rewrites pattern-match Sourcepoint's minified JS/HTML (as the pre-existing CDN-URL and /unified/ rewrites already do), so a Sourcepoint recompile could require updating an anchor. Anchored on semantic tokens (src/href, .pmOrigin) to minimise that.

@aram356 aram356 assigned aram356 and unassigned aram356 Jun 25, 2026
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.
@aram356 aram356 force-pushed the fix-sourcepoint-publicpath-404 branch from 521359a to 297dc93 Compare June 25, 2026 08:18
@aram356 aram356 changed the title Fix Sourcepoint scroll lock: rewrite empty webpack publicPath to first-party prefix Fix Sourcepoint privacy-manager 404: rewrite root-absolute asset paths in proxied iframe HTML Jun 25, 2026
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.
@aram356 aram356 changed the title Fix Sourcepoint privacy-manager 404: rewrite root-absolute asset paths in proxied iframe HTML Fix Sourcepoint first-party consent: iframe asset 404s + same-origin message guard Jun 25, 2026
@aram356 aram356 self-assigned this Jun 25, 2026
@aram356 aram356 marked this pull request as draft June 25, 2026 17:05
@aram356 aram356 marked this pull request as ready for review June 25, 2026 17:14

@ChristianPavilonis ChristianPavilonis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

Approved. The change is narrowly scoped and the reported CI checks are passing. I left two non-blocking findings inline: both are worth addressing, but I do not think they need to block this PR if you prefer to follow up separately.

/// for a cross-origin CDN about first-party serving. The match is anchored on
/// the semantic `.pmOrigin)` close of the guard; group 1 is the (possibly
/// minified) event identifier and group 2 the `…pmOrigin` operand.
static SP_MESSAGE_ORIGIN_GUARD_PATTERN: LazyLock<Regex> = LazyLock::new(|| {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: same-origin guard rewrite may miss the documented/current wrapper guard shape

This rewrite only matches guards that end in .pmOrigin). The documented Sourcepoint wrapper path (/wrapperMessagingWithoutDetection.js) currently has a different handler shape (if (<event>.origin === <origin-var>)), where the origin variable is derived from baseEndpoint. Under this proxy, that value can be /integrations/sourcepoint/cdn, while MessageEvent.origin is the bare publisher origin, so the consent-message postMessages can still be rejected for that wrapper.

Suggested fix: add a second narrowly anchored rewrite for the legacy/current if (<event>.origin === <origin-var>) handler, ideally anchored near Sourcepoint-specific message-handler tokens, and cover it with a realistic wrapper snippet test.

);
}

fn rewrite_html_response(&self, response: &mut Response<EdgeBody>, rewritten: String) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: rewritten HTML responses now inherit the JS public-cache policy

rewrite_html_response() uses the shared finalizer, which overwrites non-Set-Cookie responses with Cache-Control: public, max-age=<ttl>. That policy was appropriate for rewritten versioned JS/static assets, but the new HTML rewrite targets unversioned documents like /us_pm/index.html; it also bypasses the existing forwarded_cookies-aware apply_cache_headers path.

Suggested fix: split JS and HTML finalization/cache policy. For rewritten HTML, preserve upstream Cache-Control when present; if absent, apply the existing apply_cache_headers(response, forwarded_cookies) behavior. Add tests for preserving no-store and for private caching when cookies were forwarded.

@prk-Jr prk-Jr left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sourcepoint privacy-manager iframe assets 404: root-absolute paths not rewritten

3 participants