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 FileHandling
DescriptionIncorrect authorization in FileHandling
ComponentFileHandling
Sub-componentPWA
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.
Tracker537101736
Fix commita245abe0732f (chromium/src) +51/-1
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-07-21 (per vendor advisory)
Fix landed2026-08-10 (public git)
Disclosed2026-09-08
Patch gap29 days — fix public in source before the advisory

Background

Web app launch consent dialogs
the Views dialogs shown before a PWA handles a file or protocol, implemented by LaunchAppUserChoiceDialogView (base class) and its subclasses FileHandlerLaunchDialogView and ProtocolHandlerLaunchDialogView.
Keyjacking
an attack where a dialog is dismissed/accepted by key presses the user did not intend for it, e.g. queued Enter presses landing on a dialog the moment it appears.
`SetDefaultButton()`
sets which button (ui::mojom::DialogButton::kOk or kCancel) is activated when the user presses Enter.
`ShouldAllowKeyEventsDuringInputProtection()`
a views::DialogDelegate override; returning false enforces the input-activation delay (about 500ms) during which key events are ignored so a freshly shown dialog cannot be instantly actuated.

Root Cause Analysis

The web app launch consent dialogs could be accepted immediately by keyboard input at the moment they appeared, because two protections were absent in the shared base class. First, the default button was effectively OK for the File Handler dialog, so a stray or attacker-timed Enter key press accepted the launch rather than canceling it. Second, LaunchAppUserChoiceDialogView did not override ShouldAllowKeyEventsDuringInputProtection(), so key events were honored during the brief input-activation delay that is meant to filter out input the user did not deliberately aim at the just-shown dialog. This violated the invariant that a security-consequential consent dialog should not be actionable by input arriving before the user has had time to perceive it, and should default to the safe (deny) choice.

The fix adds SetDefaultButton(kCancel) in the base class constructor so Enter defaults to canceling, and overrides ShouldAllowKeyEventsDuringInputProtection() to return false so key events are suppressed during the activation delay; the now-redundant SetDefaultButton(kCancel) in ProtocolHandlerLaunchDialogView is removed since the base class handles it.

Key insight
The core mistake was that the launch consent dialogs defaulted to accepting (OK) on Enter and processed key events during the input-protection window; the fix makes Cancel the default button and returns false from ShouldAllowKeyEventsDuringInputProtection() so rapid/premature key presses cannot silently confirm the launch.

Attack Path

  1. Trigger a launch dialog A malicious page or file causes Chrome to show a PWA file-handler or protocol-handler launch consent dialog (e.g. via a registered protocol or file handler).
  2. Queue key input The attacker arranges for Enter key presses to arrive as the dialog appears, for instance by getting the user to hold or repeatedly press a key while the page navigates or invokes the handler (inferred timing technique).
  3. Bypass the delay Pre-patch, key events were allowed during the input-activation delay, so an Enter press landing right after the dialog opened was processed instead of being ignored.
  4. Activate the default (accept) Because the default button was OK (for the File Handler dialog), the processed Enter activated acceptance, launching the app / handling the file or protocol without deliberate user consent.
  5. Consequence The web app launches and handles the attacker-influenced file or protocol; any further impact beyond the unintended launch is not shown by the diff (inferred).

Impact Assessment

An attacker could bypass the user’s consent on PWA file- and protocol-handler launch dialogs via rapid or mistimed key presses, causing a launch/handling action the user did not deliberately approve, in the browser UI. The advisory classifies this as incorrect authorization in FileHandling and does not report exploitation in the wild; the precondition is getting the consent dialog shown and key input to arrive during its activation window. This is a consent/UI-security issue — any escalation beyond the unintended handler launch is (inferred).

Changed Functions

FunctionChangeNotes
IN_PROC_BROWSER_TEST_F
chrome/browser/ui/views/web_apps/file_handler_launch_dialog_browsertest.cc
modified

Files Changed

  • chrome/browser/ui/views/web_apps/protocol_handler_launch_dialog_view.cc
  • chrome/browser/ui/views/web_apps/launch_app_user_choice_dialog_view.cc
  • chrome/browser/ui/views/web_apps/file_handler_launch_dialog_browsertest.cc
  • chrome/browser/ui/views/web_apps/launch_app_user_choice_dialog_view.h
  • chrome/browser/ui/views/web_apps/protocol_handler_launch_dialog_browsertest.cc

Audit Directions

  • Default-accept consent dialogs
    Audit security-consequential dialogs for a default button set to OK/accept; consent/permission dialogs should default to Cancel/deny so a stray Enter is safe.
  • Missing input-protection overrides
    Look for views::DialogDelegate subclasses that gate an action but do not override ShouldAllowKeyEventsDuringInputProtection() to return false, leaving them keyjackable during the activation delay.
  • Base-vs-subclass protection gaps
    When a protection is added per-subclass (as SetDefaultButton(kCancel) was only in the protocol dialog), verify every sibling dialog inherits it; hoisting to the shared base class prevents one subclass (here the File Handler dialog) from silently lacking it.
From a245abe0732f3d9f180163b2de0ab52f54ec59c1 Mon Sep 17 00:00:00 2001
From: Kunjan Vinay Patel <ksukh@google.com>
Date: Mon, 10 Aug 2026 14:31:48 -0700
Subject: [PATCH] [PWA] Prevent keyjacking on web app launch consent dialogs.

This change enhances security for web app launch dialogs (covering both
File and Protocol handlers) to prevent accidental or malicious bypasses
via rapid key presses:

- Sets the default focused button to 'Cancel' in the base class
   LaunchAppUserChoiceDialogView.
- Enforces the 500ms input activation delay for keyboard events by
   overriding ShouldAllowKeyEventsDuringInputProtection() to return
   false.

Previously, these dialogs could be immediately accepted via the 'Enter'
key because the default button was 'OK' (specifically for File Handlers)
and key events were not blocked during the initial activation delay.

Bug: 537101736
Change-Id: Ie7396ee149ca498e0df19441c548c8eaa22e9d42
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8226085
Reviewed-by: Dibyajyoti Pal <dibyapal@chromium.org>
Commit-Queue: Kunjan Patel <ksukh@chromium.org>
Reviewed-by: Luke Deng <lukedeng@google.com>
Cr-Commit-Position: refs/heads/main@{#1676726}
---
diff --git a/chrome/browser/ui/views/web_apps/launch_app_user_choice_dialog_view.cc b/chrome/browser/ui/views/web_apps/launch_app_user_choice_dialog_view.cc
index 4141892..003b136 100644
--- a/chrome/browser/ui/views/web_apps/launch_app_user_choice_dialog_view.cc
+++ b/chrome/browser/ui/views/web_apps/launch_app_user_choice_dialog_view.cc
@@ -83,6 +83,8 @@
   SetCanResize(false);
   set_draggable(true);
 
+  SetDefaultButton(static_cast<int>(ui::mojom::DialogButton::kCancel));
+
   SetAcceptCallback(base::BindOnce(&LaunchAppUserChoiceDialogView::OnAccepted,
                                    base::Unretained(this)));
 
@@ -233,6 +235,11 @@
       gfx::ImageSkia::CreateFrom1xBitmap(std::move(masked_bitmap))));
 }
 
+bool LaunchAppUserChoiceDialogView::ShouldAllowKeyEventsDuringInputProtection()
+    const {
+  return false;
+}
+
 BEGIN_METADATA(LaunchAppUserChoiceDialogView)
 END_METADATA
 
diff --git a/chrome/browser/ui/views/web_apps/launch_app_user_choice_dialog_view.h b/chrome/browser/ui/views/web_apps/launch_app_user_choice_dialog_view.h
index 406708d3..7c164a5b 100644
--- a/chrome/browser/ui/views/web_apps/launch_app_user_choice_dialog_view.h
+++ b/chrome/browser/ui/views/web_apps/launch_app_user_choice_dialog_view.h
@@ -46,6 +46,9 @@
 
   void Init();
 
+  // views::DialogDelegate:
+  bool ShouldAllowKeyEventsDuringInputProtection() const override;
+
   static void SetDefaultRememberSelectionForTesting(bool remember_selection);
 
  protected:
diff --git a/chrome/browser/ui/views/web_apps/protocol_handler_launch_dialog_view.cc b/chrome/browser/ui/views/web_apps/protocol_handler_launch_dialog_view.cc
index 94f1812..32b758e 100644
--- a/chrome/browser/ui/views/web_apps/protocol_handler_launch_dialog_view.cc
+++ b/chrome/browser/ui/views/web_apps/protocol_handler_launch_dialog_view.cc
@@ -39,7 +39,6 @@
   SetButtonLabel(
       ui::mojom::DialogButton::kCancel,
       l10n_util::GetStringUTF16(IDS_WEB_APP_PERMISSION_NEGATIVE_BUTTON));
-  SetDefaultButton(static_cast<int>(ui::mojom::DialogButton::kCancel));
 }
 
 ProtocolHandlerLaunchDialogView::~ProtocolHandlerLaunchDialogView() = default;
diff --git a/chrome/browser/ui/views/web_apps/file_handler_launch_dialog_browsertest.cc b/chrome/browser/ui/views/web_apps/file_handler_launch_dialog_browsertest.cc
index 6aa7bdb2..0af938e 100644
--- a/chrome/browser/ui/views/web_apps/file_handler_launch_dialog_browsertest.cc
+++ b/chrome/browser/ui/views/web_apps/file_handler_launch_dialog_browsertest.cc
@@ -226,6 +226,24 @@
   EXPECT_EQ(1U, GlobalBrowserCollection::GetInstance()->GetSize());
 }
 
+IN_PROC_BROWSER_TEST_F(FileHandlerLaunchDialogTest,
+                       DefaultButtonAndInputProtection) {
+  views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
+                                       "FileHandlerLaunchDialogView");
+  LaunchAppWithFiles({{base::FilePath::FromASCII("foo.txt")}});
+  views::Widget* widget = waiter.WaitIfNeededAndGet();
+  ASSERT_NE(widget, nullptr);
+  views::DialogDelegate* dialog_delegate =
+      widget->widget_delegate()->AsDialogDelegate();
+  ASSERT_NE(dialog_delegate, nullptr);
+
+  EXPECT_EQ(dialog_delegate->GetDefaultDialogButton(),
+            static_cast<int>(ui::mojom::DialogButton::kCancel));
+  EXPECT_FALSE(dialog_delegate->ShouldAllowKeyEventsDuringInputProtection());
+
+  views::test::CancelDialog(widget);
+}
+
 IN_PROC_BROWSER_TEST_F(FileHandlerLaunchDialogTest, DisallowAndRemember) {
   // One normal browser window exists.
   EXPECT_EQ(1U, GlobalBrowserCollection::GetInstance()->GetSize());
diff --git a/chrome/browser/ui/views/web_apps/protocol_handler_launch_dialog_browsertest.cc b/chrome/browser/ui/views/web_apps/protocol_handler_launch_dialog_browsertest.cc
index 868700c..00c5c32 100644
--- a/chrome/browser/ui/views/web_apps/protocol_handler_launch_dialog_browsertest.cc
+++ b/chrome/browser/ui/views/web_apps/protocol_handler_launch_dialog_browsertest.cc
@@ -108,6 +108,29 @@
 }
 
 IN_PROC_BROWSER_TEST_F(ProtocolHandlerLaunchDialogBrowserTest,
+                       DefaultButtonAndInputProtection) {
+  views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
+                                       "ProtocolHandlerLaunchDialogView");
+  GURL protocol_url("web+test://test");
+  webapps::AppId test_app_id = InstallTestWebApp(browser()->GetProfile());
+
+  ShowWebAppProtocolLaunchDialog(protocol_url, browser()->GetProfile(),
+                                 test_app_id, base::DoNothing());
+
+  views::Widget* widget = waiter.WaitIfNeededAndGet();
+  ASSERT_NE(widget, nullptr);
+  views::DialogDelegate* dialog_delegate =
+      widget->widget_delegate()->AsDialogDelegate();
+  ASSERT_NE(dialog_delegate, nullptr);
+
+  EXPECT_EQ(dialog_delegate->GetDefaultDialogButton(),
+            static_cast<int>(ui::mojom::DialogButton::kCancel));
+  EXPECT_FALSE(dialog_delegate->ShouldAllowKeyEventsDuringInputProtection());
+
+  views::test::CancelDialog(widget);
+}
+
+IN_PROC_BROWSER_TEST_F(ProtocolHandlerLaunchDialogBrowserTest,
                        ProtocolHandlerIntentPickerDialog_DisallowAndRemember) {
   ProtocolHandlerLaunchDialogView::SetDefaultRememberSelectionForTesting(true);
   ShowDialogAndCloseWithReason(
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/ui/views/web_apps/file_handler_launch_dialog_browsertest.cc b/chrome/browser/ui/views/web_apps/file_handler_launch_dialog_browsertest.cc
index 6aa7bdb2..0af938e 100644
--- a/chrome/browser/ui/views/web_apps/file_handler_launch_dialog_browsertest.cc
+++ b/chrome/browser/ui/views/web_apps/file_handler_launch_dialog_browsertest.cc
@@ -226,6 +226,24 @@
   EXPECT_EQ(1U, GlobalBrowserCollection::GetInstance()->GetSize());
 }
 
+IN_PROC_BROWSER_TEST_F(FileHandlerLaunchDialogTest,
+                       DefaultButtonAndInputProtection) {
+  views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
+                                       "FileHandlerLaunchDialogView");
+  LaunchAppWithFiles({{base::FilePath::FromASCII("foo.txt")}});
+  views::Widget* widget = waiter.WaitIfNeededAndGet();
+  ASSERT_NE(widget, nullptr);
+  views::DialogDelegate* dialog_delegate =
+      widget->widget_delegate()->AsDialogDelegate();
+  ASSERT_NE(dialog_delegate, nullptr);
+
+  EXPECT_EQ(dialog_delegate->GetDefaultDialogButton(),
+            static_cast<int>(ui::mojom::DialogButton::kCancel));
+  EXPECT_FALSE(dialog_delegate->ShouldAllowKeyEventsDuringInputProtection());
+
+  views::test::CancelDialog(widget);
+}
+
 IN_PROC_BROWSER_TEST_F(FileHandlerLaunchDialogTest, DisallowAndRemember) {
   // One normal browser window exists.
   EXPECT_EQ(1U, GlobalBrowserCollection::GetInstance()->GetSize());
diff --git a/chrome/browser/ui/views/web_apps/protocol_handler_launch_dialog_browsertest.cc b/chrome/browser/ui/views/web_apps/protocol_handler_launch_dialog_browsertest.cc
index 868700c..00c5c32 100644
--- a/chrome/browser/ui/views/web_apps/protocol_handler_launch_dialog_browsertest.cc
+++ b/chrome/browser/ui/views/web_apps/protocol_handler_launch_dialog_browsertest.cc
@@ -108,6 +108,29 @@
 }
 
 IN_PROC_BROWSER_TEST_F(ProtocolHandlerLaunchDialogBrowserTest,
+                       DefaultButtonAndInputProtection) {
+  views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
+                                       "ProtocolHandlerLaunchDialogView");
+  GURL protocol_url("web+test://test");
+  webapps::AppId test_app_id = InstallTestWebApp(browser()->GetProfile());
+
+  ShowWebAppProtocolLaunchDialog(protocol_url, browser()->GetProfile(),
+                                 test_app_id, base::DoNothing());
+
+  views::Widget* widget = waiter.WaitIfNeededAndGet();
+  ASSERT_NE(widget, nullptr);
+  views::DialogDelegate* dialog_delegate =
+      widget->widget_delegate()->AsDialogDelegate();
+  ASSERT_NE(dialog_delegate, nullptr);
+
+  EXPECT_EQ(dialog_delegate->GetDefaultDialogButton(),
+            static_cast<int>(ui::mojom::DialogButton::kCancel));
+  EXPECT_FALSE(dialog_delegate->ShouldAllowKeyEventsDuringInputProtection());
+
+  views::test::CancelDialog(widget);
+}
+
+IN_PROC_BROWSER_TEST_F(ProtocolHandlerLaunchDialogBrowserTest,
                        ProtocolHandlerIntentPickerDialog_DisallowAndRemember) {
   ProtocolHandlerLaunchDialogView::SetDefaultRememberSelectionForTesting(true);
   ShowDialogAndCloseWithReason(
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.