Low watch CVSS 5.4 chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Low
Severity
5.4
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in DevTools
DescriptionInappropriate implementation in DevTools
ComponentDevTools
Bug ClassLogic 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.
Tracker496426191
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) marks front-ends as trusted (the real DevTools UI) or untrusted (e.g. extensions with the debugger permission), read via channel->GetClient()->IsTrusted().
`PageHandler`
the browser-side handler implementing the CDP Page domain, instantiated per session when the domain is available to the client.
`Page.setSPCTransactionMode`
an experimental command that puts the Secure Payment Confirmation dialog into an auto-accept/auto-reject mode, bypassing the UI where users review payee and amount.
`Page.setRPHRegistrationMode`
an experimental command that auto-accepts (or rejects) the custom protocol-handler (RPH) registration consent prompt.

Root Cause Analysis

This is the same commit referenced by CVE-2026-8006 (Fixed: 496426191, 496373088), framed here as an inappropriate-implementation/logic error: PageHandler implemented two consent-suppressing commands without conditioning their effect on the caller’s trust level. The implementation incorrectly treated SetSPCTransactionMode and SetRPHRegistrationMode as ordinary Page commands, so once the domain was exposed to an untrusted client the commands ran their dialog-mode logic unconditionally — the invariant that only a trusted client may override security-dialog behavior was never encoded in the methods.

The fix stores the connection trust bit in a new const bool is_trusted_ member (set from channel->GetClient()->IsTrusted() in ChromeDevToolsSession and passed through the PageHandler constructor) and makes each command early-return a permission-denied protocol::Response::ServerError(...) when !is_trusted_. Because the guard runs before the mode is applied, the corrected logic refuses untrusted callers outright.

Key insight
The core mistake was a missing trust condition inside two command implementations that override security-dialog behavior; the fix encodes the invariant directly as an is_trusted_ early-return in each method.

Attack Path

  1. Attach an untrusted client an extension with the debugger permission opens a CDP session on a page where PageHandler is constructed for untrusted clients.
  2. Call `Page.setSPCTransactionMode` pre-patch the command applied the requested transaction mode, auto-accepting SPC without the trusted review UI for payee/amount.
  3. Call `Page.setRPHRegistrationMode` pre-patch the command auto-accepted protocol-handler registration, bypassing the consent prompt.
  4. Abuse the suppressed consent proceed with an unreviewed payment authorization or an unconsented handler registration (the concrete tampered-payment outcome is advisory-stated as possible and marked (inferred)).

Impact Assessment

An untrusted DevTools client in the browser-process DevTools context could suppress user consent on SPC and protocol-handler dialogs, with the only precondition being an attached CDP session to the page. The advisory rates this low and classifies it as inappropriate implementation in DevTools; any downstream payment tampering is (inferred) from the advisory’s “could potentially” language, not demonstrated by the diff. No in-the-wild exploitation is indicated.

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

  • Mode/override commands without trust logic
    flag any CDP command whose effect is to change how a security dialog behaves; its implementation must check is_trusted_/IsTrusted() internally, not rely on domain exposure.
  • State carried but not enforced
    audit handlers where a trust/permission flag is stored but only some methods consult it, leaving sibling methods unguarded.
  • Experimental-command review
    experimental CDP commands often ship before their trust model is settled; review newly added Page/other-domain commands specifically for missing untrusted-client gating.
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};
 };
Loading diff…

Original Bug Report

reported by vm...@google.com

Bypass of Secure Payment Confirmation dialog via Page.setSPCTransactionMode

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

Overview: The experimental DevTools command Page.setSPCTransactionMode lacks trust restrictions, allowing extensions with the debugger permission to auto-accept Secure Payment Confirmation (SPC) dialogs. This bypasses the primary UI where users review transaction details (payee, amount), leaving only a generic OS-level biometric prompt that lacks context.

Affected files:

  • chrome/browser/devtools/protocol/page_handler.cc
  • chrome/browser/devtools/chrome_devtools_session.cc
  • third_party/blink/public/devtools_protocol/domains/Page.pdl
  • components/payments/content/secure_payment_confirmation_controller.cc
  • components/payments/content/secure_payment_confirmation_app.cc

Estimated timestamp from git blame: 2025-12-05

Disclaimer

Please note: As an AI agent (Fortify), I do not currently have the ability to run code or build a working Proof of Concept. The following analysis and steps are potential, based on a rigorous static analysis of the Chromium codebase.

Description

The Secure Payment Confirmation (SPC) API relies on a browser-level dialog to securely display the transaction amount and payee to the user before they authorize the payment. However, the DevTools command Page.setSPCTransactionMode (intended for automated testing) is improperly exposed to untrusted DevTools clients, such as Chrome extensions with the debugger permission.

By setting the transaction mode to autoAccept, an attacker can completely bypass the browser’s SPC dialog. The user will only see the subsequent OS-level biometric prompt (e.g., Windows Hello or macOS Touch ID). Because OS-level prompts are generic and do not display transaction details, the user can be tricked into authorizing a tampered payment (e.g., with a highly inflated amount or different payee) that they never had the opportunity to review.

Technical Details

  1. Lack of Command Restriction: In third_party/blink/public/devtools_protocol/domains/Page.pdl, the setSPCTransactionMode command is marked experimental but lacks the restricted attribute.
  2. Untrusted Client Access: In chrome/browser/devtools/chrome_devtools_session.cc, PageHandler is explicitly made available to untrusted clients. Therefore, extensions using the debugger API can invoke this command without an IsTrusted() block.
  3. UI Bypass: When invoked with autoAccept, PageHandler::SetSPCTransactionMode sets the mode to kAutoAccept in the PaymentRequestWebContentsManager. Later, when SecurePaymentConfirmationController::SetupModelAndShowDialogIfApplicable processes a PaymentRequest, it checks this mode. Seeing kAutoAccept, it immediately calls OnConfirm() synchronously, skipping the UI review phase entirely and directly triggering the WebAuthn authenticator.

Potential Attacker Steps

  1. An attacker convinces a user to install a malicious Chrome extension requiring the debugger permission.
  2. The extension attaches the debugger to a tab on a merchant site where the user has an enrolled SPC credential.
  3. The extension sends the command to auto-accept SPC prompts: chrome.debugger.sendCommand({tabId: targetTabId}, 'Page.setSPCTransactionMode', {mode: 'autoAccept'})
  4. The extension injects JavaScript (via Runtime.evaluate or a content script) to initiate a PaymentRequest, maliciously modifying the amount or payee_name.
  5. The browser instantly bypasses the SPC dialog. The user only sees a generic OS-level biometric prompt (e.g., “Verify your identity”).
  6. Unaware of the tampered details, the user completes the biometric challenge, returning a valid cryptographic assertion for the fraudulent transaction.

Suggested Fix

The command should be restricted to trusted DevTools clients. Modify third_party/blink/public/devtools_protocol/domains/Page.pdl to add the restricted attribute to setSPCTransactionMode:

  experimental restricted command setSPCTransactionMode

Alternatively, enforce a strict IsTrusted() or equivalent security check within the implementation of PageHandler::SetSPCTransactionMode.

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