WebKit · WebCore
CVE-2026-43670
CSP Bypass in WebCore
Overview
High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
Root Cause Analysis
AudioWorklet and PaintWorklet did not inherit the owner document’s CSP, allowing eval() and remote script loading even when CSP blocked them. Fix: WorkletParameters now carries contentSecurityPolicyResponseHeaders; worklet global scopes call applyContentSecurityPolicyResponseHeaders() on creation. WorkerModuleScriptLoader now treats worklet destinations as script-src governed.
Attack Path
- Step 1 Attacker serves a page with strict CSP blocking eval() and remote scripts.
- Step 2 Page creates an AudioWorklet or PaintWorklet.
- Step 3 Worklet runs without inheriting CSP, operating in a policy vacuum.
- Step 4 Attacker executes eval() or loads remote scripts inside worklet.
- Step 5 XSS or data exfiltration via attacker-controlled worklet code.
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifSource/WebCore/bindings/js/WorkerModuleScriptLoader.cpp |
modified | |
CSSFontSelectorSource/WebCore/workers/WorkerGlobalScope.h |
modified | |
CSSValuePoolSource/WebCore/workers/WorkerGlobalScope.h |
modified | |
CacheStorageConnectionSource/WebCore/workers/WorkerGlobalScope.h |
modified | |
ContentSecurityPolicyResponseHeadersSource/WebCore/workers/WorkerGlobalScope.h |
modified | |
CryptoSource/WebCore/workers/WorkerGlobalScope.h |
modified | |
CryptoKeySource/WebCore/workers/WorkerGlobalScope.h |
modified | |
FileSystemStorageConnectionSource/WebCore/workers/WorkerGlobalScope.h |
modified | |
ContentSecurityPolicyResponseHeadersSource/WebCore/workers/WorkerOrWorkletGlobalScope.h |
modified | |
EventLoopTaskGroupSource/WebCore/workers/WorkerOrWorkletGlobalScope.h |
modified | |
ScriptModuleLoaderSource/WebCore/workers/WorkerOrWorkletGlobalScope.h |
modified | |
WorkerEventLoopSource/WebCore/workers/WorkerOrWorkletGlobalScope.h |
modified | |
switchSource/WebKit/NetworkProcess/NetworkLoadChecker.cpp |
modified | |
ifLayoutTests/security/contentSecurityPolicy/audioworklet-inherits-allows-eval.html |
modified | |
ifLayoutTests/security/contentSecurityPolicy/audioworklet-inherits-blocks-eval.html |
modified | |
EvalTestProcessorLayoutTests/security/contentSecurityPolicy/resources/audioworklet-inherits-allows-eval.js |
modified | |
constructorLayoutTests/security/contentSecurityPolicy/resources/audioworklet-inherits-allows-eval.js |
modified | |
processLayoutTests/security/contentSecurityPolicy/resources/audioworklet-inherits-allows-eval.js |
modified | |
EvalTestProcessorLayoutTests/security/contentSecurityPolicy/resources/audioworklet-inherits-blocks-eval.js |
modified | |
constructorLayoutTests/security/contentSecurityPolicy/resources/audioworklet-inherits-blocks-eval.js |
modified | |
processLayoutTests/security/contentSecurityPolicy/resources/audioworklet-inherits-blocks-eval.js |
modified |
Files Changed
LayoutTests/imported/w3c/web-platform-tests/worklets/audio-worklet-csp.https-expected.txtLayoutTests/security/contentSecurityPolicy/audioworklet-inherits-allows-eval-expected.txtLayoutTests/security/contentSecurityPolicy/audioworklet-inherits-allows-eval.htmlLayoutTests/security/contentSecurityPolicy/audioworklet-inherits-blocks-eval-expected.txtLayoutTests/security/contentSecurityPolicy/audioworklet-inherits-blocks-eval.htmlLayoutTests/security/contentSecurityPolicy/resources/audioworklet-inherits-allows-eval.jsLayoutTests/security/contentSecurityPolicy/resources/audioworklet-inherits-blocks-eval.jsSource/WebCore/Modules/webaudio/AudioWorkletGlobalScope.cppSource/WebCore/Modules/webaudio/AudioWorkletMessagingProxy.cppSource/WebCore/bindings/js/WorkerModuleScriptLoader.cppSource/WebCore/loader/FetchOptions.hSource/WebCore/workers/WorkerGlobalScope.cppSource/WebCore/workers/WorkerGlobalScope.hSource/WebCore/workers/WorkerOrWorkletGlobalScope.cppSource/WebCore/workers/WorkerOrWorkletGlobalScope.hSource/WebCore/worklets/PaintWorkletGlobalScope.cppSource/WebCore/worklets/WorkletParameters.hSource/WebKit/NetworkProcess/NetworkLoadChecker.cpp
Patch
diff --git a/Source/WebCore/Modules/webaudio/AudioWorkletGlobalScope.cpp b/Source/WebCore/Modules/webaudio/AudioWorkletGlobalScope.cpp
index c87799b573d2..ab4105d50853 100644
--- a/Source/WebCore/Modules/webaudio/AudioWorkletGlobalScope.cpp
+++ b/Source/WebCore/Modules/webaudio/AudioWorkletGlobalScope.cpp
@@ -57,6 +57,7 @@ RefPtr<AudioWorkletGlobalScope> AudioWorkletGlobalScope::tryCreate(AudioWorkletT
return nullptr;
auto scope = adoptRef(*new AudioWorkletGlobalScope(thread, vm.releaseNonNull(), parameters));
scope->addToContextsMap();
+ scope->applyContentSecurityPolicyResponseHeaders(parameters.contentSecurityPolicyResponseHeaders);
return scope;
}
diff --git a/Source/WebCore/Modules/webaudio/AudioWorkletMessagingProxy.cpp b/Source/WebCore/Modules/webaudio/AudioWorkletMessagingProxy.cpp
index ac2bc14bd68d..fd7d4284ab3d 100644
--- a/Source/WebCore/Modules/webaudio/AudioWorkletMessagingProxy.cpp
+++ b/Source/WebCore/Modules/webaudio/AudioWorkletMessagingProxy.cpp
@@ -35,6 +35,7 @@
#include "AudioWorkletThread.h"
#include "BaseAudioContext.h"
#include "CacheStorageConnection.h"
+#include "ContentSecurityPolicy.h"
#include "DocumentPage.h"
#include "DocumentSettingsValues.h"
#include "LocalFrame.h"
@@ -60,7 +61,8 @@ static WorkletParameters generateWorkletParameters(AudioWorklet& worklet)
document->referrerPolicy(),
worklet.audioContext() ? !worklet.audioContext()->isOfflineContext() : false,
document->advancedPrivacyProtections(),
- document->noiseInjectionHashSalt()
+ document->noiseInjectionHashSalt(),
+ document->checkedContentSecurityPolicy()->responseHeaders()
};
}
diff --git a/Source/WebCore/bindings/js/WorkerModuleScriptLoader.cpp b/Source/WebCore/bindings/js/WorkerModuleScriptLoader.cpp
index faed94463bb2..0e85483d1569 100644
--- a/Source/WebCore/bindings/js/WorkerModuleScriptLoader.cpp
+++ b/Source/WebCore/bindings/js/WorkerModuleScriptLoader.cpp
@@ -89,7 +89,10 @@ void WorkerModuleScriptLoader::load(ScriptExecutionContext& context, URL&& sourc
ContentSecurityPolicyEnforcement contentSecurityPolicyEnforcement = ContentSecurityPolicyEnforcement::DoNotEnforce;
if (!context.shouldBypassMainWorldContentSecurityPolicy()) {
CheckedPtr contentSecurityPolicy = context.contentSecurityPolicy();
- if (fetchOptions.destination == FetchOptions::Destination::Script) {
+ // Worklets and scripts are governed by script-src; workers are governed by worker-src.
+ bool shouldEnforceScriptSrc = fetchOptions.destination == FetchOptions::Destination::Script
+ || isWorkletDestination(fetchOptions.destination);
+ if (shouldEnforceScriptSrc) {
cspCheckFailed = contentSecurityPolicy && !contentSecurityPolicy->allowScriptFromSource(m_sourceURL);
contentSecurityPolicyEnforcement = ContentSecurityPolicyEnforcement::EnforceScriptSrcDirective;
} else {
diff --git a/Source/WebCore/loader/FetchOptions.h b/Source/WebCore/loader/FetchOptions.h
index 161cf3fb7f48..cb022caff12c 100644
--- a/Source/WebCore/loader/FetchOptions.h
+++ b/Source/WebCore/loader/FetchOptions.h
@@ -108,10 +108,15 @@ inline bool isNonSubresourceRequest(FetchOptions::Destination destination)
|| destination == FetchOptions::Destination::Worker;
}
-inline bool isScriptLikeDestination(FetchOptions::Destination destination)
+inline bool isWorkletDestination(FetchOptions::Destination destination)
{
return destination == FetchOptions::Destination::Audioworklet
- || destination == FetchOptions::Destination::Paintworklet
+ || destination == FetchOptions::Destination::Paintworklet;
+}
+
+inline bool isScriptLikeDestination(FetchOptions::Destination destination)
+{
+ return isWorkletDestination(destination)
|| destination == FetchOptions::Destination::Script
|| destination == FetchOptions::Destination::Serviceworker
|| destination == FetchOptions::Destination::Sharedworker
diff --git a/Source/WebCore/workers/WorkerGlobalScope.cpp b/Source/WebCore/workers/WorkerGlobalScope.cpp
index d2734ea11f5c..e1a8096208a3 100644
--- a/Source/WebCore/workers/WorkerGlobalScope.cpp
+++ b/Source/WebCore/workers/WorkerGlobalScope.cpp
@@ -202,11 +202,6 @@ bool WorkerGlobalScope::isSecureContext() const
return m_topOrigin->isPotentiallyTrustworthy();
}
-void WorkerGlobalScope::applyContentSecurityPolicyResponseHeaders(const ContentSecurityPolicyResponseHeaders& contentSecurityPolicyResponseHeaders)
-{
- checkedContentSecurityPolicy()->didReceiveHeaders(contentSecurityPolicyResponseHeaders, String { });
-}
-
URL WorkerGlobalScope::completeURL(const String& url, ForceUTF8) const
{
// Always return a null URL when passed a null string.
diff --git a/Source/WebCore/workers/WorkerGlobalScope.h b/Source/WebCore/workers/WorkerGlobalScope.h
index 3b6dbf0035ed..99ecfecd345a 100644
--- a/Source/WebCore/workers/WorkerGlobalScope.h
+++ b/Source/WebCore/workers/WorkerGlobalScope.h
@@ -51,7 +51,6 @@ namespace WebCore {
class CSSFontSelector;
class CSSValuePool;
class CacheStorageConnection;
-class ContentSecurityPolicyResponseHeaders;
class Crypto;
class CryptoKey;
class FileSystemStorageConnection;
@@ -182,7 +181,6 @@ public:
protected:
WorkerGlobalScope(WorkerThreadType, const WorkerParameters&, Ref<SecurityOrigin>&&, WorkerThread&, Ref<SecurityOrigin>&& topOrigin, IDBClient::IDBConnectionProxy*, SocketProvider*, std::unique_ptr<WorkerClient>&&);
- void applyContentSecurityPolicyResponseHeaders(const ContentSecurityPolicyResponseHeaders&);
void updateSourceProviderBuffers(const ScriptBuffer& mainScript, const HashMap<URL, ScriptBuffer>& importedScripts);
void addConsoleMessage(MessageSource, MessageLevel, const String& message, unsigned long requestIdentifier) override;
diff --git a/Source/WebCore/workers/WorkerOrWorkletGlobalScope.cpp b/Source/WebCore/workers/WorkerOrWorkletGlobalScope.cpp
index 836d14f6bb79..5eea5c94f910 100644
--- a/Source/WebCore/workers/WorkerOrWorkletGlobalScope.cpp
+++ b/Source/WebCore/workers/WorkerOrWorkletGlobalScope.cpp
@@ -26,6 +26,7 @@
#include "config.h"
#include "WorkerOrWorkletGlobalScope.h"
+#include "ContentSecurityPolicy.h"
#include "NoiseInjectionPolicy.h"
#include "ScriptModuleLoader.h"
#include "ServiceWorkerGlobalScope.h"
@@ -158,4 +159,9 @@ RefPtr<WorkerOrWorkletThread> WorkerOrWorkletGlobalScope::workerOrWorkletThread(
return m_thread.get();
}
+void WorkerOrWorkletGlobalScope::applyContentSecurityPolicyResponseHeaders(const ContentSecurityPolicyResponseHeaders& contentSecurityPolicyResponseHeaders)
+{
+ checkedContentSecurityPolicy()->didReceiveHeaders(contentSecurityPolicyResponseHeaders, String { });
+}
+
} // namespace WebCore
diff --git a/Source/WebCore/workers/WorkerOrWorkletGlobalScope.h b/Source/WebCore/workers/WorkerOrWorkletGlobalScope.h
index b29d4a768bd2..4200fb9d0e64 100644
--- a/Source/WebCore/workers/WorkerOrWorkletGlobalScope.h
+++ b/Source/WebCore/workers/WorkerOrWorkletGlobalScope.h
@@ -34,6 +34,7 @@
namespace WebCore {
+class ContentSecurityPolicyResponseHeaders;
class EventLoopTaskGroup;
class ScriptModuleLoader;
class WorkerEventLoop;
@@ -91,6 +92,8 @@ public:
protected:
WorkerOrWorkletGlobalScope(WorkerThreadType, PAL::SessionID, Ref<JSC::VM>&&, ReferrerPolicy, WorkerOrWorkletThread*, std::optional<uint64_t>, OptionSet<AdvancedPrivacyProtections>, std::optional<ScriptExecutionContextIdentifier> = std::nullopt);
+ void applyContentSecurityPolicyResponseHeaders(const ContentSecurityPolicyResponseHeaders&);
+
// ScriptExecutionContext.
bool isJSExecutionForbidden() const final;
diff --git a/Source/WebCore/worklets/PaintWorkletGlobalScope.cpp b/Source/WebCore/worklets/PaintWorkletGlobalScope.cpp
index 6aaf8e0276d8..c0c35304ffd0 100644
--- a/Source/WebCore/worklets/PaintWorkletGlobalScope.cpp
+++ b/Source/WebCore/worklets/PaintWorkletGlobalScope.cpp
@@ -26,6 +26,7 @@
#include "config.h"
#include "PaintWorkletGlobalScope.h"
+#include "ContentSecurityPolicy.h"
#include "Document.h"
#include "JSCSSPaintCallback.h"
#include "JSDOMConvert.h"
@@ -46,6 +47,7 @@ RefPtr<PaintWorkletGlobalScope> PaintWorkletGlobalScope::tryCreate(Document& doc
return nullptr;
auto scope = adoptRef(*new PaintWorkletGlobalScope(document, vm.releaseNonNull(), WTF::move(code)));
scope->addToContextsMap();
+ scope->applyContentSecurityPolicyResponseHeaders(document.checkedContentSecurityPolicy()->responseHeaders());
return scope;
}
diff --git a/Source/WebCore/worklets/WorkletParameters.h b/Source/WebCore/worklets/WorkletParameters.h
index 0b4d7e05d513..1494f43150a1 100644
--- a/Source/WebCore/worklets/WorkletParameters.h
+++ b/Source/WebCore/worklets/WorkletParameters.h
@@ -26,6 +26,7 @@
#pragma once
#include "AdvancedPrivacyProtections.h"
+#include "ContentSecurityPolicyResponseHeaders.h"
#include "Settings.h"
#include <JavaScriptCore/RuntimeFlags.h>
#include <pal/SessionID.h>
@@ -44,9 +45,10 @@ struct WorkletParameters {
bool isAudioContextRealTime;
OptionSet<AdvancedPrivacyProtections> advancedPrivacyProtections;
std::optional<uint64_t> noiseInjectionHashSalt;
+ ContentSecurityPolicyResponseHeaders contentSecurityPolicyResponseHeaders;
- WorkletParameters isolatedCopy() const & { return { windowURL.isolatedCopy(), jsRuntimeFlags, sampleRate, identifier.isolatedCopy(), sessionID, settingsValues.isolatedCopy(), referrerPolicy, isAudioContextRealTime, advancedPrivacyProtections, noiseInjectionHashSalt }; }
- WorkletParameters isolatedCopy() && { return { WTF::move(windowURL).isolatedCopy(), jsRuntimeFlags, sampleRate, WTF::move(identifier).isolatedCopy(), sessionID, WTF::move(settingsValues).isolatedCopy(), referrerPolicy, isAudioContextRealTime, advancedPrivacyProtections, WTF::move(noiseInjectionHashSalt) }; }
+ WorkletParameters isolatedCopy() const & { return { windowURL.isolatedCopy(), jsRuntimeFlags, sampleRate, identifier.isolatedCopy(), sessionID, settingsValues.isolatedCopy(), referrerPolicy, isAudioContextRealTime, advancedPrivacyProtections, noiseInjectionHashSalt, contentSecurityPolicyResponseHeaders.isolatedCopy() }; }
+ WorkletParameters isolatedCopy() && { return { WTF::move(windowURL).isolatedCopy(), jsRuntimeFlags, sampleRate, WTF::move(identifier).isolatedCopy(), sessionID, WTF::move(settingsValues).isolatedCopy(), referrerPolicy, isAudioContextRealTime, advancedPrivacyProtections, WTF::move(noiseInjectionHashSalt), WTF::move(contentSecurityPolicyResponseHeaders).isolatedCopy() }; }
};
} // namespace WebCore
diff --git a/Source/WebKit/NetworkProcess/NetworkLoadChecker.cpp b/Source/WebKit/NetworkProcess/NetworkLoadChecker.cpp
index 08e4d3b28fcb..53495a910301 100644
--- a/Source/WebKit/NetworkProcess/NetworkLoadChecker.cpp
+++ b/Source/WebKit/NetworkProcess/NetworkLoadChecker.cpp
@@ -366,6 +366,7 @@ bool NetworkLoadChecker::isAllowedByContentSecurityPolicy(const ResourceRequest&
switch (m_options.destination) {
case FetchOptions::Destination::Audioworklet:
case FetchOptions::Destination::Paintworklet:
+ return contentSecurityPolicy->allowScriptFromSource(request.url(), redirectResponseReceived, preRedirectURL);
case FetchOptions::Destination::Worker:
case FetchOptions::Destination::Serviceworker:
case FetchOptions::Destination::Sharedworker:
diff --git a/LayoutTests/imported/w3c/web-platform-tests/worklets/audio-worklet-csp.https-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/worklets/audio-worklet-csp.https-expected.txt
index 05d1f20ce5f8..13d9af2156a5 100644
--- a/LayoutTests/imported/w3c/web-platform-tests/worklets/audio-worklet-csp.https-expected.txt
+++ b/LayoutTests/imported/w3c/web-platform-tests/worklets/audio-worklet-csp.https-expected.txt
@@ -1,9 +1,9 @@
PASS A remote-origin worklet should be blocked by the script-src 'self' directive.
-FAIL A same-origin worklet importing a remote-origin script should be blocked by the script-src 'self' directive. assert_equals: expected "REJECTED" but got "RESOLVED"
+PASS A same-origin worklet importing a remote-origin script should be blocked by the script-src 'self' directive.
PASS A remote-origin worklet importing a remote-origin script should be blocked by the script-src 'self' directive.
-FAIL A remote-origin-redirected worklet should be blocked by the script-src 'self' directive. assert_equals: expected "REJECTED" but got "RESOLVED"
-FAIL A same-origin worklet importing a remote-origin-redirected script should be blocked by the script-src 'self' directive. assert_equals: expected "REJECTED" but got "RESOLVED"
+PASS A remote-origin-redirected worklet should be blocked by the script-src 'self' directive.
+PASS A same-origin worklet importing a remote-origin-redirected script should be blocked by the script-src 'self' directive.
PASS A remote-origin worklet should not be blocked because the script-src directive specifying the origin allows it.
PASS A same-origin worklet importing a remote-origin script should not be blocked because the script-src directive specifying the origin allows it.
PASS A remote-origin worklet importing a remote-origin script should not be blocked because the script-src directive specifying the origin allows it.
diff --git a/LayoutTests/security/contentSecurityPolicy/audioworklet-inherits-allows-eval-expected.txt b/LayoutTests/security/contentSecurityPolicy/audioworklet-inherits-allows-eval-expected.txt
new file mode 100644
index 000000000000..8ec7f78db479
--- /dev/null
+++ b/LayoutTests/security/contentSecurityPolicy/audioworklet-inherits-allows-eval-expected.txt
@@ -0,0 +1,3 @@
+This tests that the Content Security Policy (CSP) of the owner document allows an AudioWorklet to use eval() because the CSP lists unsafe-eval in script-src.
+
+PASS eval() was allowed by CSP.
diff --git a/LayoutTests/security/contentSecurityPolicy/audioworklet-inherits-allows-eval.html b/LayoutTests/security/contentSecurityPolicy/audioworklet-inherits-allows-eval.html
new file mode 100644
index 000000000000..196e9c071441
--- /dev/null
+++ b/LayoutTests/security/contentSecurityPolicy/audioworklet-inherits-allows-eval.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline' 'unsafe-eval'">
+<script>
+if (window.testRunner) {
+ testRunner.dumpAsText();
+ testRunner.waitUntilDone();
+}
+</script>
+</head>
+<body>
+<p>This tests that the Content Security Policy (CSP) of the owner document allows an AudioWorklet to use eval() because the CSP lists unsafe-eval in script-src.</p>
+<pre id="result"></pre>
+<script>
+var context = new OfflineAudioContext(2, 100, 44100);
+context.audioWorklet.addModule("resources/audioworklet-inherits-allows-eval.js").then(() => {
+ var node = new AudioWorkletNode(context, 'eval-test');
+ node.port.onmessage = function(event) {
+ document.getElementById("result").textContent = event.data;
+ if (window.testRunner)
+ testRunner.notifyDone();
+ };
+}, (e) => {
+ document.getElementById("result").textContent = "FAIL: addModule rejected with " + e;
+ if (window.testRunner)
+ testRunner.notifyDone();
+});
+</script>
+</body>
+</html>
diff --git a/LayoutTests/security/contentSecurityPolicy/audioworklet-inherits-blocks-eval-expected.txt b/LayoutTests/security/contentSecurityPolicy/audioworklet-inherits-blocks-eval-expected.txt
new file mode 100644
index 000000000000..df7a1c5b5392
--- /dev/null
+++ b/LayoutTests/security/contentSecurityPolicy/audioworklet-inherits-blocks-eval-expected.txt
@@ -0,0 +1,4 @@
+This tests that the Content Security Policy (CSP) of the owner document blocks an AudioWorklet from using eval() because the CSP does not list unsafe-eval in script-src.
+
+PASS threw exception EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' or 'trusted-types-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self' 'unsafe-inline'".
+.
diff --git a/LayoutTests/security/contentSecurityPolicy/audioworklet-inherits-blocks-eval.html b/LayoutTests/security/contentSecurityPolicy/audioworklet-inherits-blocks-eval.html
new file mode 100644
index 000000000000..0b0a29907cac
--- /dev/null
+++ b/LayoutTests/security/contentSecurityPolicy/audioworklet-inherits-blocks-eval.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'">
+<script>
+if (window.testRunner) {
+ testRunner.dumpAsText();
+ testRunner.waitUntilDone();
+}
+</script>
+</head>
+<body>
+<p>This tests that the Content Security Policy (CSP) of the owner document blocks an AudioWorklet from using eval() because the CSP does not list unsafe-eval in script-src.</p>
+<pre id="result"></pre>
+<script>
+var context = new OfflineAudioContext(2, 100, 44100);
+context.audioWorklet.addModule("resources/audioworklet-inherits-blocks-eval.js").then(() => {
+ var node = new AudioWorkletNode(context, 'eval-test');
+ node.port.onmessage = function(event) {
+ document.getElementById("result").textContent = event.data;
+ if (window.testRunner)
+ testRunner.notifyDone();
+ };
+}, (e) => {
+ document.getElementById("result").textContent = "FAIL: addModule rejected with " + e;
+ if (window.testRunner)
+ testRunner.notifyDone();
+});
+</script>
+</body>
+</html>
diff --git a/LayoutTests/security/contentSecurityPolicy/resources/audioworklet-inherits-allows-eval.js b/LayoutTests/security/contentSecurityPolicy/resources/audioworklet-inherits-allows-eval.js
new file mode 100644
index 000000000000..81f7296eab20
--- /dev/null
+++ b/LayoutTests/security/contentSecurityPolicy/resources/audioworklet-inherits-allows-eval.js
@@ -0,0 +1,17 @@
+class EvalTestProcessor extends AudioWorkletProcessor {
+ constructor() {
+ super();
+ var exception;
+ try {
+ eval("1 + 0");
+ } catch (e) {
+ exception = e;
+ }
+ if (!exception)
+ this.port.postMessage("PASS eval() was allowed by CSP.");
+ else
+ this.port.postMessage("FAIL eval() threw exception " + exception + ".");
+ }
+ process() { return false; }
+}
+registerProcessor('eval-test', EvalTestProcessor);
diff --git a/LayoutTests/security/contentSecurityPolicy/resources/audioworklet-inherits-blocks-eval.js b/LayoutTests/security/contentSecurityPolicy/resources/audioworklet-inherits-blocks-eval.js
new file mode 100644
index 000000000000..a675831c6802
--- /dev/null
+++ b/LayoutTests/security/contentSecurityPolicy/resources/audioworklet-inherits-blocks-eval.js
@@ -0,0 +1,19 @@
+class EvalTestProcessor extends AudioWorkletProcessor {
+ constructor() {
+ super();
+ var exception;
+ try {
+ eval("1 + 0");
+ } catch (e) {
+ exception = e;
+ }
+ if (!exception)
+ this.port.postMessage("FAIL should throw EvalError. But did not throw an exception.");
+ else if (exception instanceof EvalError)
+ this.port.postMessage("PASS threw exception " + exception + ".");
+ else
+ this.port.postMessage("FAIL should throw EvalError. Threw exception " + exception + ".");
+ }
+ process() { return false; }
+}
+registerProcessor('eval-test', EvalTestProcessor);
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/LayoutTests/imported/w3c/web-platform-tests/worklets/audio-worklet-csp.https-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/worklets/audio-worklet-csp.https-expected.txt
index 05d1f20ce5f8..13d9af2156a5 100644
--- a/LayoutTests/imported/w3c/web-platform-tests/worklets/audio-worklet-csp.https-expected.txt
+++ b/LayoutTests/imported/w3c/web-platform-tests/worklets/audio-worklet-csp.https-expected.txt
@@ -1,9 +1,9 @@
PASS A remote-origin worklet should be blocked by the script-src 'self' directive.
-FAIL A same-origin worklet importing a remote-origin script should be blocked by the script-src 'self' directive. assert_equals: expected "REJECTED" but got "RESOLVED"
+PASS A same-origin worklet importing a remote-origin script should be blocked by the script-src 'self' directive.
PASS A remote-origin worklet importing a remote-origin script should be blocked by the script-src 'self' directive.
-FAIL A remote-origin-redirected worklet should be blocked by the script-src 'self' directive. assert_equals: expected "REJECTED" but got "RESOLVED"
-FAIL A same-origin worklet importing a remote-origin-redirected script should be blocked by the script-src 'self' directive. assert_equals: expected "REJECTED" but got "RESOLVED"
+PASS A remote-origin-redirected worklet should be blocked by the script-src 'self' directive.
+PASS A same-origin worklet importing a remote-origin-redirected script should be blocked by the script-src 'self' directive.
PASS A remote-origin worklet should not be blocked because the script-src directive specifying the origin allows it.
PASS A same-origin worklet importing a remote-origin script should not be blocked because the script-src directive specifying the origin allows it.
PASS A remote-origin worklet importing a remote-origin script should not be blocked because the script-src directive specifying the origin allows it.
diff --git a/LayoutTests/security/contentSecurityPolicy/audioworklet-inherits-allows-eval-expected.txt b/LayoutTests/security/contentSecurityPolicy/audioworklet-inherits-allows-eval-expected.txt
new file mode 100644
index 000000000000..8ec7f78db479
--- /dev/null
+++ b/LayoutTests/security/contentSecurityPolicy/audioworklet-inherits-allows-eval-expected.txt
@@ -0,0 +1,3 @@
+This tests that the Content Security Policy (CSP) of the owner document allows an AudioWorklet to use eval() because the CSP lists unsafe-eval in script-src.
+
+PASS eval() was allowed by CSP.
diff --git a/LayoutTests/security/contentSecurityPolicy/audioworklet-inherits-allows-eval.html b/LayoutTests/security/contentSecurityPolicy/audioworklet-inherits-allows-eval.html
new file mode 100644
index 000000000000..196e9c071441
--- /dev/null
+++ b/LayoutTests/security/contentSecurityPolicy/audioworklet-inherits-allows-eval.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline' 'unsafe-eval'">
+<script>
+if (window.testRunner) {
+ testRunner.dumpAsText();
+ testRunner.waitUntilDone();
+}
+</script>
+</head>
+<body>
+<p>This tests that the Content Security Policy (CSP) of the owner document allows an AudioWorklet to use eval() because the CSP lists unsafe-eval in script-src.</p>
+<pre id="result"></pre>
+<script>
+var context = new OfflineAudioContext(2, 100, 44100);
+context.audioWorklet.addModule("resources/audioworklet-inherits-allows-eval.js").then(() => {
+ var node = new AudioWorkletNode(context, 'eval-test');
+ node.port.onmessage = function(event) {
+ document.getElementById("result").textContent = event.data;
+ if (window.testRunner)
+ testRunner.notifyDone();
+ };
+}, (e) => {
+ document.getElementById("result").textContent = "FAIL: addModule rejected with " + e;
+ if (window.testRunner)
+ testRunner.notifyDone();
+});
+</script>
+</body>
+</html>
diff --git a/LayoutTests/security/contentSecurityPolicy/audioworklet-inherits-blocks-eval-expected.txt b/LayoutTests/security/contentSecurityPolicy/audioworklet-inherits-blocks-eval-expected.txt
new file mode 100644
index 000000000000..df7a1c5b5392
--- /dev/null
+++ b/LayoutTests/security/contentSecurityPolicy/audioworklet-inherits-blocks-eval-expected.txt
@@ -0,0 +1,4 @@
+This tests that the Content Security Policy (CSP) of the owner document blocks an AudioWorklet from using eval() because the CSP does not list unsafe-eval in script-src.
+
+PASS threw exception EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' or 'trusted-types-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self' 'unsafe-inline'".
+.
diff --git a/LayoutTests/security/contentSecurityPolicy/audioworklet-inherits-blocks-eval.html b/LayoutTests/security/contentSecurityPolicy/audioworklet-inherits-blocks-eval.html
new file mode 100644
index 000000000000..0b0a29907cac
--- /dev/null
+++ b/LayoutTests/security/contentSecurityPolicy/audioworklet-inherits-blocks-eval.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'">
+<script>
+if (window.testRunner) {
+ testRunner.dumpAsText();
+ testRunner.waitUntilDone();
+}
+</script>
+</head>
+<body>
+<p>This tests that the Content Security Policy (CSP) of the owner document blocks an AudioWorklet from using eval() because the CSP does not list unsafe-eval in script-src.</p>
+<pre id="result"></pre>
+<script>
+var context = new OfflineAudioContext(2, 100, 44100);
+context.audioWorklet.addModule("resources/audioworklet-inherits-blocks-eval.js").then(() => {
+ var node = new AudioWorkletNode(context, 'eval-test');
+ node.port.onmessage = function(event) {
+ document.getElementById("result").textContent = event.data;
+ if (window.testRunner)
+ testRunner.notifyDone();
+ };
+}, (e) => {
+ document.getElementById("result").textContent = "FAIL: addModule rejected with " + e;
+ if (window.testRunner)
+ testRunner.notifyDone();
+});
+</script>
+</body>
+</html>
diff --git a/LayoutTests/security/contentSecurityPolicy/resources/audioworklet-inherits-allows-eval.js b/LayoutTests/security/contentSecurityPolicy/resources/audioworklet-inherits-allows-eval.js
new file mode 100644
index 000000000000..81f7296eab20
--- /dev/null
+++ b/LayoutTests/security/contentSecurityPolicy/resources/audioworklet-inherits-allows-eval.js
@@ -0,0 +1,17 @@
+class EvalTestProcessor extends AudioWorkletProcessor {
+ constructor() {
+ super();
+ var exception;
+ try {
+ eval("1 + 0");
+ } catch (e) {
+ exception = e;
+ }
+ if (!exception)
+ this.port.postMessage("PASS eval() was allowed by CSP.");
+ else
+ this.port.postMessage("FAIL eval() threw exception " + exception + ".");
+ }
+ process() { return false; }
+}
+registerProcessor('eval-test', EvalTestProcessor);
diff --git a/LayoutTests/security/contentSecurityPolicy/resources/audioworklet-inherits-blocks-eval.js b/LayoutTests/security/contentSecurityPolicy/resources/audioworklet-inherits-blocks-eval.js
new file mode 100644
index 000000000000..a675831c6802
--- /dev/null
+++ b/LayoutTests/security/contentSecurityPolicy/resources/audioworklet-inherits-blocks-eval.js
@@ -0,0 +1,19 @@
+class EvalTestProcessor extends AudioWorkletProcessor {
+ constructor() {
+ super();
+ var exception;
+ try {
+ eval("1 + 0");
+ } catch (e) {
+ exception = e;
+ }
+ if (!exception)
+ this.port.postMessage("FAIL should throw EvalError. But did not throw an exception.");
+ else if (exception instanceof EvalError)
+ this.port.postMessage("PASS threw exception " + exception + ".");
+ else
+ this.port.postMessage("FAIL should throw EvalError. Threw exception " + exception + ".");
+ }
+ process() { return false; }
+}
+registerProcessor('eval-test', EvalTestProcessor);
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker.
References
On This Page