Context
PR #956 removes allow-same-origin from CREATIVE_SANDBOX_TOKENS, so creatives rendered via srcdoc run in an opaque origin. Any fetch from that context is cross-origin with Origin: null; the JSON POST to /first-party/sign preflights and fails, so signProxyUrl now bails out early (returns null) when window.origin === 'null'.
The consequence is that the dynamic-src guard's runtime proxying is disabled inside sandboxed creative iframes: runtime-injected <img>/<iframe> elements fall back to loading their original third-party URLs directly (dynamic_src_guard.ts). The sandbox still isolates them from the publisher origin, but the first-party proxying/privacy benefit is lost for dynamically inserted resources.
Proposed design
A narrowly validated same-origin parent postMessage broker:
- The parent-page tsjs core registers a
message listener that accepts sign requests only from iframes it created (validate event.source against tracked creative iframe contentWindows, never trust event.origin — it is null for these frames).
- Request payload is a strict shape (
{type, id, url}); the parent performs the same-origin POST /first-party/sign itself and posts back {type, id, href} to the requesting frame.
- The creative runtime's
signProxyUrl uses the broker when window.origin === 'null', with a timeout falling back to the current unsigned behavior.
- A real-browser regression test covering a mutated rewritten click and a dynamic resource sign round-trip from a sandboxed
srcdoc iframe.
References
Additional scope: CORS-mode subresources
PR #956 briefly added Access-Control-Allow-Origin: * to /first-party/proxy responses so that ES modules, crossorigin fonts, and fetch/XHR could load inside the opaque creative frame. That was reverted: /first-party/proxy is a generic signed fetcher — it forwards the EC ID and curated client-derived headers, follows redirects, and runs in open mode when proxy.allowed_domains is empty. With sanitize_creatives disabled, the rewriter mints signed URLs from bidder-controlled markup, so a readable response would let a creative read arbitrary proxied bodies (a readable bidder-controlled proxy). The signature proves only that this service minted the URL, not that the target is safe to expose.
The fix belongs in the same constrained capability as the signing broker:
- A separate asset route/capability rather than the generic proxy.
- Destination allowlisting, with private/reserved-address rejection on every redirect hop.
- No EC forwarding and no client-derived header forwarding.
- Only then a scoped CORS policy for opaque origins.
- Regression coverage for opaque-frame exfiltration attempts against both routes.
Until this lands, CORS-mode subresources do not load inside sandboxed creative iframes; ordinary <img>/<script src>/stylesheet loads are unaffected.
Context
PR #956 removes
allow-same-originfromCREATIVE_SANDBOX_TOKENS, so creatives rendered viasrcdocrun in an opaque origin. Anyfetchfrom that context is cross-origin withOrigin: null; the JSON POST to/first-party/signpreflights and fails, sosignProxyUrlnow bails out early (returnsnull) whenwindow.origin === 'null'.The consequence is that the dynamic-src guard's runtime proxying is disabled inside sandboxed creative iframes: runtime-injected
<img>/<iframe>elements fall back to loading their original third-party URLs directly (dynamic_src_guard.ts). The sandbox still isolates them from the publisher origin, but the first-party proxying/privacy benefit is lost for dynamically inserted resources.Proposed design
A narrowly validated same-origin parent postMessage broker:
messagelistener that accepts sign requests only from iframes it created (validateevent.sourceagainst tracked creative iframecontentWindows, never trustevent.origin— it isnullfor these frames).{type, id, url}); the parent performs the same-originPOST /first-party/signitself and posts back{type, id, href}to the requesting frame.signProxyUrluses the broker whenwindow.origin === 'null', with a timeout falling back to the current unsigned behavior.srcdociframe.References
proxy_sign.ts(opaque-origin CORS) andrender.ts(sandbox tokens).Additional scope: CORS-mode subresources
PR #956 briefly added
Access-Control-Allow-Origin: *to/first-party/proxyresponses so that ES modules,crossoriginfonts, andfetch/XHR could load inside the opaque creative frame. That was reverted:/first-party/proxyis a generic signed fetcher — it forwards the EC ID and curated client-derived headers, follows redirects, and runs in open mode whenproxy.allowed_domainsis empty. Withsanitize_creativesdisabled, the rewriter mints signed URLs from bidder-controlled markup, so a readable response would let a creative read arbitrary proxied bodies (a readable bidder-controlled proxy). The signature proves only that this service minted the URL, not that the target is safe to expose.The fix belongs in the same constrained capability as the signing broker:
Until this lands, CORS-mode subresources do not load inside sandboxed creative iframes; ordinary
<img>/<script src>/stylesheet loads are unaffected.