Medium chrome Logic Error 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in ServiceWorker
DescriptionIncorrect authorization in ServiceWorker
ComponentServiceWorker
Bug ClassLogic Error
Tracker498869663
Fix commitc7e9116783f4 (chromium/src) +190/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-09-08

Background

`ServiceWorkerMainResourceHandle`
A browser-side object (service_worker_handle_) that reserves a ServiceWorkerClient for a main-resource navigation so the committed document can be controlled by a service worker.
`ServiceWorkerContainerHost`
The browser-process endpoint that backs a document’s service worker container and ships controller info to the renderer during CommitNavigation.
CSP `sandbox` directive
A Content-Security-Policy: sandbox response header that applies sandbox flags to the document it delivers, forcing an opaque origin unless allow-same-origin is present.
`network::mojom::WebSandboxFlags::kOrigin`
The sandbox flag that, when set, forces the document to commit with an opaque origin and thus makes it ineligible to use service workers.

Root Cause Analysis

NavigationRequest decided whether to allocate service_worker_handle_ (a ServiceWorkerMainResourceHandle) before sending the request, using only the sandbox flags inherited from the frame owner element via pending_frame_policy(). When the response itself carried a Content-Security-Policy: sandbox header without allow-same-origin, the document committed with an opaque origin — which is not eligible for service worker control — yet the previously created handle was never re-evaluated. As a result a ServiceWorkerContainerHost and controller info were still created and sent to the renderer in CommitNavigation, with the renderer merely discarding them later in ServiceWorkerNetworkProviderForFrame::DidCreateNewDocument(). The invariant violated is that an opaque-origin (CSP-sandboxed) document must never be provisioned with a service worker container in the browser.

The fix re-checks the final policies at commit time — after CSP-delivered sandbox flags are known — and if WebSandboxFlags::kOrigin is set it calls service_worker_handle_.reset(), destroying the reserved ServiceWorkerClient before any container host is built.

Key insight
The core mistake was making the service-worker-eligibility decision solely from pre-request frame-owner sandbox flags and never re-validating it once response-delivered CSP sandbox flags were known. The fix repeats the check at commit time against the final policies, so a kOrigin-sandboxed document has its handle dropped in the browser rather than relying on the renderer to clean up.

Attack Path

  1. Register a controlling service worker An attacker or victim registration owns a scope covering the target URL, so navigations to it are matched against the registration.
  2. Serve a CSP-sandboxed response The in-scope resource returns Content-Security-Policy: sandbox (without allow-same-origin), causing the document to commit with an opaque origin.
  3. Navigate in scope Loading the resource as a subframe (no sandbox iframe attribute) or as a main frame matches the request against the registration before response headers are known.
  4. Container host is provisioned anyway The stale service_worker_handle_ survives to commit, so the browser builds a ServiceWorkerContainerHost and controller info for a document that should have none.
  5. Renderer-only cleanup Eligibility is enforced only later in the renderer, leaving the browser having granted service-worker provisioning to an opaque-origin document.

Impact Assessment

An attacker gains an authorization inconsistency in which a CSP-sandboxed, opaque-origin document is provisioned with a browser-side service worker container and controller it should not be eligible to receive. This occurs in the browser process during main-resource navigation commit, with the mismatch reconciled only in the renderer. Preconditions are an active service worker registration whose scope covers a URL whose response applies Content-Security-Policy: sandbox without allow-same-origin; the CVE is classified as a medium-severity logic/authorization error.

Changed Functions

FunctionChangeNotes
BindLambdaForTesting
content/browser/service_worker/service_worker_browsertest.cc
modified

Files Changed

  • content/browser/renderer_host/navigation_request.cc
  • content/browser/service_worker/service_worker_browsertest.cc

Audit Directions

  • Pre-request vs. commit-time policy decisions
    Audit any navigation decision made from pending_frame_policy() or frame-owner sandbox flags and confirm it is re-validated against policy_container_builder_->FinalPolicies() once response-delivered CSP is known.
  • Opaque-origin eligibility invariants
    Search for browser-side capabilities (storage, service workers, permissions) provisioned before origin computation and verify WebSandboxFlags::kOrigin / opaque-origin cases are excluded in the browser, not only in the renderer.
  • Renderer-side cleanup as a smell
    Treat any state the renderer silently drops (e.g. in DidCreateNewDocument()) as a signal that the browser granted something it should have withheld, and push the check upstream to the trust boundary.
From c7e9116783f4036c2ccef5e4d992053cfc55efef Mon Sep 17 00:00:00 2001
From: Jiacheng Guo <gjc@google.com>
Date: Wed, 01 Jul 2026 21:05:36 -0700
Subject: [PATCH] Drop ServiceWorkerMainResourceHandle for CSP-sandboxed documents

NavigationRequest decides whether to create a
ServiceWorkerMainResourceHandle before sending the request, based only
on the sandbox flags inherited from the frame owner element
(pending_frame_policy()). When the response carries a
`Content-Security-Policy: sandbox` header without `allow-same-origin`,
the document commits with an opaque origin and is not eligible to use
service workers, but the handle was kept and a
ServiceWorkerContainerHost (and controller info) was created and sent to
the renderer in CommitNavigation. The renderer then dropped them in
ServiceWorkerNetworkProviderForFrame::DidCreateNewDocument().

Perform the same check in the browser at commit time, once the final
policies (which include CSP-delivered sandbox flags) are known: if the
kOrigin sandbox flag is set, reset the handle so the reserved
ServiceWorkerClient is destroyed without creating a container host and
nothing service-worker related is sent to the renderer.

Add a content_browsertest that registers a service worker, loads a page
in scope whose response is CSP-sandboxed (both as a subframe with no
`sandbox` iframe attribute and as a main frame), and verifies that the
reserved ServiceWorkerClient is destroyed without ever transitioning to
the response-committed state.

Bug: 498869663
Change-Id: Idb36e05924a003e3f7363431db58415fcd8617cd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8018942
Reviewed-by: Rakina Zata Amni <rakina@chromium.org>
Commit-Queue: Jiacheng Guo <gjc@google.com>
Cr-Commit-Position: refs/heads/main@{#1655887}
---

diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index b12de8e..7d56d368 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -7128,6 +7128,19 @@
       service_worker_container_info;
   blink::mojom::ControllerServiceWorkerInfoPtr controller;
 
+  // The decision to create `service_worker_handle_` was made before the
+  // request was sent, based only on the sandbox flags inherited from the frame
+  // owner element. If the response delivered a `Content-Security-Policy:
+  // sandbox` header without `allow-same-origin`, the document will commit with
+  // an opaque origin and is not eligible to use service workers, so drop the
+  // handle now instead of creating a container host for the committed document.
+  if (service_worker_handle_ &&
+      (policy_container_builder_->FinalPolicies().sandbox_flags &
+       network::mojom::WebSandboxFlags::kOrigin) ==
+          network::mojom::WebSandboxFlags::kOrigin) {
+    service_worker_handle_.reset();
+  }
+
   // Notify the service worker navigation handle that navigation commit is
   // about to go.
   if (service_worker_handle_ &&
diff --git a/content/browser/service_worker/service_worker_browsertest.cc b/content/browser/service_worker/service_worker_browsertest.cc
index 940049f..d355bcba 100644
--- a/content/browser/service_worker/service_worker_browsertest.cc
+++ b/content/browser/service_worker/service_worker_browsertest.cc
@@ -1045,6 +1045,162 @@
       running_info.render_process_id);
 }
 
+// A document that commits with an opaque origin because its response carries a
+// `Content-Security-Policy: sandbox` header (without `allow-same-origin`) must
+// not be given a service worker container in the browser process, even if its
+// URL is in the scope of an active registration. The sandbox flags from the
+// response are only known once the final policies have been computed, so this
+// must be enforced at commit time in addition to the pre-request check based
+// on frame-owner sandbox flags.
+IN_PROC_BROWSER_TEST_F(ServiceWorkerBrowserTest,
+                       NoContainerHostForCSPSandboxedDocument) {
+  StartServerAndNavigateToSetup();
+
+  // Register a service worker that controls /service_worker/.
+  EXPECT_TRUE(NavigateToURL(shell(),
+                            embedded_test_server()->GetURL(
+                                "/service_worker/create_service_worker.html")));
+  EXPECT_EQ("DONE",
+            EvalJs(shell(), "register('fetch_event_pass_through.js');"));
+
+  // Navigate the main frame to a controlled page so it can host a subframe.
+  EXPECT_TRUE(NavigateToURL(
+      shell(), embedded_test_server()->GetURL("/service_worker/empty.html")));
+  RenderFrameHostImpl* main_frame = static_cast<RenderFrameHostImpl*>(
+      shell()->web_contents()->GetPrimaryMainFrame());
+  ASSERT_TRUE(main_frame->GetLastCommittedServiceWorkerClient());
+  EXPECT_TRUE(main_frame->GetLastCommittedServiceWorkerClient()->controller());
+
+  // Create a same-site iframe with no `sandbox` attribute on the <iframe>
+  // element. The response carries `Content-Security-Policy: sandbox`, so the
+  // subframe document commits with an opaque origin. While the navigation
+  // request itself may still be matched against the registration (the response
+  // headers are not yet known), the resulting reserved client must be dropped
+  // without creating a container host once the final sandbox flags are known.
+  GURL subframe_url =
+      embedded_test_server()->GetURL("/service_worker/csp_sandboxed.html");
+  blink::StorageKey key =
+      blink::StorageKey::CreateFirstParty(url::Origin::Create(subframe_url));
+
+  auto verify_client_destroyed = [&](TestNavigationManager& nav_manager) {
+    ASSERT_TRUE(nav_manager.WaitForResponse());
+
+    ServiceWorkerClient* reserved_client = nullptr;
+    for (auto it = wrapper()
+                       ->context()
+                       ->service_worker_client_owner()
+                       .GetServiceWorkerClients(
+                           key, /*include_reserved_clients=*/true,
+                           /*include_back_forward_cached_clients=*/false);
+         !it.IsAtEnd(); ++it) {
+      if (it->url() == subframe_url) {
+        reserved_client = &(*it);
+        break;
+      }
+    }
+    ASSERT_TRUE(reserved_client);
+    EXPECT_FALSE(reserved_client->is_response_committed());
+
+    bool client_destroyed = false;
+    bool was_response_committed = false;
+    reserved_client->SetDestructionCallbackForTesting(
+        base::BindLambdaForTesting([&]() {
+          client_destroyed = true;
+          was_response_committed = reserved_client->is_response_committed();
+        }));
+
+    nav_manager.ResumeNavigation();
+    EXPECT_TRUE(nav_manager.WaitForNavigationFinished());
+    EXPECT_TRUE(client_destroyed);
+    EXPECT_FALSE(was_response_committed);
+  };
+
+  {
+    TestNavigationManager nav_manager(shell()->web_contents(), subframe_url);
+    EXPECT_TRUE(
+        ExecJs(main_frame, JsReplace("let f = document.createElement('iframe');"
+                                     "f.src = $1;"
+                                     "document.body.appendChild(f);",
+                                     subframe_url)));
+    verify_client_destroyed(nav_manager);
+  }
+
+  ASSERT_EQ(1u, main_frame->child_count());
+  RenderFrameHostImpl* child_frame =
+      main_frame->child_at(0)->current_frame_host();
+  EXPECT_EQ(subframe_url, child_frame->GetLastCommittedURL());
+  EXPECT_TRUE(child_frame->GetLastCommittedOrigin().opaque());
+  EXPECT_FALSE(child_frame->GetLastCommittedServiceWorkerClient());
+
+  // The same applies when the main frame itself is CSP-sandboxed.
+  {
+    TestNavigationManager nav_manager(shell()->web_contents(), subframe_url);
+    shell()->LoadURL(subframe_url);
+    verify_client_destroyed(nav_manager);
+  }
+  main_frame = static_cast<RenderFrameHostImpl*>(
+      shell()->web_contents()->GetPrimaryMainFrame());
+  EXPECT_TRUE(main_frame->GetLastCommittedOrigin().opaque());
+  EXPECT_FALSE(main_frame->GetLastCommittedServiceWorkerClient());
+}
+
+// A document that commits with a non-opaque origin because its response carries
+// a `Content-Security-Policy: sandbox allow-same-origin` header must keep its
+// service worker container in the browser process and remain controlled by the
+// service worker.
+IN_PROC_BROWSER_TEST_F(ServiceWorkerBrowserTest,
+                       ContainerHostForCSPSandboxedAllowSameOriginDocument) {
+  StartServerAndNavigateToSetup();
+
+  // Register a service worker that controls /service_worker/.
+  EXPECT_TRUE(NavigateToURL(shell(),
+                            embedded_test_server()->GetURL(
+                                "/service_worker/create_service_worker.html")));
+  EXPECT_EQ("DONE",
+            EvalJs(shell(), "register('fetch_event_pass_through.js');"));
+
+  // Navigate the main frame to a controlled page so it can host a subframe.
+  EXPECT_TRUE(NavigateToURL(
+      shell(), embedded_test_server()->GetURL("/service_worker/empty.html")));
+  RenderFrameHostImpl* main_frame = static_cast<RenderFrameHostImpl*>(
+      shell()->web_contents()->GetPrimaryMainFrame());
+  ASSERT_TRUE(main_frame->GetLastCommittedServiceWorkerClient());
+  EXPECT_TRUE(main_frame->GetLastCommittedServiceWorkerClient()->controller());
+
+  // Create a same-site iframe with no `sandbox` attribute on the <iframe>
+  // element. The response carries `Content-Security-Policy: sandbox
+  // allow-same-origin`, so the subframe document commits with a non-opaque
+  // origin and remains controlled by the service worker.
+  GURL subframe_url = embedded_test_server()->GetURL(
+      "/service_worker/csp_sandboxed_allow_same_origin.html");
+  {
+    EXPECT_TRUE(
+        ExecJs(main_frame, JsReplace("let f = document.createElement('iframe');"
+                                     "f.src = $1;"
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/service_worker/service_worker_browsertest.cc b/content/browser/service_worker/service_worker_browsertest.cc
index 940049f..d355bcba 100644
--- a/content/browser/service_worker/service_worker_browsertest.cc
+++ b/content/browser/service_worker/service_worker_browsertest.cc
@@ -1045,6 +1045,162 @@
       running_info.render_process_id);
 }
 
+// A document that commits with an opaque origin because its response carries a
+// `Content-Security-Policy: sandbox` header (without `allow-same-origin`) must
+// not be given a service worker container in the browser process, even if its
+// URL is in the scope of an active registration. The sandbox flags from the
+// response are only known once the final policies have been computed, so this
+// must be enforced at commit time in addition to the pre-request check based
+// on frame-owner sandbox flags.
+IN_PROC_BROWSER_TEST_F(ServiceWorkerBrowserTest,
+                       NoContainerHostForCSPSandboxedDocument) {
+  StartServerAndNavigateToSetup();
+
+  // Register a service worker that controls /service_worker/.
+  EXPECT_TRUE(NavigateToURL(shell(),
+                            embedded_test_server()->GetURL(
+                                "/service_worker/create_service_worker.html")));
+  EXPECT_EQ("DONE",
+            EvalJs(shell(), "register('fetch_event_pass_through.js');"));
+
+  // Navigate the main frame to a controlled page so it can host a subframe.
+  EXPECT_TRUE(NavigateToURL(
+      shell(), embedded_test_server()->GetURL("/service_worker/empty.html")));
+  RenderFrameHostImpl* main_frame = static_cast<RenderFrameHostImpl*>(
+      shell()->web_contents()->GetPrimaryMainFrame());
+  ASSERT_TRUE(main_frame->GetLastCommittedServiceWorkerClient());
+  EXPECT_TRUE(main_frame->GetLastCommittedServiceWorkerClient()->controller());
+
+  // Create a same-site iframe with no `sandbox` attribute on the <iframe>
+  // element. The response carries `Content-Security-Policy: sandbox`, so the
+  // subframe document commits with an opaque origin. While the navigation
+  // request itself may still be matched against the registration (the response
+  // headers are not yet known), the resulting reserved client must be dropped
+  // without creating a container host once the final sandbox flags are known.
+  GURL subframe_url =
+      embedded_test_server()->GetURL("/service_worker/csp_sandboxed.html");
+  blink::StorageKey key =
+      blink::StorageKey::CreateFirstParty(url::Origin::Create(subframe_url));
+
+  auto verify_client_destroyed = [&](TestNavigationManager& nav_manager) {
+    ASSERT_TRUE(nav_manager.WaitForResponse());
+
+    ServiceWorkerClient* reserved_client = nullptr;
+    for (auto it = wrapper()
+                       ->context()
+                       ->service_worker_client_owner()
+                       .GetServiceWorkerClients(
+                           key, /*include_reserved_clients=*/true,
+                           /*include_back_forward_cached_clients=*/false);
+         !it.IsAtEnd(); ++it) {
+      if (it->url() == subframe_url) {
+        reserved_client = &(*it);
+        break;
+      }
+    }
+    ASSERT_TRUE(reserved_client);
+    EXPECT_FALSE(reserved_client->is_response_committed());
+
+    bool client_destroyed = false;
+    bool was_response_committed = false;
+    reserved_client->SetDestructionCallbackForTesting(
+        base::BindLambdaForTesting([&]() {
+          client_destroyed = true;
+          was_response_committed = reserved_client->is_response_committed();
+        }));
+
+    nav_manager.ResumeNavigation();
+    EXPECT_TRUE(nav_manager.WaitForNavigationFinished());
+    EXPECT_TRUE(client_destroyed);
+    EXPECT_FALSE(was_response_committed);
+  };
+
+  {
+    TestNavigationManager nav_manager(shell()->web_contents(), subframe_url);
+    EXPECT_TRUE(
+        ExecJs(main_frame, JsReplace("let f = document.createElement('iframe');"
+                                     "f.src = $1;"
+                                     "document.body.appendChild(f);",
+                                     subframe_url)));
+    verify_client_destroyed(nav_manager);
+  }
+
+  ASSERT_EQ(1u, main_frame->child_count());
+  RenderFrameHostImpl* child_frame =
+      main_frame->child_at(0)->current_frame_host();
+  EXPECT_EQ(subframe_url, child_frame->GetLastCommittedURL());
+  EXPECT_TRUE(child_frame->GetLastCommittedOrigin().opaque());
+  EXPECT_FALSE(child_frame->GetLastCommittedServiceWorkerClient());
+
+  // The same applies when the main frame itself is CSP-sandboxed.
+  {
+    TestNavigationManager nav_manager(shell()->web_contents(), subframe_url);
+    shell()->LoadURL(subframe_url);
+    verify_client_destroyed(nav_manager);
+  }
+  main_frame = static_cast<RenderFrameHostImpl*>(
+      shell()->web_contents()->GetPrimaryMainFrame());
+  EXPECT_TRUE(main_frame->GetLastCommittedOrigin().opaque());
+  EXPECT_FALSE(main_frame->GetLastCommittedServiceWorkerClient());
+}
+
+// A document that commits with a non-opaque origin because its response carries
+// a `Content-Security-Policy: sandbox allow-same-origin` header must keep its
+// service worker container in the browser process and remain controlled by the
+// service worker.
+IN_PROC_BROWSER_TEST_F(ServiceWorkerBrowserTest,
+                       ContainerHostForCSPSandboxedAllowSameOriginDocument) {
+  StartServerAndNavigateToSetup();
+
+  // Register a service worker that controls /service_worker/.
+  EXPECT_TRUE(NavigateToURL(shell(),
+                            embedded_test_server()->GetURL(
+                                "/service_worker/create_service_worker.html")));
+  EXPECT_EQ("DONE",
+            EvalJs(shell(), "register('fetch_event_pass_through.js');"));
+
+  // Navigate the main frame to a controlled page so it can host a subframe.
+  EXPECT_TRUE(NavigateToURL(
+      shell(), embedded_test_server()->GetURL("/service_worker/empty.html")));
+  RenderFrameHostImpl* main_frame = static_cast<RenderFrameHostImpl*>(
+      shell()->web_contents()->GetPrimaryMainFrame());
+  ASSERT_TRUE(main_frame->GetLastCommittedServiceWorkerClient());
+  EXPECT_TRUE(main_frame->GetLastCommittedServiceWorkerClient()->controller());
+
+  // Create a same-site iframe with no `sandbox` attribute on the <iframe>
+  // element. The response carries `Content-Security-Policy: sandbox
+  // allow-same-origin`, so the subframe document commits with a non-opaque
+  // origin and remains controlled by the service worker.
+  GURL subframe_url = embedded_test_server()->GetURL(
+      "/service_worker/csp_sandboxed_allow_same_origin.html");
+  {
+    EXPECT_TRUE(
+        ExecJs(main_frame, JsReplace("let f = document.createElement('iframe');"
+                                     "f.src = $1;"
+                                     "document.body.appendChild(f);",
+                                     subframe_url)));
+    EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+  }
+
+  ASSERT_EQ(1u, main_frame->child_count());
+  RenderFrameHostImpl* child_frame =
+      main_frame->child_at(0)->current_frame_host();
+  EXPECT_EQ(subframe_url, child_frame->GetLastCommittedURL());
+  EXPECT_FALSE(child_frame->GetLastCommittedOrigin().opaque());
+  ASSERT_TRUE(child_frame->GetLastCommittedServiceWorkerClient());
+  EXPECT_TRUE(child_frame->GetLastCommittedServiceWorkerClient()->controller());
+
+  // The same applies when the main frame itself is sandboxed with
+  // allow-same-origin.
+  shell()->LoadURL(subframe_url);
+  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+  main_frame = static_cast<RenderFrameHostImpl*>(
+      shell()->web_contents()->GetPrimaryMainFrame());
+  EXPECT_FALSE(main_frame->GetLastCommittedOrigin().opaque());
+  ASSERT_TRUE(main_frame->GetLastCommittedServiceWorkerClient());
+  EXPECT_TRUE(main_frame->GetLastCommittedServiceWorkerClient()->controller());
+}
+
 IN_PROC_BROWSER_TEST_F(ServiceWorkerBrowserTest, StartWorkerWhileInstalling) {
   StartServerAndNavigateToSetup();
   const char kWorkerUrl[] = "/service_worker/while_true_in_install_worker.js";
diff --git a/content/test/content_test_bundle_data.filelist b/content/test/content_test_bundle_data.filelist
index 8f98feaa..4a62856 100644
--- a/content/test/content_test_bundle_data.filelist
+++ b/content/test/content_test_bundle_data.filelist
@@ -8920,6 +8920,10 @@
 data/service_worker/create_service_worker_from_isolated.html.mock-http-headers
 data/service_worker/cross_site_xfer.js
 data/service_worker/cross_site_xfer.js.mock-http-headers
+data/service_worker/csp_sandboxed.html
+data/service_worker/csp_sandboxed.html.mock-http-headers
+data/service_worker/csp_sandboxed_allow_same_origin.html
+data/service_worker/csp_sandboxed_allow_same_origin.html.mock-http-headers
 data/service_worker/disable_web_security_cross_origin.html
 data/service_worker/disable_web_security_get_registration.html
 data/service_worker/disable_web_security_register.html
diff --git a/content/test/data/service_worker/csp_sandboxed.html b/content/test/data/service_worker/csp_sandboxed.html
new file mode 100644
index 0000000..081ad86
--- /dev/null
+++ b/content/test/data/service_worker/csp_sandboxed.html
@@ -0,0 +1,5 @@
+<html>
+  <head>
+    <title>ServiceWorker test - CSP sandboxed page</title>
+  </head>
+</html>
diff --git a/content/test/data/service_worker/csp_sandboxed.html.mock-http-headers b/content/test/data/service_worker/csp_sandboxed.html.mock-http-headers
new file mode 100644
index 0000000..4f8228a
--- /dev/null
+++ b/content/test/data/service_worker/csp_sandboxed.html.mock-http-headers
@@ -0,0 +1,3 @@
+HTTP/1.1 200 OK
+Content-Type: text/html
+Content-Security-Policy: sandbox allow-scripts
diff --git a/content/test/data/service_worker/csp_sandboxed_allow_same_origin.html b/content/test/data/service_worker/csp_sandboxed_allow_same_origin.html
new file mode 100644
index 0000000..9dd05839
--- /dev/null
+++ b/content/test/data/service_worker/csp_sandboxed_allow_same_origin.html
@@ -0,0 +1,5 @@
+<html>
+  <head>
+    <title>ServiceWorker test - CSP sandboxed page with allow-same-origin</title>
+  </head>
+</html>
diff --git a/content/test/data/service_worker/csp_sandboxed_allow_same_origin.html.mock-http-headers b/content/test/data/service_worker/csp_sandboxed_allow_same_origin.html.mock-http-headers
new file mode 100644
index 0000000..23ec234b
--- /dev/null
+++ b/content/test/data/service_worker/csp_sandboxed_allow_same_origin.html.mock-http-headers
@@ -0,0 +1,4 @@
+HTTP/1.1 200 OK
+Content-Type: text/html
+Content-Security-Policy: sandbox allow-scripts allow-same-origin
+
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.