Low chrome Logic Error 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMissing authorization in DOM
DescriptionMissing authorization in DOM
ComponentDOM
Bug ClassLogic Error
Tracker537476242
Fix commit78f4142f505e (chromium/src) +109/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-09-08

Changed Functions

FunctionChangeNotes
TEST_F
third_party/blink/renderer/core/page/focusgroup_controller_test.cc
modified

Files Changed

  • third_party/blink/renderer/core/page/focusgroup_controller.cc
  • third_party/blink/renderer/core/page/focusgroup_controller_test.cc
From 78f4142f505e1aeca80ce1bff1dfb1f3737d122a Mon Sep 17 00:00:00 2001
From: Jacques Newman <janewman@microsoft.com>
Date: Wed, 29 Jul 2026 21:07:04 -0700
Subject: [PATCH] [focusgroup] Ignore untrusted keyboard navigation

FocusgroupController accepted script-created keydown events and moved
focus with a forward or backward focus type, marking the target as
focused from a user gesture.

Reject untrusted keyboard events before focusgroup navigation. Add
coverage for both navigation directions while preserving trusted
keyboard behavior.

Fixed: 537476242
Bug: 40210717
Change-Id: Ie75fc020926ecb8f143dd00534a685a3951e222b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8162543
Commit-Queue: Jacques Newman <janewman@microsoft.com>
Reviewed-by: Mason Freed <masonf@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1670818}
---

diff --git a/third_party/blink/renderer/core/page/focusgroup_controller.cc b/third_party/blink/renderer/core/page/focusgroup_controller.cc
index e9a3d8e..c7fadff 100644
--- a/third_party/blink/renderer/core/page/focusgroup_controller.cc
+++ b/third_party/blink/renderer/core/page/focusgroup_controller.cc
@@ -29,6 +29,9 @@
                                                const LocalFrame* frame) {
   CHECK(frame);
   CHECK(frame->DomWindow());
+  if (!event->isTrusted()) {
+    return false;
+  }
   ExecutionContext* context = frame->DomWindow()->GetExecutionContext();
   if (!RuntimeEnabledFeatures::FocusgroupEnabled(context)) {
     return false;
diff --git a/third_party/blink/renderer/core/page/focusgroup_controller_test.cc b/third_party/blink/renderer/core/page/focusgroup_controller_test.cc
index ccd2b630..7c40d86 100644
--- a/third_party/blink/renderer/core/page/focusgroup_controller_test.cc
+++ b/third_party/blink/renderer/core/page/focusgroup_controller_test.cc
@@ -8,6 +8,7 @@
 
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/input/web_keyboard_event.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_keyboard_event_init.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/shadow_root.h"
 #include "third_party/blink/renderer/core/events/keyboard_event.h"
@@ -16,6 +17,7 @@
 #include "third_party/blink/renderer/core/html/html_dialog_element.h"
 #include "third_party/blink/renderer/core/html/html_element.h"
 #include "third_party/blink/renderer/core/input/event_handler.h"
+#include "third_party/blink/renderer/core/keywords.h"
 #include "third_party/blink/renderer/core/page/focusgroup_controller_utils.h"
 #include "third_party/blink/renderer/core/page/grid_focusgroup_structure_info.h"
 #include "third_party/blink/renderer/core/testing/page_test_base.h"
@@ -46,6 +48,16 @@
     return event;
   }
 
+  KeyboardEvent* UntrustedKeyDownEvent(const String& key, Element* target) {
+    KeyboardEventInit* init = KeyboardEventInit::Create();
+    init->setBubbles(true);
+    init->setKey(key);
+    auto* event =
+        MakeGarbageCollected<KeyboardEvent>(event_type_names::kKeydown, init);
+    event->SetTarget(target);
+    return event;
+  }
+
   void SendEvent(KeyboardEvent* event) {
     if (event->target()) {
       event->target()->DispatchEvent(*event);
@@ -1110,6 +1122,100 @@
   ASSERT_EQ(GetDocument().FocusedElement(), item1);
 }
 
+TEST_F(FocusgroupControllerTest,
+       UntrustedArrowDownDoesNotMoveFocusOrMarkUserGesture) {
+  GetDocument().body()->SetInnerHTMLWithoutTrustedTypes(R"HTML(
+    <div focusgroup="menu block">
+      <div id=first tabindex=0>First</div>
+      <div id=spellcheck tabindex=0 contenteditable spellcheck=true>
+        Spellcheck target
+      </div>
+    </div>
+  )HTML");
+  UpdateAllLifecyclePhasesForTest();
+
+  auto* first = GetElementById("first");
+  auto* spellcheck = GetElementById("spellcheck");
+  ASSERT_TRUE(first);
+  ASSERT_TRUE(spellcheck);
+
+  first->Focus();
+  ASSERT_EQ(GetDocument().FocusedElement(), first);
+  ASSERT_FALSE(spellcheck->WasLastFocusFromUserGesture());
+
+  auto* event = UntrustedKeyDownEvent(keywords::kArrowDown, first);
+  ASSERT_FALSE(event->isTrusted());
+  EXPECT_FALSE(FocusgroupController::HandleKeyboardEvent(
+      event, GetDocument().GetFrame()));
+  EXPECT_EQ(GetDocument().FocusedElement(), first);
+  EXPECT_FALSE(spellcheck->WasLastFocusFromUserGesture());
+}
+
+TEST_F(FocusgroupControllerTest,
+       UntrustedArrowUpDoesNotMoveFocusOrMarkUserGesture) {
+  GetDocument().body()->SetInnerHTMLWithoutTrustedTypes(R"HTML(
+    <div focusgroup="menu block">
+      <div id=spellcheck tabindex=0 contenteditable spellcheck=true>
+        Spellcheck target
+      </div>
+      <div id=last tabindex=0>Last</div>
+    </div>
+  )HTML");
+  UpdateAllLifecyclePhasesForTest();
+
+  auto* spellcheck = GetElementById("spellcheck");
+  auto* last = GetElementById("last");
+  ASSERT_TRUE(spellcheck);
+  ASSERT_TRUE(last);
+
+  last->Focus();
+  ASSERT_EQ(GetDocument().FocusedElement(), last);
+  ASSERT_FALSE(spellcheck->WasLastFocusFromUserGesture());
+
+  auto* event = UntrustedKeyDownEvent(keywords::kArrowUp, last);
+  ASSERT_FALSE(event->isTrusted());
+  EXPECT_FALSE(FocusgroupController::HandleKeyboardEvent(
+      event, GetDocument().GetFrame()));
+  EXPECT_EQ(GetDocument().FocusedElement(), last);
+  EXPECT_FALSE(spellcheck->WasLastFocusFromUserGesture());
+}
+
+TEST_F(FocusgroupControllerTest, TrustedArrowKeysMoveFocusAndMarkUserGesture) {
+  GetDocument().body()->SetInnerHTMLWithoutTrustedTypes(R"HTML(
+    <div focusgroup="menu block">
+      <div id=first tabindex=0>First</div>
+      <div id=spellcheck tabindex=0 contenteditable spellcheck=true>
+        Spellcheck target
+      </div>
+      <div id=last tabindex=0>Last</div>
+    </div>
+  )HTML");
+  UpdateAllLifecyclePhasesForTest();
+
+  auto* first = GetElementById("first");
+  auto* spellcheck = GetElementById("spellcheck");
+  auto* last = GetElementById("last");
+  ASSERT_TRUE(first);
+  ASSERT_TRUE(spellcheck);
+  ASSERT_TRUE(last);
+
+  first->Focus();
+  ASSERT_EQ(GetDocument().FocusedElement(), first);
+  ASSERT_FALSE(spellcheck->WasLastFocusFromUserGesture());
+
+  SendArrowDown(first);
+  EXPECT_EQ(GetDocument().FocusedElement(), spellcheck);
+  EXPECT_TRUE(spellcheck->WasLastFocusFromUserGesture());
+
+  last->Focus();
+  ASSERT_EQ(GetDocument().FocusedElement(), last);
+  ASSERT_FALSE(spellcheck->WasLastFocusFromUserGesture());
+
+  SendArrowUp(last);
+  EXPECT_EQ(GetDocument().FocusedElement(), spellcheck);
+  EXPECT_TRUE(spellcheck->WasLastFocusFromUserGesture());
+}
+
 TEST_F(FocusgroupControllerTest, NestedFocusgroupsHaveSeparateScopes) {
   GetDocument().body()->SetHTMLUnsafeWithoutTrustedTypes(R"HTML(
     <div id=outer focusgroup="toolbar">
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/renderer/core/page/focusgroup_controller_test.cc b/third_party/blink/renderer/core/page/focusgroup_controller_test.cc
index ccd2b630..7c40d86 100644
--- a/third_party/blink/renderer/core/page/focusgroup_controller_test.cc
+++ b/third_party/blink/renderer/core/page/focusgroup_controller_test.cc
@@ -8,6 +8,7 @@
 
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/input/web_keyboard_event.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_keyboard_event_init.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/shadow_root.h"
 #include "third_party/blink/renderer/core/events/keyboard_event.h"
@@ -16,6 +17,7 @@
 #include "third_party/blink/renderer/core/html/html_dialog_element.h"
 #include "third_party/blink/renderer/core/html/html_element.h"
 #include "third_party/blink/renderer/core/input/event_handler.h"
+#include "third_party/blink/renderer/core/keywords.h"
 #include "third_party/blink/renderer/core/page/focusgroup_controller_utils.h"
 #include "third_party/blink/renderer/core/page/grid_focusgroup_structure_info.h"
 #include "third_party/blink/renderer/core/testing/page_test_base.h"
@@ -46,6 +48,16 @@
     return event;
   }
 
+  KeyboardEvent* UntrustedKeyDownEvent(const String& key, Element* target) {
+    KeyboardEventInit* init = KeyboardEventInit::Create();
+    init->setBubbles(true);
+    init->setKey(key);
+    auto* event =
+        MakeGarbageCollected<KeyboardEvent>(event_type_names::kKeydown, init);
+    event->SetTarget(target);
+    return event;
+  }
+
   void SendEvent(KeyboardEvent* event) {
     if (event->target()) {
       event->target()->DispatchEvent(*event);
@@ -1110,6 +1122,100 @@
   ASSERT_EQ(GetDocument().FocusedElement(), item1);
 }
 
+TEST_F(FocusgroupControllerTest,
+       UntrustedArrowDownDoesNotMoveFocusOrMarkUserGesture) {
+  GetDocument().body()->SetInnerHTMLWithoutTrustedTypes(R"HTML(
+    <div focusgroup="menu block">
+      <div id=first tabindex=0>First</div>
+      <div id=spellcheck tabindex=0 contenteditable spellcheck=true>
+        Spellcheck target
+      </div>
+    </div>
+  )HTML");
+  UpdateAllLifecyclePhasesForTest();
+
+  auto* first = GetElementById("first");
+  auto* spellcheck = GetElementById("spellcheck");
+  ASSERT_TRUE(first);
+  ASSERT_TRUE(spellcheck);
+
+  first->Focus();
+  ASSERT_EQ(GetDocument().FocusedElement(), first);
+  ASSERT_FALSE(spellcheck->WasLastFocusFromUserGesture());
+
+  auto* event = UntrustedKeyDownEvent(keywords::kArrowDown, first);
+  ASSERT_FALSE(event->isTrusted());
+  EXPECT_FALSE(FocusgroupController::HandleKeyboardEvent(
+      event, GetDocument().GetFrame()));
+  EXPECT_EQ(GetDocument().FocusedElement(), first);
+  EXPECT_FALSE(spellcheck->WasLastFocusFromUserGesture());
+}
+
+TEST_F(FocusgroupControllerTest,
+       UntrustedArrowUpDoesNotMoveFocusOrMarkUserGesture) {
+  GetDocument().body()->SetInnerHTMLWithoutTrustedTypes(R"HTML(
+    <div focusgroup="menu block">
+      <div id=spellcheck tabindex=0 contenteditable spellcheck=true>
+        Spellcheck target
+      </div>
+      <div id=last tabindex=0>Last</div>
+    </div>
+  )HTML");
+  UpdateAllLifecyclePhasesForTest();
+
+  auto* spellcheck = GetElementById("spellcheck");
+  auto* last = GetElementById("last");
+  ASSERT_TRUE(spellcheck);
+  ASSERT_TRUE(last);
+
+  last->Focus();
+  ASSERT_EQ(GetDocument().FocusedElement(), last);
+  ASSERT_FALSE(spellcheck->WasLastFocusFromUserGesture());
+
+  auto* event = UntrustedKeyDownEvent(keywords::kArrowUp, last);
+  ASSERT_FALSE(event->isTrusted());
+  EXPECT_FALSE(FocusgroupController::HandleKeyboardEvent(
+      event, GetDocument().GetFrame()));
+  EXPECT_EQ(GetDocument().FocusedElement(), last);
+  EXPECT_FALSE(spellcheck->WasLastFocusFromUserGesture());
+}
+
+TEST_F(FocusgroupControllerTest, TrustedArrowKeysMoveFocusAndMarkUserGesture) {
+  GetDocument().body()->SetInnerHTMLWithoutTrustedTypes(R"HTML(
+    <div focusgroup="menu block">
+      <div id=first tabindex=0>First</div>
+      <div id=spellcheck tabindex=0 contenteditable spellcheck=true>
+        Spellcheck target
+      </div>
+      <div id=last tabindex=0>Last</div>
+    </div>
+  )HTML");
+  UpdateAllLifecyclePhasesForTest();
+
+  auto* first = GetElementById("first");
+  auto* spellcheck = GetElementById("spellcheck");
+  auto* last = GetElementById("last");
+  ASSERT_TRUE(first);
+  ASSERT_TRUE(spellcheck);
+  ASSERT_TRUE(last);
+
+  first->Focus();
+  ASSERT_EQ(GetDocument().FocusedElement(), first);
+  ASSERT_FALSE(spellcheck->WasLastFocusFromUserGesture());
+
+  SendArrowDown(first);
+  EXPECT_EQ(GetDocument().FocusedElement(), spellcheck);
+  EXPECT_TRUE(spellcheck->WasLastFocusFromUserGesture());
+
+  last->Focus();
+  ASSERT_EQ(GetDocument().FocusedElement(), last);
+  ASSERT_FALSE(spellcheck->WasLastFocusFromUserGesture());
+
+  SendArrowUp(last);
+  EXPECT_EQ(GetDocument().FocusedElement(), spellcheck);
+  EXPECT_TRUE(spellcheck->WasLastFocusFromUserGesture());
+}
+
 TEST_F(FocusgroupControllerTest, NestedFocusgroupsHaveSeparateScopes) {
   GetDocument().body()->SetHTMLUnsafeWithoutTrustedTypes(R"HTML(
     <div id=outer focusgroup="toolbar">
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.