← All issues

[1] CSP bypass in sandboxed srcdoc iframes

Severity: High | Component: WebCore loader / DocumentWriter | b4390e8

Rated High because the observable effect is a script-injection / CSP-bypass primitive in any document with a sandboxed srcdoc iframe, the analyst's confidence is 0.92, and four pre-existing WPT failures (location change, window.open, two form submissions) provide commit-backed evidence that the same single-line condition was responsible for a wider family of mis-attributions.

Source/WebCore/loader/DocumentWriter.cpp

- if (currentHistoryItem && currentHistoryItem->policyContainer()) {
+ if (triggeringAction && triggeringAction->type() == NavigationType::BackForward && currentHistoryItem && currentHistoryItem->policyContainer()) {
const auto& policyContainerFromHistory = currentHistoryItem->policyContainer();
ASSERT(policyContainerFromHistory);
document->inheritPolicyContainerFrom(*policyContainerFromHistory);

LayoutTests/http/tests/security/contentSecurityPolicy/iframe-srcdoc-import-bypass.html

+ iframe1.srcdoc = srcdocContent;
+ iframe2.srcdoc = srcdocContent.replaceAll(iframe1.id, iframe2.id);
+<iframe id="iframe1" sandbox="allow-scripts"></iframe>
+<iframe id="iframe2" sandbox="allow-scripts" srcdoc=""></iframe>

DocumentWriter::begin() previously inherited the new document's PolicyContainer from the current HistoryItem whenever that item carried a non-null policy container. The fix narrows the condition: history-based inheritance only triggers when a triggeringAction is present and its NavigationType is BackForward. All other navigation forms — including dynamic srcdoc mutation on a sandboxed iframe — fall through to the spec-mandated initiator-based inheritance. The WPT inheritance-from-initiator expectation file flips four FAIL→PASS results (location change, window.open, two form submission variants), and a new layout test demonstrates a sandboxed iframe loading a cross-origin module via dynamic import() after a script-driven srcdoc assignment.

Wrong policy-container source selected for non-back/forward navigations: history-item inheritance applied where initiator inheritance was required.

A PolicyContainer is the WebCore aggregate that holds a document's CSP, referrer policy, and cross-origin policies. The HTML spec says srcdoc documents — having no network response of their own — must inherit their policy container from their initiator (the document that triggered the navigation). For history traversal, by contrast, the document's HistoryItem carries a snapshot of the original policy container so that back/forward restores the document with its original policy. DocumentWriter::begin() runs early in new-document construction, before any script in the new document executes, and is responsible for selecting which container the new document adopts.

Before the fix, the loader took the simplest possible heuristic: "if there is a history policy container, use it." For a back/forward traversal that's the spec; for a fresh navigation triggered by a script-driven srcdoc mutation, it isn't — the iframe's stored history container does not contain the parent's CSP. So the new document inherited a permissive (or absent) policy where the spec demanded the strict CSP of the parent that just wrote srcdoc.

🔒

The general mechanics of CSP-inheritance for sandboxed srcdoc iframes are walked through, with attention to which navigation forms previously consulted the wrong policy source and what that meant for an attacker holding only an HTML-injection primitive.

Subscribe to read more

🔒

Several reusable audit patterns covering policy-container inheritance across multiple loader entry points and local-scheme document constructions, with concrete grep starting points.

Subscribe to read more