CVE-2026-20665
Overview
Background
- Policy container
- A per-document bundle of security context (Content Security Policy, referrer policy, cross-origin settings) that WebKit inherits or restores when a document is created.
- Content Security Policy (CSP)
- A policy delivered via header or meta tag that restricts which origins scripts, styles, and other resources may load from, mitigating injection attacks.
- srcdoc iframe
- An iframe whose document is generated from an inline HTML string rather than fetched over the network, and which must inherit its security context (including CSP) from the embedding/parent document.
- HistoryItem policy container
- A cached copy of a document’s policy container stored on a session-history entry so the exact prior security context can be restored during back/forward navigation.
- NavigationType::BackForward
- The navigation type flag indicating a history traversal (back/forward), the only case in which restoring a document’s security context from a stored HistoryItem is correct.
Root Cause Analysis
The patch modifies DocumentWriter::begin, which is responsible for initializing a new Document’s security state as a load commits.
Before the fix, the code unconditionally inherited the Document’s policy container (which carries the Content Security Policy, referrer policy, and related security context) from currentHistoryItem whenever that history item existed and had a stored policy container: ‘if (currentHistoryItem && currentHistoryItem->policyContainer())’.
The invariant that was violated is that a policy container should only be resurrected from a HistoryItem when the navigation is actually a history traversal (Back/Forward), where restoring the exact prior security context is correct. For all other navigation types the new document must derive its policy container from the correct live source (for srcdoc iframes and other locally-generated documents, from the parent/initiator document). Because the guard did not check the navigation type, an iframe that happened to have a stale or attacker-influenced currentHistoryItem could have its CSP overwritten by whatever policy container was cached in that history item, rather than inheriting the parent frame’s restrictive CSP.
The included layout test demonstrates the concrete failure: an iframe with sandbox=‘allow-scripts’ whose srcdoc is assigned dynamically (rather than present as an initial attribute) would end up NOT enforcing the parent’s ‘script-src self’ policy, so a dynamic import() of a cross-origin module that should be blocked was instead allowed to load.
The fix restores the invariant by gating the history-based inheritance on ’triggeringAction && triggeringAction->type() == NavigationType::BackForward’, so history policy containers are only reapplied during genuine back/forward navigations; every other case falls through to the normal initiator-based inheritance and the parent CSP is correctly propagated into the srcdoc document.
The updated WPT expectation file (inheritance-from-initiator) flipping four FAIL results to PASS confirms the general bug: policy/context inheritance was being taken from the wrong source (history ‘p’ instead of the initiator ‘a’/‘b’) for location changes, window.open, and form submissions.
Attack Path
- Serve an attacker page with a restrictive CSP The top document sets ‘Content-Security-Policy: default-src self; script-src self …’ (as in the test), which should be inherited by locally-generated child documents such as srcdoc iframes.
- Create a srcdoc iframe without an initial srcdoc attribute Insert an <iframe sandbox=‘allow-scripts’> that has no srcdoc attribute at parse time, so its associated history item / initial empty-document security state is established before any srcdoc content exists.
- Assign srcdoc dynamically after load Set iframe.srcdoc = ‘<script>…import(…)</script>’ from script. This triggers a new document load in the frame that goes through DocumentWriter::begin with a currentHistoryItem that carries a cached policy container but is NOT a BackForward navigation.
- Cause the wrong policy container to be inherited The pre-patch unconditional branch copies the history item’s policy container into the new srcdoc document, discarding the parent’s CSP so the srcdoc document runs with a missing or permissive policy.
- Execute content the parent CSP forbids Inside the srcdoc document, perform an action the parent policy should block — e.g. dynamic import() of a cross-origin module (module-pass.py) or injection of inline styles/scripts. With CSP not enforced, the load succeeds, defeating the site’s script-src restriction.
- Leverage the bypass Use the now-unrestricted execution context to load attacker-controlled script/modules that the deploying site intended CSP to block, undermining injection defenses (e.g. turning an otherwise-contained HTML injection into script execution).
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
DocumentWriter::beginSource/WebCore/loader/DocumentWriter.cpp |
modified | Added a guard so a policy container is inherited from currentHistoryItem only when triggeringAction is a NavigationType::BackForward navigation; otherwise normal initiator-based policy-container inheritance applies, preserving the parent frame's CSP for dynamically-set srcdoc documents. |
Files Changed
LayoutTests/http/tests/security/contentSecurityPolicy/iframe-srcdoc-import-bypass-expected.txtLayoutTests/http/tests/security/contentSecurityPolicy/iframe-srcdoc-import-bypass.htmlLayoutTests/http/tests/security/contentSecurityPolicy/resources/module-pass.pyLayoutTests/imported/w3c/web-platform-tests/content-security-policy/inheritance/inheritance-from-initiator.sub-expected.txtSource/WebCore/loader/DocumentWriter.cpp
Audit Directions
- Other policy-container inheritance branches in DocumentWriterIn DocumentWriter.cpp and DocumentLoader, audit every call to inheritPolicyContainerFrom and every read of currentHistoryItem()->policyContainer() to confirm each is guarded by the correct navigation type; grep for ‘policyContainer’, ‘inheritPolicyContainerFrom’, and ‘currentHistoryItem’.
- Initial-empty-document and about:blank/about:srcdoc contextsCheck that documents created for about:blank, about:srcdoc, javascript: URLs, and synchronously-committed loads derive CSP/policy from the initiator rather than any cached history state; grep for ‘isSrcdocDocument’, ‘aboutSrcDoc’, ‘shouldInheritSecurityOriginFromOwner’.
- Navigation-type gating consistency across security inheritanceSearch for other conditionals that restore per-history security state (referrer policy, sandbox flags, COOP/COEP) without checking NavigationType::BackForward; grep ‘NavigationType::BackForward’, ’triggeringAction’, and history-item restore paths to find analogous missing guards.