Low watch CVSS 6.5 chrome Authorization Error 🔧 Commit mapped

Overview

Low
Severity
6.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in Safebrowsing
DescriptionIncorrect authorization in Safebrowsing
ComponentSafebrowsing
Sub-componentSafe Browsing
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.
Tracker501763003
Fix commitd592f083e77d (chromium/src) +44/-14
CWECWE-863
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-04-11 (per vendor advisory)
Fix landed2026-08-06 (public git)
Disclosed2026-09-08
Patch gap33 days — fix public in source before the advisory

Background

Safe Browsing interstitial
The full-page warning shown before an unsafe navigation, whose buttons and keyboard shortcuts emit SecurityInterstitialCommand values such as CMD_PROCEED and CMD_DONT_PROCEED.
`SafeBrowsingProceedAnywayDisabled` policy
An enterprise policy that, when active, is surfaced through is_proceed_anyway_disabled() and is meant to make the interstitial non-overridable so the user cannot continue to the unsafe site.
Keyboard override ("thisisunsafe")
A hidden shortcut that dispatches a CMD_PROCEED command to the interstitial without the visible “proceed” button, per the patch description bypassing policy checks.
Allowlist
A per-host record added in base_blocking_page.cc on proceed so that Safe Browsing stops re-showing the interstitial for that host.

Root Cause Analysis

In the committed-interstitial proceed path in base_blocking_page.cc, the code added the unsafe host to the allowlist whenever the received command was CMD_PROCEED, with no check of whether proceeding was actually permitted. When the SafeBrowsingProceedAnywayDisabled policy was active, the “thisisunsafe” keyboard shortcut still emitted CMD_PROCEED, so the host was allowlisted before the policy could force the navigation back to safety — violating the invariant that a policy-blocked interstitial must never grant an authorization the user is forbidden to grant. In the error-UI command handlers (safe_browsing_loud_error_ui.cc and safe_browsing_quiet_error_ui.cc), user_made_decision_ = true was also set unconditionally on CMD_PROCEED (and the CMD_PROCEED case appears to fall through into CMD_DONT_PROCEED, per the added comment), further recording a decision that policy did not allow.

The fix gates the allowlist addition on !sb_error_ui_->is_proceed_anyway_disabled(), moves user_made_decision_ = true inside the !is_proceed_anyway_disabled() branch, and guards the CMD_DONT_PROCEED assignment against a fallthrough from a policy-blocked CMD_PROCEED. It also publishes disableKeyboardOverride to the front-end so the keyboard shortcut can be suppressed when proceeding is disabled.

Key insight
The single core mistake was treating any CMD_PROCEED command as authorization to allowlist the host without consulting is_proceed_anyway_disabled(), so the keyboard override could allowlist a host the policy forbids proceeding to; the fix conditions both the allowlist write and the “user made a decision” bookkeeping on proceeding being permitted, and disables the keyboard override in the UI.

Attack Path

  1. Policy makes the interstitial non-overridable An administrator enables SafeBrowsingProceedAnywayDisabled, so is_proceed_anyway_disabled() is true and the user is expected to be unable to continue to a flagged host.
  2. User triggers the hidden keyboard override On the Safe Browsing interstitial the user types the “thisisunsafe” shortcut, which dispatches a CMD_PROCEED command even though the visible proceed control is disabled (per the patch description).
  3. Host is allowlisted before policy enforcement In the pre-patch base_blocking_page.cc proceed handler, CMD_PROCEED calls set_proceeded(true) and adds the host to the allowlist without checking is_proceed_anyway_disabled(), so the allowlist entry is written prematurely.
  4. Bypass persists past forced navigation Policy checks then force navigation away, but the host has already been allowlisted, so the safety decision the policy meant to prevent has effectively been recorded — an incorrect authorization. Any subsequent access to that host succeeding without the interstitial is (inferred), as the diff shows only the premature allowlist write, not later navigations.

Impact Assessment

An attacker (or a user acting against enterprise policy) gains the ability to defeat the SafeBrowsingProceedAnywayDisabled enforcement in the browser process, allowlisting a Safe-Browsing-flagged host despite a policy that should make the interstitial non-overridable. The precondition is that the policy is active and the user reaches the interstitial and issues the keyboard override; the advisory classifies this as incorrect authorization in Safe Browsing and does not report exploitation in the wild. Any consequence beyond the allowlisting itself — reaching malware/phishing content or further compromise — is (inferred), since the patch demonstrates only the premature allowlist write and the decision-recording bug.

Changed Functions

FunctionChangeNotes
switch
components/security_interstitials/core/safe_browsing_loud_error_ui.cc
modified
if
components/security_interstitials/core/safe_browsing_loud_error_ui.cc
modified
switch
components/security_interstitials/core/safe_browsing_quiet_error_ui.cc
modified

Files Changed

  • components/safe_browsing/content/browser/ui_manager_unittest.cc
  • components/security_interstitials/core/safe_browsing_loud_error_ui.cc
  • components/security_interstitials/core/safe_browsing_quiet_error_ui.cc
  • components/safe_browsing/content/browser/base_blocking_page.cc

Audit Directions

  • Command-to-side-effect gating
    Audit every interstitial CMD_PROCEED/proceed handler for state writes (allowlist adds, set_proceeded, user_made_decision_) that fire on the command alone without consulting is_proceed_anyway_disabled() or the equivalent authorization flag.
  • Keyboard/alternate override entry points
    Look for shortcuts or programmatic paths (like “thisisunsafe”) that emit proceed commands independently of the visible UI, and verify they respect the same policy checks and the new disableKeyboardOverride signal.
  • Switch-case fallthrough into decision recording
    Review command switch statements where a disabled CMD_PROCEED falls through to CMD_DONT_PROCEED, and confirm decision-recording assignments are guarded by the actual command rather than shared across cases.
From d592f083e77d44162c672637d66334cba1851561 Mon Sep 17 00:00:00 2001
From: Piotr Krzeszewski <krzeszewski@google.com>
Date: Thu, 06 Aug 2026 08:03:17 -0700
Subject: [PATCH] [Safe Browsing] Disable keyboard override when proceeding is disabled

When SafeBrowsingProceedAnywayDisabled policy is active, typing "thisisunsafe"
prematurely allowlisted the host before policy checks forced navigation away.

This change:
- Sets `disableKeyboardOverride` in SafeBrowsingLoudErrorUI.
- Prevents allowlisting in BaseBlockingPage if proceeding is disabled.
- Adds unit test coverage for policy enforcement.

Tested with local reproduction steps presented in the bug

Bug: 501763003
Change-Id: Idea6720c9a8c07c6bfb261e9d29d4e50734f84cc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8178244
Reviewed-by: thefrog <thefrog@chromium.org>
Reviewed-by: Michał Kaczmarczyk <mickaczmarczyk@google.com>
Commit-Queue: Piotr Krzeszewski <krzeszewski@google.com>
Reviewed-by: Dominique Fauteux-Chapleau <domfc@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1674980}
---
diff --git a/components/safe_browsing/content/browser/base_blocking_page.cc b/components/safe_browsing/content/browser/base_blocking_page.cc
index 4b07a2bc..d600077c 100644
--- a/components/safe_browsing/content/browser/base_blocking_page.cc
+++ b/components/safe_browsing/content/browser/base_blocking_page.cc
@@ -118,7 +118,8 @@
       static_cast<security_interstitials::SecurityInterstitialCommand>(command);
 
   if (interstitial_command ==
-      security_interstitials::SecurityInterstitialCommand::CMD_PROCEED) {
+          security_interstitials::SecurityInterstitialCommand::CMD_PROCEED &&
+      !sb_error_ui_->is_proceed_anyway_disabled()) {
     // With committed interstitials, OnProceed() doesn't get called, so handle
     // adding to the allow list here.
     set_proceeded(true);
diff --git a/components/security_interstitials/core/safe_browsing_loud_error_ui.cc b/components/security_interstitials/core/safe_browsing_loud_error_ui.cc
index e12e759..a154c82 100644
--- a/components/security_interstitials/core/safe_browsing_loud_error_ui.cc
+++ b/components/security_interstitials/core/safe_browsing_loud_error_ui.cc
@@ -86,6 +86,7 @@
       "primaryButtonText",
       l10n_util::GetStringUTF16(IDS_SAFEBROWSING_OVERRIDABLE_SAFETY_BUTTON));
   load_time_data.Set("overridable", !is_proceed_anyway_disabled());
+  load_time_data.Set("disableKeyboardOverride", is_proceed_anyway_disabled());
   load_time_data.Set(
       security_interstitials::kOptInLink,
       l10n_util::GetStringUTF16(IDS_SAFE_BROWSING_SCOUT_REPORTING_AGREE));
@@ -131,8 +132,8 @@
   switch (command) {
     case CMD_PROCEED: {
       // User pressed on the button to proceed.
-      user_made_decision_ = true;
       if (!is_proceed_anyway_disabled()) {
+        user_made_decision_ = true;
         controller()->metrics_helper()->RecordUserDecision(
             MetricsHelper::PROCEED);
         controller()->Proceed();
@@ -143,7 +144,11 @@
     }
     case CMD_DONT_PROCEED: {
       // User pressed on the button to return to safety.
-      user_made_decision_ = true;
+      // Only record a user decision if the command was actually
+      // CMD_DONT_PROCEED (not a fallthrough from a policy-blocked CMD_PROCEED).
+      if (command == CMD_DONT_PROCEED) {
+        user_made_decision_ = true;
+      }
       // Don't record the user action here because there are other ways of
       // triggering DontProceed, like clicking the back button.
       if (is_main_frame_load_pending()) {
diff --git a/components/security_interstitials/core/safe_browsing_quiet_error_ui.cc b/components/security_interstitials/core/safe_browsing_quiet_error_ui.cc
index 75baec1..7482022 100644
--- a/components/security_interstitials/core/safe_browsing_quiet_error_ui.cc
+++ b/components/security_interstitials/core/safe_browsing_quiet_error_ui.cc
@@ -83,8 +83,8 @@
   switch (command) {
     case CMD_PROCEED: {
       // User pressed on the button to proceed.
-      user_made_decision_ = true;
       if (!is_proceed_anyway_disabled()) {
+        user_made_decision_ = true;
         controller()->metrics_helper()->RecordUserDecision(
             MetricsHelper::PROCEED);
         controller()->Proceed();
diff --git a/components/safe_browsing/content/browser/ui_manager_unittest.cc b/components/safe_browsing/content/browser/ui_manager_unittest.cc
index 4ef14a1..7ed70e7 100644
--- a/components/safe_browsing/content/browser/ui_manager_unittest.cc
+++ b/components/safe_browsing/content/browser/ui_manager_unittest.cc
@@ -96,7 +96,8 @@
   TestSafeBrowsingBlockingPage(BaseUIManager* manager,
                                content::WebContents* web_contents,
                                const GURL& main_frame_url,
-                               const UnsafeResourceList& unsafe_resources)
+                               const UnsafeResourceList& unsafe_resources,
+                               bool is_proceed_anyway_disabled = false)
       : SafeBrowsingBlockingPage(
             manager,
             web_contents,
@@ -117,14 +118,14 @@
             BaseSafeBrowsingErrorUI::SBErrorDisplayOptions(
                 BaseBlockingPage::IsMainPageResourceLoadPending(
                     unsafe_resources),
-                false,                 // is_extended_reporting_opt_in_allowed
-                false,                 // is_off_the_record
-                false,                 // is_extended_reporting_enabled
-                false,                 // is_extended_reporting_policy_managed
-                false,                 // is_enhanced_protection_enabled
-                false,                 // is_proceed_anyway_disabled
-                false,                 // should_open_links_in_new_tab
-                true,                  // always_show_back_to_safety
+                false,  // is_extended_reporting_opt_in_allowed
+                false,  // is_off_the_record
+                false,  // is_extended_reporting_enabled
+                false,  // is_extended_reporting_policy_managed
+                false,  // is_enhanced_protection_enabled
+                is_proceed_anyway_disabled,  // is_proceed_anyway_disabled
+                false,                       // should_open_links_in_new_tab
+                true,                        // always_show_back_to_safety
                 false,                 // is_enhanced_protection_message_enabled
                 false,                 // is_safe_browsing_managed
                 "cpn_safe_browsing"),  // help_center_article_link
@@ -132,7 +133,7 @@
             /*navigation_observer_manager=*/nullptr,
             /*metrics_collector=*/nullptr,
             /*trigger_manager=*/nullptr,
-            /*is_proceed_anyway_disabled=*/false,
+            is_proceed_anyway_disabled,
             /*is_safe_browsing_surveys_enabled=*/true,
             /*trust_safety_sentiment_service_trigger=*/base::NullCallback(),
             /*ignore_auto_revocation_notifications_trigger=*/
@@ -824,4 +825,27 @@
       &threat_type));
 }
 
+TEST_F(SafeBrowsingUIManagerTest,
+       ProceedAnywayDisabled_CommandReceivedDoesNotAllowlist) {
+  security_interstitials::UnsafeResource resource =
+      MakeUnsafeResourceAndStartNavigation(kBadURL);
+  resource.threat_source = ThreatSource::LOCAL_PVER4;
+  std::vector<security_interstitials::UnsafeResource> resources = {resource};
+  TestSafeBrowsingBlockingPage blocking_page(
+      ui_manager(), web_contents(), GURL(kBadURL), resources,
+      /*is_proceed_anyway_disabled=*/true);
+
+  // 1. Verify frontend string population: disableKeyboardOverride must be true.
+  base::DictValue load_time_data;
+  blocking_page.sb_error_ui()->PopulateStringsForHtml(load_time_data);
+  std::optional<bool> disable_keyboard =
+      load_time_data.FindBool("disableKeyboardOverride");
+  ASSERT_TRUE(disable_keyboard.has_value());
+  EXPECT_TRUE(disable_keyboard.value());
+
+  // 2. Verify backend command handling: CMD_PROCEED (1) must not allowlist URL.
+  blocking_page.CommandReceived("1");
+  EXPECT_FALSE(IsAllowlisted(resource));
+}
+
 }  // namespace safe_browsing
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/components/safe_browsing/content/browser/ui_manager_unittest.cc b/components/safe_browsing/content/browser/ui_manager_unittest.cc
index 4ef14a1..7ed70e7 100644
--- a/components/safe_browsing/content/browser/ui_manager_unittest.cc
+++ b/components/safe_browsing/content/browser/ui_manager_unittest.cc
@@ -96,7 +96,8 @@
   TestSafeBrowsingBlockingPage(BaseUIManager* manager,
                                content::WebContents* web_contents,
                                const GURL& main_frame_url,
-                               const UnsafeResourceList& unsafe_resources)
+                               const UnsafeResourceList& unsafe_resources,
+                               bool is_proceed_anyway_disabled = false)
       : SafeBrowsingBlockingPage(
             manager,
             web_contents,
@@ -117,14 +118,14 @@
             BaseSafeBrowsingErrorUI::SBErrorDisplayOptions(
                 BaseBlockingPage::IsMainPageResourceLoadPending(
                     unsafe_resources),
-                false,                 // is_extended_reporting_opt_in_allowed
-                false,                 // is_off_the_record
-                false,                 // is_extended_reporting_enabled
-                false,                 // is_extended_reporting_policy_managed
-                false,                 // is_enhanced_protection_enabled
-                false,                 // is_proceed_anyway_disabled
-                false,                 // should_open_links_in_new_tab
-                true,                  // always_show_back_to_safety
+                false,  // is_extended_reporting_opt_in_allowed
+                false,  // is_off_the_record
+                false,  // is_extended_reporting_enabled
+                false,  // is_extended_reporting_policy_managed
+                false,  // is_enhanced_protection_enabled
+                is_proceed_anyway_disabled,  // is_proceed_anyway_disabled
+                false,                       // should_open_links_in_new_tab
+                true,                        // always_show_back_to_safety
                 false,                 // is_enhanced_protection_message_enabled
                 false,                 // is_safe_browsing_managed
                 "cpn_safe_browsing"),  // help_center_article_link
@@ -132,7 +133,7 @@
             /*navigation_observer_manager=*/nullptr,
             /*metrics_collector=*/nullptr,
             /*trigger_manager=*/nullptr,
-            /*is_proceed_anyway_disabled=*/false,
+            is_proceed_anyway_disabled,
             /*is_safe_browsing_surveys_enabled=*/true,
             /*trust_safety_sentiment_service_trigger=*/base::NullCallback(),
             /*ignore_auto_revocation_notifications_trigger=*/
@@ -824,4 +825,27 @@
       &threat_type));
 }
 
+TEST_F(SafeBrowsingUIManagerTest,
+       ProceedAnywayDisabled_CommandReceivedDoesNotAllowlist) {
+  security_interstitials::UnsafeResource resource =
+      MakeUnsafeResourceAndStartNavigation(kBadURL);
+  resource.threat_source = ThreatSource::LOCAL_PVER4;
+  std::vector<security_interstitials::UnsafeResource> resources = {resource};
+  TestSafeBrowsingBlockingPage blocking_page(
+      ui_manager(), web_contents(), GURL(kBadURL), resources,
+      /*is_proceed_anyway_disabled=*/true);
+
+  // 1. Verify frontend string population: disableKeyboardOverride must be true.
+  base::DictValue load_time_data;
+  blocking_page.sb_error_ui()->PopulateStringsForHtml(load_time_data);
+  std::optional<bool> disable_keyboard =
+      load_time_data.FindBool("disableKeyboardOverride");
+  ASSERT_TRUE(disable_keyboard.has_value());
+  EXPECT_TRUE(disable_keyboard.value());
+
+  // 2. Verify backend command handling: CMD_PROCEED (1) must not allowlist URL.
+  blocking_page.CommandReceived("1");
+  EXPECT_FALSE(IsAllowlisted(resource));
+}
+
 }  // namespace safe_browsing
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.