Low watch CVSS 4.3 chrome Input Validation 🔧 Commit mapped

Overview

Low
Severity
4.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper input validation in ServiceWorker
DescriptionImproper input validation in ServiceWorker
ComponentServiceWorker
Sub-componentService Workers
Bug ClassInput Validation
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.
Tracker522291712
Fix commitd1967cd13713 (chromium/src) +58/-2
CWECWE-20 (Improper input validation)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-06-10 (per vendor advisory)
Fix landed2026-07-15 (public git)
Disclosed2026-08-25
Patch gap41 days — fix public in source before the advisory

Background

`OnReportConsoleMessage`
The browser-process handler in ServiceWorkerContextCore that receives console-message reports emitted by a service worker running in a renderer.
`source_url`
A renderer-supplied parameter naming the script URL a console message came from, which web content can freely set via the V8 //# sourceURL= pragma.
`IsBuiltinComponent` / `HasWebUIScheme`
Trust checks that treat privileged (e.g. chrome://, WebUI) origins as built-in components, granting them relaxed logging behavior including bypass of Incognito logging restrictions and higher log severity.
`ServiceWorkerVersion::script_url()` / `key().origin()`
The browser-authenticated script URL and storage-key origin of the actual service-worker version, which cannot be spoofed by the renderer.

Root Cause Analysis

ServiceWorkerContextCore::OnReportConsoleMessage computed is_builtin_component by passing the renderer-provided source_url to HasWebUIScheme(source_url) and IsBuiltinComponent(browser_context, url::Origin::Create(source_url)). Because ordinary web content can set source_url to an arbitrary string (via the //# sourceURL= pragma), an unprivileged service worker could report a message with a spoofed chrome:// URL and be misclassified as a built-in/privileged component. This violated the invariant that a privileged-component determination must be based on the browser’s authenticated view of the worker, not on renderer-controlled data.

The fix evaluates the check against version->script_url() and version->key().origin(), which the browser process derives from the trusted registration, so a spoofed source_url no longer influences the trust decision. The shipped regression test confirms that a chrome://settings/ source_url causes IsBuiltinComponent to be queried with the worker’s real https://www.example.com origin.

Key insight
The core mistake was making a privilege/trust decision from renderer-controlled source_url input; the fix substitutes the browser-authenticated version->script_url() and version->key().origin() so the classification can no longer be spoofed.

Attack Path

  1. Register an ordinary service worker An attacker on unprivileged web content registers a service worker under their own origin (e.g. https://www.example.com).
  2. Spoof the source URL Inside the worker the attacker emits a console message whose source_url is forced to a privileged value such as chrome://settings/ using the //# sourceURL= pragma.
  3. Trigger the trust check OnReportConsoleMessage runs HasWebUIScheme/IsBuiltinComponent against the spoofed URL and sets is_builtin_component = true.
  4. Escalate logging The message is then logged as if from a built-in component, bypassing Incognito logging restrictions and escalating log severity as described in the commit message.

Impact Assessment

An unprivileged renderer/service worker gains the ability to have its console output treated as originating from a privileged built-in component, bypassing Incognito logging restrictions and raising log severity within the browser process’s logging path. The precondition is only the ability to register and run a service worker on attacker-controlled web content. The advisory classifies this as improper input validation in ServiceWorker and states no exploited-in-the-wild status; any impact beyond the logging-classification bypass is (inferred).

Changed Functions

FunctionChangeNotes
BuiltinComponentTestBrowserClient
content/browser/service_worker/service_worker_context_core_unittest.cc
modified
TEST_F
content/browser/service_worker/service_worker_context_core_unittest.cc
modified

Files Changed

  • content/browser/service_worker/service_worker_context_core.cc
  • content/browser/service_worker/service_worker_context_core_unittest.cc

Audit Directions

  • Renderer-supplied URLs in trust checks
    Grep for security or privilege decisions that consume source_url, script names, or other IPC string parameters via url::Origin::Create/HasWebUIScheme instead of an authenticated origin.
  • `//# sourceURL=` influence
    Look for browser-side logging, DevTools, or reporting paths that key behavior on the script/source URL a renderer attaches to console or error events.
  • Origin vs. reported-URL mismatch
    Audit service-worker and worker IPC handlers where the acting principal (version/StorageKey) is available but the code trusts a separately-transmitted URL parameter.
From d1967cd13713c3104c7751ebb2d536867cfb7385 Mon Sep 17 00:00:00 2001
From: Andrea Orru <andreaorru@chromium.org>
Date: Tue, 14 Jul 2026 18:42:34 -0700
Subject: [PATCH] [Service Workers] Use version origin for IsBuiltinComponent check

ServiceWorkerContextCore::OnReportConsoleMessage calculated
is_builtin_component using the renderer-provided source_url parameter.
Because standard web content can set source_url via the V8 //#
sourceURL= pragma, an unprivileged service worker could spoof a
chrome:// URL to bypass Incognito logging restrictions and escalate log
severity.

Fix this by evaluating is_builtin_component against the ServiceWorker
version's authenticated script_url() and key().origin() instead of
source_url.

Bug: 522291712
Change-Id: I224739dd6b4aa509925c187ed1f83ec92af12d39
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8088995
Reviewed-by: Yoshisato Yanagisawa <yyanagisawa@chromium.org>
Commit-Queue: Andrea Orru <andreaorru@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1662317}
---
diff --git a/content/browser/service_worker/service_worker_context_core.cc b/content/browser/service_worker/service_worker_context_core.cc
index d6bfd3c0..154e32f8 100644
--- a/content/browser/service_worker/service_worker_context_core.cc
+++ b/content/browser/service_worker/service_worker_context_core.cc
@@ -1385,9 +1385,9 @@
   DCHECK(browser_context);
   DCHECK_EQ(this, version->context().get());
   const bool is_builtin_component =
-      HasWebUIScheme(source_url) ||
+      HasWebUIScheme(version->script_url()) ||
       GetContentClient()->browser()->IsBuiltinComponent(
-          browser_context, url::Origin::Create(source_url));
+          browser_context, version->key().origin());
 
   LogConsoleMessage(message_level, message, line_number, is_builtin_component,
                     wrapper_->is_incognito(),
diff --git a/content/browser/service_worker/service_worker_context_core_unittest.cc b/content/browser/service_worker/service_worker_context_core_unittest.cc
index d92a3e9..1059fa9 100644
--- a/content/browser/service_worker/service_worker_context_core_unittest.cc
+++ b/content/browser/service_worker/service_worker_context_core_unittest.cc
@@ -15,7 +15,9 @@
 #include "content/browser/service_worker/service_worker_registration.h"
 #include "content/browser/service_worker/service_worker_test_utils.h"
 #include "content/browser/service_worker/service_worker_version.h"
+#include "content/public/common/content_client.h"
 #include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_content_browser_client.h"
 #include "content/public/test/test_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
@@ -373,4 +375,58 @@
   EXPECT_EQ(blink::ServiceWorkerStatusCode::kErrorFailed, status);
 }
 
+// Test browser client to capture the origin checked by `IsBuiltinComponent()`.
+class BuiltinComponentTestBrowserClient : public TestContentBrowserClient {
+ public:
+  bool IsBuiltinComponent(BrowserContext* browser_context,
+                          const url::Origin& origin) override {
+    last_queried_origin_ = origin;
+    return origin.scheme() == "chrome";
+  }
+
+  const std::optional<url::Origin>& last_queried_origin() const {
+    return last_queried_origin_;
+  }
+
+ private:
+  std::optional<url::Origin> last_queried_origin_;
+};
+
+// Ensures that `OnReportConsoleMessage` checks whether the service worker's
+// authenticated origin (rather than a renderer-provided `source_url` which can
+// be spoofed) is a built-in component. Regression test for crbug.com/522291712.
+TEST_F(ServiceWorkerContextCoreTest, OnReportConsoleMessageUsesVersionOrigin) {
+  BuiltinComponentTestBrowserClient test_browser_client;
+  ContentBrowserClient* old_browser_client =
+      SetBrowserClientForTesting(&test_browser_client);
+  base::ScopedClosureRunner reset_browser_client(base::BindOnce(
+      [](ContentBrowserClient* client) { SetBrowserClientForTesting(client); },
+      old_browser_client));
+
+  const GURL script("https://www.example.com/sw.js");
+  const GURL scope("https://www.example.com/");
+  const url::Origin origin = url::Origin::Create(scope);
+  const blink::StorageKey key = blink::StorageKey::CreateFirstParty(origin);
+
+  blink::mojom::ServiceWorkerRegistrationOptions options;
+  options.scope = scope;
+  scoped_refptr<ServiceWorkerRegistration> registration;
+  RegisterServiceWorker(script, key, options, &registration);
+  ASSERT_TRUE(registration->active_version());
+
+  // Report a console message with a spoofed source_url (e.g.
+  // chrome://settings/).
+  const GURL spoofed_source_url("chrome://settings/");
+  context()->OnReportConsoleMessage(
+      registration->active_version(),
+      blink::mojom::ConsoleMessageSource::kConsoleApi,
+      blink::mojom::ConsoleMessageLevel::kError, u"spoofed console message", 1,
+      spoofed_source_url);
+
+  // Verify that IsBuiltinComponent was called with the Service Worker's actual
+  // origin rather than the spoofed source_url origin.
+  ASSERT_TRUE(test_browser_client.last_queried_origin().has_value());
+  EXPECT_EQ(origin, test_browser_client.last_queried_origin().value());
+}
+
 }  // namespace content
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/service_worker/service_worker_context_core_unittest.cc b/content/browser/service_worker/service_worker_context_core_unittest.cc
index d92a3e9..1059fa9 100644
--- a/content/browser/service_worker/service_worker_context_core_unittest.cc
+++ b/content/browser/service_worker/service_worker_context_core_unittest.cc
@@ -15,7 +15,9 @@
 #include "content/browser/service_worker/service_worker_registration.h"
 #include "content/browser/service_worker/service_worker_test_utils.h"
 #include "content/browser/service_worker/service_worker_version.h"
+#include "content/public/common/content_client.h"
 #include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_content_browser_client.h"
 #include "content/public/test/test_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
@@ -373,4 +375,58 @@
   EXPECT_EQ(blink::ServiceWorkerStatusCode::kErrorFailed, status);
 }
 
+// Test browser client to capture the origin checked by `IsBuiltinComponent()`.
+class BuiltinComponentTestBrowserClient : public TestContentBrowserClient {
+ public:
+  bool IsBuiltinComponent(BrowserContext* browser_context,
+                          const url::Origin& origin) override {
+    last_queried_origin_ = origin;
+    return origin.scheme() == "chrome";
+  }
+
+  const std::optional<url::Origin>& last_queried_origin() const {
+    return last_queried_origin_;
+  }
+
+ private:
+  std::optional<url::Origin> last_queried_origin_;
+};
+
+// Ensures that `OnReportConsoleMessage` checks whether the service worker's
+// authenticated origin (rather than a renderer-provided `source_url` which can
+// be spoofed) is a built-in component. Regression test for crbug.com/522291712.
+TEST_F(ServiceWorkerContextCoreTest, OnReportConsoleMessageUsesVersionOrigin) {
+  BuiltinComponentTestBrowserClient test_browser_client;
+  ContentBrowserClient* old_browser_client =
+      SetBrowserClientForTesting(&test_browser_client);
+  base::ScopedClosureRunner reset_browser_client(base::BindOnce(
+      [](ContentBrowserClient* client) { SetBrowserClientForTesting(client); },
+      old_browser_client));
+
+  const GURL script("https://www.example.com/sw.js");
+  const GURL scope("https://www.example.com/");
+  const url::Origin origin = url::Origin::Create(scope);
+  const blink::StorageKey key = blink::StorageKey::CreateFirstParty(origin);
+
+  blink::mojom::ServiceWorkerRegistrationOptions options;
+  options.scope = scope;
+  scoped_refptr<ServiceWorkerRegistration> registration;
+  RegisterServiceWorker(script, key, options, &registration);
+  ASSERT_TRUE(registration->active_version());
+
+  // Report a console message with a spoofed source_url (e.g.
+  // chrome://settings/).
+  const GURL spoofed_source_url("chrome://settings/");
+  context()->OnReportConsoleMessage(
+      registration->active_version(),
+      blink::mojom::ConsoleMessageSource::kConsoleApi,
+      blink::mojom::ConsoleMessageLevel::kError, u"spoofed console message", 1,
+      spoofed_source_url);
+
+  // Verify that IsBuiltinComponent was called with the Service Worker's actual
+  // origin rather than the spoofed source_url origin.
+  ASSERT_TRUE(test_browser_client.last_queried_origin().has_value());
+  EXPECT_EQ(origin, test_browser_client.last_queried_origin().value());
+}
+
 }  // 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.