Medium CVSS 7.5 webkit Bypass 🔧 Commit mapped

Overview

Medium
Severity
7.5
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionProcessing maliciously crafted web content may prevent Content Security Policy from being enforced
ComponentWebCore Page
Bug ClassBypass
Tracker308906
Fix commitf8ed382fb244 (WebKit/WebKit) +66/-1
CWECWE-693
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
CISA KEVNot listed
CreditedCantina
Disclosed2026-05-11

Background

Content Security Policy (CSP)
A response-header mechanism restricting script/resource loading; multiple policies apply cumulatively, with the most restrictive winning.
CSP inheritance
Local-scheme documents (blob:, about:blank, srcdoc) inherit their creator’s CSP, propagated via response headers.
setHTTPHeaderField vs addHTTPHeaderField
set replaces any existing value for a header name; add appends, preserving earlier values.

Root Cause Analysis

This fixes a Content Security Policy enforcement bypass where only the LAST of multiple CSP headers was preserved when propagating policies onto a response. ContentSecurityPolicyResponseHeaders::addPolicyHeadersTo iterates a document’s CSP headers and writes them onto a ResourceResponse. The pre-patch code used response.setHTTPHeaderField(HTTPHeaderName::ContentSecurityPolicy, header.first) for enforced policies (and setHTTPHeaderField for report-only), but setHTTPHeaderField REPLACES any existing value for that header name. When a document carries multiple enforced CSP headers, each iteration overwrote the previous, so the response ended up with only the last policy. This matters for documents whose CSP is INHERITED from a creator — notably blob: URL iframes and other local-scheme frames, which inherit the creator’s CSP via these response headers. If the creator had two enforced policies (for example one that forbids inline scripts and one that allows them), dropping all but the last could discard the stricter policy, so the inherited document failed to enforce it and inline script that should be blocked would run.

The fix uses response.addHTTPHeaderField for both enforced and report-only CSP, appending each policy so ALL policies propagate and the most-restrictive-wins semantics are preserved.

The restored invariant is that a document with multiple CSP policies propagates every one to inheriting contexts. The added layout test serves two enforced CSP headers (one without and one with ‘unsafe-inline’) and verifies inline script inside a blob: iframe is blocked; the updated WPT expectation flips trusted-types blob inheritance from FAIL to PASS.

Key insight
Propagating CSP with setHTTPHeaderField overwrote earlier policies, so a document with multiple CSP headers passed only the last one to inheriting blob:/local-scheme frames; appending with addHTTPHeaderField preserves all of them.

Attack Path

  1. Serve multiple CSP headers An attacker-influenced creator document is delivered with two or more enforced CSP headers where one is stricter (blocks inline scripts).
  2. Create an inheriting frame Spawn a blob: URL (or other local-scheme) iframe that inherits the creator’s CSP via response headers.
  3. Drop all but the last policy setHTTPHeaderField overwrites, so the inherited document keeps only the last, weaker policy.
  4. Run blocked script Inline script that the stricter policy should block executes in the blob document, bypassing CSP.

Impact Assessment

A CSP enforcement bypass confined to the WebContent process with no memory corruption: inheriting documents (blob:/local-scheme frames) silently dropped all but the last CSP policy, weakening XSS mitigations and letting otherwise-blocked inline script run. Impact depends on the site’s reliance on multiple CSP headers, but it undermines a core defense-in-depth control.

Changed Functions

FunctionChangeNotes
ContentSecurityPolicyResponseHeaders::addPolicyHeadersTo
Source/WebCore/page/csp/ContentSecurityPolicyResponseHeaders.cpp
modified Uses addHTTPHeaderField (append) instead of setHTTPHeaderField (replace) for both Enforce and Report CSP headers, so multiple policies all propagate to inheriting documents.

Files Changed

  • LayoutTests/http/tests/security/contentSecurityPolicy/blob-url-inherits-multiple-csp-policies-expected.txt
  • LayoutTests/http/tests/security/contentSecurityPolicy/blob-url-inherits-multiple-csp-policies.html
  • LayoutTests/http/tests/security/contentSecurityPolicy/resources/create-blob-iframe.js
  • LayoutTests/http/tests/security/contentSecurityPolicy/resources/echo-multiple-csp-blob-iframe.py
  • LayoutTests/imported/w3c/web-platform-tests/trusted-types/inheriting-csp-for-local-schemes-expected.txt
  • Source/WebCore/page/csp/ContentSecurityPolicyResponseHeaders.cpp

Audit Directions

  • Same pattern: header propagation
    Grep for setHTTPHeaderField on headers that can legitimately appear multiple times (Content-Security-Policy, Content-Security-Policy-Report-Only) where add is required.
  • CSP inheritance paths
    Audit blob:/about:blank/srcdoc CSP inheritance to confirm the full policy list (not just one) is carried across the boundary.
diff --git a/LayoutTests/http/tests/security/contentSecurityPolicy/blob-url-inherits-multiple-csp-policies-expected.txt b/LayoutTests/http/tests/security/contentSecurityPolicy/blob-url-inherits-multiple-csp-policies-expected.txt
new file mode 100644
index 000000000000..228b9da58211
--- /dev/null
+++ b/LayoutTests/http/tests/security/contentSecurityPolicy/blob-url-inherits-multiple-csp-policies-expected.txt
@@ -0,0 +1,14 @@
+CONSOLE MESSAGE: Refused to execute a script because its hash, its nonce, or 'unsafe-inline' does not appear in the script-src directive of the Content Security Policy.
+Tests that a blob: URL iframe inherits ALL CSP policies from its creator document, not just the last one. When the creator has two enforced CSP headers where one blocks inline scripts, inline scripts inside the blob document should be blocked.
+
+
+
+--------
+Frame: '<!--frame1-->'
+--------
+
+
+--------
+Frame: '<!--frame2-->'
+--------
+PASS: Inline script was blocked by CSP.
diff --git a/LayoutTests/http/tests/security/contentSecurityPolicy/blob-url-inherits-multiple-csp-policies.html b/LayoutTests/http/tests/security/contentSecurityPolicy/blob-url-inherits-multiple-csp-policies.html
new file mode 100644
index 000000000000..3b21b13e864c
--- /dev/null
+++ b/LayoutTests/http/tests/security/contentSecurityPolicy/blob-url-inherits-multiple-csp-policies.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+if (window.testRunner) {
+    testRunner.dumpAsText();
+    testRunner.dumpChildFramesAsText();
+    testRunner.waitUntilDone();
+}
+setTimeout(function() {
+    if (window.testRunner)
+        testRunner.notifyDone();
+}, 1000);
+</script>
+</head>
+<body>
+<p>Tests that a blob: URL iframe inherits ALL CSP policies from its creator document,
+not just the last one. When the creator has two enforced CSP headers where one blocks
+inline scripts, inline scripts inside the blob document should be blocked.</p>
+<iframe src="http://127.0.0.1:8000/security/contentSecurityPolicy/resources/echo-multiple-csp-blob-iframe.py"></iframe>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/security/contentSecurityPolicy/resources/create-blob-iframe.js b/LayoutTests/http/tests/security/contentSecurityPolicy/resources/create-blob-iframe.js
new file mode 100644
index 000000000000..3d28c62bbbdf
--- /dev/null
+++ b/LayoutTests/http/tests/security/contentSecurityPolicy/resources/create-blob-iframe.js
@@ -0,0 +1,12 @@
+var iframe = document.getElementById("blob-frame");
+var html = [
+    "<!DOCTYPE html>",
+    "<html><body>",
+    "<p id='result'>PASS: Inline script was blocked by CSP.</p>",
+    "<script>",
+    "document.getElementById('result').textContent = 'FAIL: Inline script executed (CSP policy was dropped).';",
+    "</" + "script>",
+    "</body></html>"
+].join("\n");
+var blob = new Blob([html], { type: "text/html" });
+iframe.src = URL.createObjectURL(blob);
diff --git a/LayoutTests/http/tests/security/contentSecurityPolicy/resources/echo-multiple-csp-blob-iframe.py b/LayoutTests/http/tests/security/contentSecurityPolicy/resources/echo-multiple-csp-blob-iframe.py
new file mode 100755
index 000000000000..923aa9170663
--- /dev/null
+++ b/LayoutTests/http/tests/security/contentSecurityPolicy/resources/echo-multiple-csp-blob-iframe.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python3
+
+import sys
+
+sys.stdout.write(
+    'Content-Type: text/html; charset=UTF-8\r\n'
+    "Content-Security-Policy: script-src 'self'; frame-src blob:; default-src 'self'\r\n"
+    "Content-Security-Policy: script-src 'self' 'unsafe-inline'; frame-src blob:; default-src 'self'\r\n"
+    '\r\n'
+    '<!DOCTYPE html>\n'
+    '<html>\n'
+    '<body>\n'
+    '<iframe id="blob-frame"></iframe>\n'
+    '<script src="/security/contentSecurityPolicy/resources/create-blob-iframe.js"></script>\n'
+    '</body>\n'
+    '</html>\n'
+)
diff --git a/LayoutTests/imported/w3c/web-platform-tests/trusted-types/inheriting-csp-for-local-schemes-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/trusted-types/inheriting-csp-for-local-schemes-expected.txt
index 47396d14dd48..1a48f1b35af2 100644
--- a/LayoutTests/imported/w3c/web-platform-tests/trusted-types/inheriting-csp-for-local-schemes-expected.txt
+++ b/LayoutTests/imported/w3c/web-platform-tests/trusted-types/inheriting-csp-for-local-schemes-expected.txt
@@ -4,7 +4,7 @@ PASS trusted-types directive should be inherited in local srcdoc frames
 PASS require-trusted-types-for directive should be inherited in local data frames
 PASS trusted-types directive should be inherited in local data frames
 PASS require-trusted-types-for directive should be inherited in local blob frames
-FAIL trusted-types directive should be inherited in local blob frames assert_not_equals: got disallowed value null
+PASS trusted-types directive should be inherited in local blob frames
 PASS require-trusted-types-for directive should be inherited in local about:blank frames
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker.