Overview

Low
Severity
3.1
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in ServiceWorker
DescriptionIncorrect authorization in ServiceWorker
ComponentServiceWorker
Sub-componentServiceWorker
Bug ClassAuthorization Error
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker497359396
Fix commit17630fd0572b (chromium/src) +122/-4
CWECWE-863
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-03-29 (per vendor advisory)
Fix landed2026-08-03 (public git)
Disclosed2026-09-08
Patch gap36 days — fix public in source before the advisory

Background

Fenced frame
An embedded frame type that is strongly isolated from its embedder to protect cross-site data, and is barred from user-activation-gated navigation/window operations.
`ServiceWorkerVersion`
The browser-process object representing a running service worker instance; it stores ancestor_frame_type_, the frame type of the context that registered the worker.
Window client APIs
Clients.openWindow(), WindowClient.focus(), and PaymentRequestEvent.openWindow(), script APIs that open or focus top-level windows and are gated on user activation in the renderer.
`ReportBadMessage`
A Mojo mechanism the browser process uses to reject an IPC that violates an invariant the renderer should have enforced, terminating trust in that message.

Root Cause Analysis

The renderer gates WindowClient.focus(), Clients.openWindow(), and PaymentRequestEvent.openWindow() behind user-interaction/activation checks, and none of those gates can be satisfied from a fenced-frame service-worker context, so the calls are expected never to arrive. The browser-process handlers ServiceWorkerVersion::OpenNewTab(), ServiceWorkerVersion::OpenPaymentHandlerWindow(), and ServiceWorkerVersion::FocusClient() did not re-verify this expectation, even though ServiceWorkerVersion already knows ancestor_frame_type_. A compromised or misbehaving renderer could therefore send these IPCs for a worker whose registration ancestor was a fenced frame, and the browser would honor them, violating the invariant that fenced-frame contexts cannot invoke these window operations.

The fix adds an early check in each of the three handlers: when ancestor_frame_type_ == blink::mojom::AncestorFrameType::kFencedFrame, it calls associated_interface_receiver_.ReportBadMessage(...), resets receiver_, and returns before any window work runs. Because the guard executes first, the operation is refused and the offending renderer connection is torn down, mirroring the renderer-side restriction in the trusted process.

Key insight
The single mistake was trusting the renderer’s user-activation gating without an independent browser-process authorization check, so a fenced-frame worker’s window-API IPCs were serviced; the fix re-checks the already-stored ancestor_frame_type_ in each handler and rejects kFencedFrame requests as bad messages.

Attack Path

  1. Register a worker under a fenced frame An attacker arranges for a service worker to be registered from a fenced-frame context, so its ServiceWorkerVersion carries ancestor_frame_type_ == kFencedFrame.
  2. Bypass the renderer gate Using a compromised or malicious renderer, the attacker issues an OpenNewTab(), OpenPaymentHandlerWindow(), or FocusClient() IPC directly, skipping the renderer-side user-activation check that would normally block it.
  3. Browser honors the request Pre-patch, the browser handler ran the window operation (open/focus) with no ancestor-frame-type check, granting the fenced-frame worker a capability it should not have.
  4. Isolation boundary weakened (inferred) The unauthorized window open/focus could be leveraged to undermine fenced-frame isolation guarantees, but the patch does not demonstrate a concrete data-exfiltration or navigation primitive.

Impact Assessment

An attacker controlling a renderer could invoke top-level window open/focus APIs from a fenced-frame service-worker context in the browser process, defeating the authorization boundary that fenced frames are meant to preserve; the advisory classifies this as incorrect authorization in ServiceWorker. The precondition is a service worker registered with a fenced-frame ancestor plus the ability to send the corresponding Mojo IPCs (a compromised renderer). The vendor advisory rates this low severity and does not report in-the-wild exploitation; NVD scores it CVSS 3.1. The advisory reports no in-the-wild exploitation or severity rating in this material.

Changed Functions

FunctionChangeNotes
if
content/browser/service_worker/service_worker_version.cc
modified
ServiceWorkerVersionFencedFrameTest
content/browser/service_worker/service_worker_version_unittest.cc
modified
switch
content/browser/service_worker/service_worker_version_unittest.cc
modified
TEST_P
content/browser/service_worker/service_worker_version_unittest.cc
modified

Files Changed

  • content/browser/service_worker/service_worker_test_utils.cc
  • content/browser/service_worker/service_worker_version.cc
  • content/browser/service_worker/service_worker_test_utils.h
  • content/browser/service_worker/service_worker_version_unittest.cc

Audit Directions

  • Browser-side re-validation of renderer-gated invariants
    Audit other service-worker and window/client IPC handlers that assume the renderer already enforced activation, origin, or frame-type constraints; the browser must independently verify anything a compromised renderer could forge.
  • Fenced-frame capability gating
    Review all APIs reachable from fenced-frame contexts (navigation, focus, payment, popups) to confirm each has a browser-process AncestorFrameType/kFencedFrame check, not just renderer gating.
  • Stored-context checks left unused
    Look for objects that already hold a security-relevant attribute (like ancestor_frame_type_) but fail to consult it in every handler that grants a privilege, indicating an incomplete authorization surface.
From 17630fd0572bfd5c86f1b19cc4b2dc3cdfaf2cbb Mon Sep 17 00:00:00 2001
From: Shunya Shishido <[email protected]>
Date: Mon, 03 Aug 2026 04:07:54 -0700
Subject: [PATCH] [ServiceWorker] Restrict window APIs in fenced-frame workers

WindowClient.focus(), Clients.openWindow() and
PaymentRequestEvent.openWindow() are gated in the renderer on user
interaction/activation gates. None of those can be triggered from a
fenced frame context for service workers.

Mirror that expectation in the browser process: ServiceWorkerVersion
already stores the registration's ancestor frame type, so check it
in FocusClient(), OpenNewTab(), and OpenPaymentHandlerWindow() and
ReportBadMessage when it is kFencedFrame.

Add ServiceWorkerVersionFencedFrameTest unit tests and plumb an
optional ancestor_frame_type through
CreateNewServiceWorkerRegistration() so the fixture can construct a
kFencedFrame registration.

TAG=agy
CONV=66793587-3f0d-4c60-ba41-165335ec8a6f

Bug: 497359396
Change-Id: I2157fafdc610b1c606fc28c52f732c2320858d40
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8173283
Reviewed-by: Yoshisato Yanagisawa <[email protected]>
Commit-Queue: Shunya Shishido <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1672583}
---
diff --git a/content/browser/service_worker/service_worker_test_utils.cc b/content/browser/service_worker/service_worker_test_utils.cc
index f7ae128..f6b0bcf1 100644
--- a/content/browser/service_worker/service_worker_test_utils.cc
+++ b/content/browser/service_worker/service_worker_test_utils.cc
@@ -475,7 +475,8 @@
 scoped_refptr<ServiceWorkerRegistration> CreateNewServiceWorkerRegistration(
     ServiceWorkerRegistry& registry,
     const blink::mojom::ServiceWorkerRegistrationOptions& options,
-    const blink::StorageKey& key) {
+    const blink::StorageKey& key,
+    blink::mojom::AncestorFrameType ancestor_frame_type) {
   scoped_refptr<ServiceWorkerRegistration> registration;
   // Using nestable run loop because:
   // * The CreateNewRegistration() internally uses a mojo remote and the
@@ -488,7 +489,7 @@
   // problematic.
   base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
   registry.CreateNewRegistration(
-      options, key, blink::mojom::AncestorFrameType::kNormalFrame,
+      options, key, ancestor_frame_type,
       base::BindLambdaForTesting(
           [&](scoped_refptr<ServiceWorkerRegistration> new_registration) {
             registration = std::move(new_registration);
diff --git a/content/browser/service_worker/service_worker_test_utils.h b/content/browser/service_worker/service_worker_test_utils.h
index c6d36e4..6d16ac5c 100644
--- a/content/browser/service_worker/service_worker_test_utils.h
+++ b/content/browser/service_worker/service_worker_test_utils.h
@@ -30,6 +30,7 @@
 #include "services/network/public/mojom/referrer_policy.mojom-shared.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/mojom/loader/fetch_client_settings_object.mojom-forward.h"
+#include "third_party/blink/public/mojom/service_worker/service_worker_ancestor_frame_type.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_provider.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h"
 
@@ -164,7 +165,9 @@
 scoped_refptr<ServiceWorkerRegistration> CreateNewServiceWorkerRegistration(
     ServiceWorkerRegistry& registry,
     const blink::mojom::ServiceWorkerRegistrationOptions& options,
-    const blink::StorageKey& key);
+    const blink::StorageKey& key,
+    blink::mojom::AncestorFrameType ancestor_frame_type =
+        blink::mojom::AncestorFrameType::kNormalFrame);
 
 // Calls CreateNewVersion() synchronously.
 scoped_refptr<ServiceWorkerVersion> CreateNewServiceWorkerVersion(
diff --git a/content/browser/service_worker/service_worker_version.cc b/content/browser/service_worker/service_worker_version.cc
index 35ca907..965cf81 100644
--- a/content/browser/service_worker/service_worker_version.cc
+++ b/content/browser/service_worker/service_worker_version.cc
@@ -73,6 +73,7 @@
 #include "third_party/blink/public/common/service_worker/service_worker_type_converters.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker.mojom.h"
+#include "third_party/blink/public/mojom/service_worker/service_worker_ancestor_frame_type.mojom.h"
 #include "third_party/perfetto/include/perfetto/tracing/track.h"
 
 namespace content {
@@ -1866,6 +1867,12 @@
 
 void ServiceWorkerVersion::OpenNewTab(const GURL& url,
                                       OpenNewTabCallback callback) {
+  if (ancestor_frame_type_ == blink::mojom::AncestorFrameType::kFencedFrame) {
+    associated_interface_receiver_.ReportBadMessage(
+        "Received Clients#openWindow() request from a fenced frame.");
+    receiver_.reset();
+    return;
+  }
   // TODO(crbug.com/40177656): After StorageKey implements partitioning update
   // this to reject with InvalidAccessError if key_ is partitioned.
   OpenWindow(url, service_worker_client_utils::WindowType::NEW_TAB_WINDOW,
@@ -1883,6 +1890,14 @@
     return;
   }
 
+  if (ancestor_frame_type_ == blink::mojom::AncestorFrameType::kFencedFrame) {
+    associated_interface_receiver_.ReportBadMessage(
+        "Received PaymentRequestEvent#openWindow() request from a fenced "
+        "frame.");
+    receiver_.reset();
+    return;
+  }
+
   if (!url.is_valid() || !key_.origin().IsSameOriginWith(url)) {
     associated_interface_receiver_.ReportBadMessage(
         "Received PaymentRequestEvent#openWindow() request for a cross-origin "
@@ -2074,6 +2089,12 @@
     std::move(callback).Run(std::move(result));
     return;
   }
+  if (ancestor_frame_type_ == blink::mojom::AncestorFrameType::kFencedFrame) {
+    associated_interface_receiver_.ReportBadMessage(
+        "Received WindowClient#focus() request from a fenced frame.");
+    receiver_.reset();
+    return;
+  }
   ServiceWorkerClient* service_worker_client =
       context_->service_worker_client_owner().GetServiceWorkerClientByClientID(
           client_uuid);
diff --git a/content/browser/service_worker/service_worker_version_unittest.cc b/content/browser/service_worker/service_worker_version_unittest.cc
index b965e009..7e45b499 100644
--- a/content/browser/service_worker/service_worker_version_unittest.cc
+++ b/content/browser/service_worker/service_worker_version_unittest.cc
@@ -54,6 +54,7 @@
 #include "third_party/blink/public/common/storage_key/storage_key.h"
 #include "third_party/blink/public/common/tokens/tokens.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker.mojom.h"
+#include "third_party/blink/public/mojom/service_worker/service_worker_ancestor_frame_type.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_event_status.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_installed_scripts_manager.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h"
@@ -150,6 +151,10 @@
  protected:
   using FetchHandlerExistence = blink::mojom::FetchHandlerExistence;
 
+  virtual blink::mojom::AncestorFrameType GetAncestorFrameType() const {
+    return blink::mojom::AncestorFrameType::kNormalFrame;
+  }
+
   struct CachedMetadataUpdateListener : public ServiceWorkerVersion::Observer {
     CachedMetadataUpdateListener() = default;
     ~CachedMetadataUpdateListener() override = default;
@@ -176,7 +181,8 @@
     blink::mojom::ServiceWorkerRegistrationOptions options;
     options.scope = scope_;
     registration_ = CreateNewServiceWorkerRegistration(
-        helper_->context()->registry(), options, GetTestStorageKey(scope_));
+        helper_->context()->registry(), options, GetTestStorageKey(scope_),
+        GetAncestorFrameType());
     version_ = CreateNewServiceWorkerVersion(
         helper_->context()->registry(), registration_.get(),
         GURL("https://www.example.com/test/service_worker.js"),
@@ -2780,5 +2786,92 @@
       bad_message_observer.WaitForBadMessage());
 }
 
+class ServiceWorkerVersionFencedFrameTest : public ServiceWorkerVersionTest {
+ protected:
+  blink::mojom::AncestorFrameType GetAncestorFrameType() const override {
+    return blink::mojom::AncestorFrameType::kFencedFrame;
+  }
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    ServiceWorkerVersionFencedFrameTest,
+    testing::ValuesIn({StorageKeyTestCase::kFirstParty,
+                       StorageKeyTestCase::kThirdParty}),
+    [](const testing::TestParamInfo<StorageKeyTestCase>& info) {
+      switch (info.param) {
+        case (StorageKeyTestCase::kFirstParty):
+          return "FirstPartyStorageKey";
+        case (StorageKeyTestCase::kThirdParty):
+          return "ThirdPartyStorageKey";
+      }
+    });
+
+// Verifies that FocusClient() rejects calls for a service worker registered
+// in a fenced frame and reports a bad message.
+TEST_P(ServiceWorkerVersionFencedFrameTest, FocusClient_Rejected) {
+  auto* service_worker =
+      helper_->AddNewPendingServiceWorker<FakeServiceWorker>(helper_.get());
+  ASSERT_EQ(blink::ServiceWorkerStatusCode::kOk,
+            StartServiceWorker(version_.get()));
+  service_worker->RunUntilInitializeGlobalScope();
+  version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
+
+  mojo::test::BadMessageObserver bad_message_observer;
+
+  base::test::TestFuture<blink::mojom::FocusResultPtr> future;
+  service_worker->host()->FocusClient("some-uuid", future.GetCallback());
+
+  EXPECT_EQ("Received WindowClient#focus() request from a fenced frame.",
+            bad_message_observer.WaitForBadMessage());
+}
+
+// Verifies that OpenNewTab() rejects calls for a service worker registered
+// in a fenced frame and reports a bad message.
+TEST_P(ServiceWorkerVersionFencedFrameTest, OpenNewTab_Rejected) {
+  auto* service_worker =
+      helper_->AddNewPendingServiceWorker<FakeServiceWorker>(helper_.get());
+  ASSERT_EQ(blink::ServiceWorkerStatusCode::kOk,
+            StartServiceWorker(version_.get()));
+  service_worker->RunUntilInitializeGlobalScope();
+  version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
+
+  mojo::test::BadMessageObserver bad_message_observer;
+
+  GURL url = scope_.Resolve("new_tab.html");
+  base::test::TestFuture<bool, blink::mojom::ServiceWorkerClientInfoPtr,
+                         const std::optional<std::string>&>
+      future;
+
+  service_worker->host()->OpenNewTab(url, future.GetCallback());
+
+  EXPECT_EQ("Received Clients#openWindow() request from a fenced frame.",
+            bad_message_observer.WaitForBadMessage());
+}
+
+// Verifies that OpenPaymentHandlerWindow() rejects calls for a service worker
+// registered in a fenced frame and reports a bad message.
+TEST_P(ServiceWorkerVersionFencedFrameTest, OpenPaymentHandlerWindow_Rejected) {
+  auto* service_worker =
+      helper_->AddNewPendingServiceWorker<FakeServiceWorker>(helper_.get());
+  ASSERT_EQ(blink::ServiceWorkerStatusCode::kOk,
+            StartServiceWorker(version_.get()));
+  service_worker->RunUntilInitializeGlobalScope();
+  version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
+
+  mojo::test::BadMessageObserver bad_message_observer;
+
+  GURL url = scope_.Resolve("payment_handler.html");
+  base::test::TestFuture<bool, blink::mojom::ServiceWorkerClientInfoPtr,
+                         const std::optional<std::string>&>
+      future;
+
+  service_worker->host()->OpenPaymentHandlerWindow(url, future.GetCallback());
+
+  EXPECT_EQ(
+      "Received PaymentRequestEvent#openWindow() request from a fenced frame.",
+      bad_message_observer.WaitForBadMessage());
+}
+
 }  // namespace service_worker_version_unittest
 }  // namespace content
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/service_worker/service_worker_version_unittest.cc b/content/browser/service_worker/service_worker_version_unittest.cc
index b965e009..7e45b499 100644
--- a/content/browser/service_worker/service_worker_version_unittest.cc
+++ b/content/browser/service_worker/service_worker_version_unittest.cc
@@ -54,6 +54,7 @@
 #include "third_party/blink/public/common/storage_key/storage_key.h"
 #include "third_party/blink/public/common/tokens/tokens.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker.mojom.h"
+#include "third_party/blink/public/mojom/service_worker/service_worker_ancestor_frame_type.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_event_status.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_installed_scripts_manager.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h"
@@ -150,6 +151,10 @@
  protected:
   using FetchHandlerExistence = blink::mojom::FetchHandlerExistence;
 
+  virtual blink::mojom::AncestorFrameType GetAncestorFrameType() const {
+    return blink::mojom::AncestorFrameType::kNormalFrame;
+  }
+
   struct CachedMetadataUpdateListener : public ServiceWorkerVersion::Observer {
     CachedMetadataUpdateListener() = default;
     ~CachedMetadataUpdateListener() override = default;
@@ -176,7 +181,8 @@
     blink::mojom::ServiceWorkerRegistrationOptions options;
     options.scope = scope_;
     registration_ = CreateNewServiceWorkerRegistration(
-        helper_->context()->registry(), options, GetTestStorageKey(scope_));
+        helper_->context()->registry(), options, GetTestStorageKey(scope_),
+        GetAncestorFrameType());
     version_ = CreateNewServiceWorkerVersion(
         helper_->context()->registry(), registration_.get(),
         GURL("https://www.example.com/test/service_worker.js"),
@@ -2780,5 +2786,92 @@
       bad_message_observer.WaitForBadMessage());
 }
 
+class ServiceWorkerVersionFencedFrameTest : public ServiceWorkerVersionTest {
+ protected:
+  blink::mojom::AncestorFrameType GetAncestorFrameType() const override {
+    return blink::mojom::AncestorFrameType::kFencedFrame;
+  }
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    ServiceWorkerVersionFencedFrameTest,
+    testing::ValuesIn({StorageKeyTestCase::kFirstParty,
+                       StorageKeyTestCase::kThirdParty}),
+    [](const testing::TestParamInfo<StorageKeyTestCase>& info) {
+      switch (info.param) {
+        case (StorageKeyTestCase::kFirstParty):
+          return "FirstPartyStorageKey";
+        case (StorageKeyTestCase::kThirdParty):
+          return "ThirdPartyStorageKey";
+      }
+    });
+
+// Verifies that FocusClient() rejects calls for a service worker registered
+// in a fenced frame and reports a bad message.
+TEST_P(ServiceWorkerVersionFencedFrameTest, FocusClient_Rejected) {
+  auto* service_worker =
+      helper_->AddNewPendingServiceWorker<FakeServiceWorker>(helper_.get());
+  ASSERT_EQ(blink::ServiceWorkerStatusCode::kOk,
+            StartServiceWorker(version_.get()));
+  service_worker->RunUntilInitializeGlobalScope();
+  version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
+
+  mojo::test::BadMessageObserver bad_message_observer;
+
+  base::test::TestFuture<blink::mojom::FocusResultPtr> future;
+  service_worker->host()->FocusClient("some-uuid", future.GetCallback());
+
+  EXPECT_EQ("Received WindowClient#focus() request from a fenced frame.",
+            bad_message_observer.WaitForBadMessage());
+}
+
+// Verifies that OpenNewTab() rejects calls for a service worker registered
+// in a fenced frame and reports a bad message.
+TEST_P(ServiceWorkerVersionFencedFrameTest, OpenNewTab_Rejected) {
+  auto* service_worker =
+      helper_->AddNewPendingServiceWorker<FakeServiceWorker>(helper_.get());
+  ASSERT_EQ(blink::ServiceWorkerStatusCode::kOk,
+            StartServiceWorker(version_.get()));
+  service_worker->RunUntilInitializeGlobalScope();
+  version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
+
+  mojo::test::BadMessageObserver bad_message_observer;
+
+  GURL url = scope_.Resolve("new_tab.html");
+  base::test::TestFuture<bool, blink::mojom::ServiceWorkerClientInfoPtr,
+                         const std::optional<std::string>&>
+      future;
+
+  service_worker->host()->OpenNewTab(url, future.GetCallback());
+
+  EXPECT_EQ("Received Clients#openWindow() request from a fenced frame.",
+            bad_message_observer.WaitForBadMessage());
+}
+
+// Verifies that OpenPaymentHandlerWindow() rejects calls for a service worker
+// registered in a fenced frame and reports a bad message.
+TEST_P(ServiceWorkerVersionFencedFrameTest, OpenPaymentHandlerWindow_Rejected) {
+  auto* service_worker =
+      helper_->AddNewPendingServiceWorker<FakeServiceWorker>(helper_.get());
+  ASSERT_EQ(blink::ServiceWorkerStatusCode::kOk,
+            StartServiceWorker(version_.get()));
+  service_worker->RunUntilInitializeGlobalScope();
+  version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
+
+  mojo::test::BadMessageObserver bad_message_observer;
+
+  GURL url = scope_.Resolve("payment_handler.html");
+  base::test::TestFuture<bool, blink::mojom::ServiceWorkerClientInfoPtr,
+                         const std::optional<std::string>&>
+      future;
+
+  service_worker->host()->OpenPaymentHandlerWindow(url, future.GetCallback());
+
+  EXPECT_EQ(
+      "Received PaymentRequestEvent#openWindow() request from a fenced frame.",
+      bad_message_observer.WaitForBadMessage());
+}
+
 }  // namespace service_worker_version_unittest
 }  // namespace content
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.