Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in UI
DescriptionInappropriate implementation in UI
ComponentUI
Bug ClassLogic Error
Tracker514063409
Fix commiteee6356c73bc (chromium/src) +113/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Changed Functions

FunctionChangeNotes
if
ui/base/cocoa/command_dispatcher.mm
modified
CommandDispatcherTest
ui/base/cocoa/command_dispatcher_unittest.mm
modified
TEST_F
ui/base/cocoa/command_dispatcher_unittest.mm
modified

Files Changed

  • ui/base/BUILD.gn
  • ui/base/cocoa/command_dispatcher.mm
  • ui/base/cocoa/command_dispatcher_unittest.mm
From eee6356c73bc7597e2be23640526b01f3f30d91b Mon Sep 17 00:00:00 2001
From: Bryan Oltman <[email protected]>
Date: Wed, 20 May 2026 18:22:24 -0700
Subject: [PATCH] [macOS] Drop redispatched events if window is no longer key

When an unhandled renderer event is returned for system processing,
the CommandDispatcher redispatches it. If the window has lost its
"key" status (e.g., due to a focus change while the event was
in-flight), redispatching it can cause the event to be incorrectly
handled by the newly focused window.

This CL ensures that we only redispatch events if the target window
is still the key window.

Fixed: 514063409
Change-Id: I022d7f45d5c35d26a96b4efc55c0621eb73b3a80
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7864509
Reviewed-by: Avi Drissman <[email protected]>
Commit-Queue: Bryan Oltman <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1633968}
---

diff --git a/ui/base/BUILD.gn b/ui/base/BUILD.gn
index 49a345c7..1add8ac9 100644
--- a/ui/base/BUILD.gn
+++ b/ui/base/BUILD.gn
@@ -1116,6 +1116,7 @@
       "cocoa/base_view_unittest.mm",
       "cocoa/bubble_closer_unittest.mm",
       "cocoa/cocoa_base_utils_unittest.mm",
+      "cocoa/command_dispatcher_unittest.mm",
       "cocoa/constrained_window/constrained_window_animation_unittest.mm",
       "cocoa/defaults_utils_unittest.mm",
       "cocoa/nsmenu_additions_unittest.mm",
diff --git a/ui/base/cocoa/command_dispatcher.mm b/ui/base/cocoa/command_dispatcher.mm
index 5b05c78..f1b8c36 100644
--- a/ui/base/cocoa/command_dispatcher.mm
+++ b/ui/base/cocoa/command_dispatcher.mm
@@ -197,6 +197,12 @@
   CHECK(eventType == NSEventTypeKeyDown || eventType == NSEventTypeKeyUp ||
         eventType == NSEventTypeFlagsChanged);
 
+  // If the event's window is no longer the key window, don't attempt to
+  // redispatch it (https://crbug.com/514063409).
+  if (event.window && !event.window.keyWindow) {
+    return NO;
+  }
+
   // Sometimes, an event will be redispatched from a child window to a parent
   // window to allow the parent window a chance to handle it. In that case, fix
   // up the native event to reference the correct window. Failure to do this can
diff --git a/ui/base/cocoa/command_dispatcher_unittest.mm b/ui/base/cocoa/command_dispatcher_unittest.mm
new file mode 100644
index 0000000..dc78fc4
--- /dev/null
+++ b/ui/base/cocoa/command_dispatcher_unittest.mm
@@ -0,0 +1,106 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/base/cocoa/command_dispatcher.h"
+
+#include "base/test/task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/gtest_mac.h"
+#import "ui/base/test/cocoa_helper.h"
+
+@interface TestCommandDispatchingWindow
+    : CocoaTestHelperWindow <CommandDispatchingWindow> {
+  CommandDispatcher* __strong _dispatcher;
+}
+@end
+
+@implementation TestCommandDispatchingWindow
+
+- (instancetype)initWithContentRect:(NSRect)contentRect {
+  if ((self = [super initWithContentRect:contentRect])) {
+    _dispatcher = [[CommandDispatcher alloc] initWithOwner:self];
+  }
+  return self;
+}
+
+- (CommandDispatcher*)commandDispatcher {
+  return _dispatcher;
+}
+
+- (NSWindow<CommandDispatchingWindow>*)commandDispatchParent {
+  return nil;
+}
+
+- (void)setCommandHandler:(id<UserInterfaceItemCommandHandler>)commandHandler {
+}
+
+- (BOOL)defaultPerformKeyEquivalent:(NSEvent*)event {
+  return NO;
+}
+
+- (BOOL)defaultValidateUserInterfaceItem:
+    (id<NSValidatedUserInterfaceItem>)item {
+  return NO;
+}
+
+- (void)commandDispatch:(id)sender {
+}
+
+- (void)commandDispatchUsingKeyModifiers:(id)sender {
+}
+
+@end
+
+namespace ui {
+
+class CommandDispatcherTest : public CocoaTest {
+ public:
+  void SetUp() override {
+    CocoaTest::SetUp();
+    window_ = [[TestCommandDispatchingWindow alloc] init];
+    key_event_ = [NSEvent keyEventWithType:NSEventTypeKeyDown
+                                  location:NSZeroPoint
+                             modifierFlags:0
+                                 timestamp:0
+                              windowNumber:window_.windowNumber
+                                   context:nil
+                                characters:@"a"
+               charactersIgnoringModifiers:@"a"
+                                 isARepeat:NO
+                                   keyCode:0];
+  }
+
+  void TearDown() override {
+    key_event_ = nil;
+    [window_ close];
+    window_ = nil;
+    CocoaTest::TearDown();
+  }
+
+  TestCommandDispatchingWindow* __strong window_;
+  NSEvent* __strong key_event_;
+  base::test::TaskEnvironment task_environment_{
+      base::test::TaskEnvironment::MainThreadType::UI};
+};
+
+// Verifies that -redispatchKeyEvent: drops redispatched events (unhandled
+// renderer events returned for system processing) when the window is no longer
+// key, as they would otherwise be incorrectly redirected to the now-key window.
+TEST_F(CommandDispatcherTest, RedispatchDropsEventIfWindowNotKey) {
+  CommandDispatcher* dispatcher = [window_ commandDispatcher];
+  window_.pretendIsKeyWindow = NO;
+  EXPECT_FALSE(window_.isKeyWindow);
+  EXPECT_FALSE([dispatcher redispatchKeyEvent:key_event_]);
+}
+
+// Tests that -redispatchKeyEvent: correctly redispatches events when the
+// window is still key, allowing normal system handling of unhandled keys.
+TEST_F(CommandDispatcherTest, RedispatchSendsEventIfWindowIsKey) {
+  CommandDispatcher* dispatcher = [window_ commandDispatcher];
+  window_.pretendIsKeyWindow = YES;
+  EXPECT_TRUE(window_.isKeyWindow);
+  EXPECT_TRUE([dispatcher redispatchKeyEvent:key_event_]);
+}
+
+}  // namespace ui
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/ui/base/cocoa/command_dispatcher_unittest.mm b/ui/base/cocoa/command_dispatcher_unittest.mm
new file mode 100644
index 0000000..dc78fc4
--- /dev/null
+++ b/ui/base/cocoa/command_dispatcher_unittest.mm
@@ -0,0 +1,106 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/base/cocoa/command_dispatcher.h"
+
+#include "base/test/task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/gtest_mac.h"
+#import "ui/base/test/cocoa_helper.h"
+
+@interface TestCommandDispatchingWindow
+    : CocoaTestHelperWindow <CommandDispatchingWindow> {
+  CommandDispatcher* __strong _dispatcher;
+}
+@end
+
+@implementation TestCommandDispatchingWindow
+
+- (instancetype)initWithContentRect:(NSRect)contentRect {
+  if ((self = [super initWithContentRect:contentRect])) {
+    _dispatcher = [[CommandDispatcher alloc] initWithOwner:self];
+  }
+  return self;
+}
+
+- (CommandDispatcher*)commandDispatcher {
+  return _dispatcher;
+}
+
+- (NSWindow<CommandDispatchingWindow>*)commandDispatchParent {
+  return nil;
+}
+
+- (void)setCommandHandler:(id<UserInterfaceItemCommandHandler>)commandHandler {
+}
+
+- (BOOL)defaultPerformKeyEquivalent:(NSEvent*)event {
+  return NO;
+}
+
+- (BOOL)defaultValidateUserInterfaceItem:
+    (id<NSValidatedUserInterfaceItem>)item {
+  return NO;
+}
+
+- (void)commandDispatch:(id)sender {
+}
+
+- (void)commandDispatchUsingKeyModifiers:(id)sender {
+}
+
+@end
+
+namespace ui {
+
+class CommandDispatcherTest : public CocoaTest {
+ public:
+  void SetUp() override {
+    CocoaTest::SetUp();
+    window_ = [[TestCommandDispatchingWindow alloc] init];
+    key_event_ = [NSEvent keyEventWithType:NSEventTypeKeyDown
+                                  location:NSZeroPoint
+                             modifierFlags:0
+                                 timestamp:0
+                              windowNumber:window_.windowNumber
+                                   context:nil
+                                characters:@"a"
+               charactersIgnoringModifiers:@"a"
+                                 isARepeat:NO
+                                   keyCode:0];
+  }
+
+  void TearDown() override {
+    key_event_ = nil;
+    [window_ close];
+    window_ = nil;
+    CocoaTest::TearDown();
+  }
+
+  TestCommandDispatchingWindow* __strong window_;
+  NSEvent* __strong key_event_;
+  base::test::TaskEnvironment task_environment_{
+      base::test::TaskEnvironment::MainThreadType::UI};
+};
+
+// Verifies that -redispatchKeyEvent: drops redispatched events (unhandled
+// renderer events returned for system processing) when the window is no longer
+// key, as they would otherwise be incorrectly redirected to the now-key window.
+TEST_F(CommandDispatcherTest, RedispatchDropsEventIfWindowNotKey) {
+  CommandDispatcher* dispatcher = [window_ commandDispatcher];
+  window_.pretendIsKeyWindow = NO;
+  EXPECT_FALSE(window_.isKeyWindow);
+  EXPECT_FALSE([dispatcher redispatchKeyEvent:key_event_]);
+}
+
+// Tests that -redispatchKeyEvent: correctly redispatches events when the
+// window is still key, allowing normal system handling of unhandled keys.
+TEST_F(CommandDispatcherTest, RedispatchSendsEventIfWindowIsKey) {
+  CommandDispatcher* dispatcher = [window_ commandDispatcher];
+  window_.pretendIsKeyWindow = YES;
+  EXPECT_TRUE(window_.isKeyWindow);
+  EXPECT_TRUE([dispatcher redispatchKeyEvent:key_event_]);
+}
+
+}  // namespace ui
Loading diff…

Original Bug Report

reported by [email protected]

Potential auto-confirmation of native macOS dialogs via keyboard event redispatch

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A logic flaw in the macOS keyboard event redispatching mechanism allows a malicious website to potentially auto-confirm native system dialogs. By delaying input acknowledgment, an attacker can ensure a redispatched ‘Enter’ key is delivered to a native panel after it gains focus. This bypasses user consent requirements for sensitive actions like file system access.

Affected files:

  • ui/base/cocoa/command_dispatcher.mm
  • chrome/browser/ui/views/frame/browser_native_widget_mac.mm
  • content/browser/renderer_host/render_widget_host_impl.cc
  • components/remote_cocoa/app_shim/native_widget_mac_nswindow.mm

Estimated timestamp from git blame: Unknown (Google3 checkout)

Summary

There is a potential logic flaw in how keyboard events are redispatched on macOS when they are left unhandled by a renderer. An attacker could exploit this to automatically confirm native system dialogs (e.g., NSSavePanel or NSOpenPanel) without explicit user intent. This specifically impacts security-sensitive APIs that rely on native dialogs for user consent, such as the File System Access API.

Technical Details

On macOS, Chromium uses a redispatch mechanism to allow the system to perform default actions for keyboard events that the renderer chooses not to handle. This logic resides in ui/base/cocoa/command_dispatcher.mm. When an input acknowledgment (ack) is received indicating the event was unhandled, [CommandDispatcher redispatchKeyEvent:] is called, which re-injects the original NSEvent via [NSApp sendEvent:event].

According to macOS AppKit behavior, [NSApplication sendEvent:] redirects KeyDown events to the current keyWindow if the event’s associated window is not focused. When a native dialog like NSSavePanel is opened (e.g., via window.showSaveFilePicker()), it immediately becomes the keyWindow for the application.

If the renderer delays the input ack (for example, by performing a synchronous busy loop in JavaScript immediately after calling the picker API), the browser will only attempt to redispatch the ‘Enter’ key after the native dialog is already visible and focused. AppKit will then deliver the ‘Enter’ event directly to the dialog, which interprets it as a user confirmation (clicking the default “Save” or “Open” button).

Existing protections in Chrome, such as the isEventBeingRedispatched: check, are only implemented in Chrome’s custom NativeWidgetMacNSWindow subclasses. Native system panels do not inherit from these classes and therefore do not benefit from these safeguards. Consequently, they treat the redispatched event as fresh user input.

Potential Attack Steps

  1. A user interacts with a malicious site and presses the ‘Enter’ key.
  2. The site’s keydown listener triggers a native dialog, such as window.showSaveFilePicker().
  3. The JavaScript execution continues and performs a synchronous delay (e.g., a short while loop) before completing the event handler.
  4. During this delay, the macOS native file picker appears and becomes the active keyWindow.
  5. Once the handler finishes and the ‘unhandled’ status is acked to the browser, the browser redispatches the ‘Enter’ key.
  6. NSApplication delivers the redispatched ‘Enter’ to the focused native dialog.
  7. The dialog confirms automatically, granting the site access to the file system at the suggested location.

Suggested Fix

The browser should verify that the window associated with the CommandDispatcher is still the keyWindow before proceeding with redispatchKeyEvent:. If the focus has shifted to a native panel or another window, the redispatch should be aborted to prevent accidental or malicious auto-confirmation. Additionally, implementing a short input lockout (similar to InputEventActivationProtector) for native macOS dialogs would provide a robust defense against such timing-based attacks.

Evaluated with Chrome root at commit: 1a8d40fc44df2088d5945c0bf53584038aa1614a


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker