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 DOM
DescriptionIncorrect authorization in DOM
ComponentDOM
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.
Tracker533079345
Fix commit46163c79cd40 (chromium/src) +40/-3
CWECWE-863
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-07-09 (per vendor advisory)
Fix landed2026-07-21 (public git)
Disclosed2026-08-25
Patch gap35 days — fix public in source before the advisory

Background

`Element::Focus` delegation
when a focus target delegates focus (e.g. a shadow host with delegatesFocus), Blink re-runs Focus() on the element returned by GetFocusableArea() instead of focusing the original element.
`FocusParams` / `mojom::blink::FocusType`
FocusParams bundles the parameters of a focus operation, including a FocusType (kScript, kForward, kMouse, etc.) that records how the focus was initiated.
`WasLastFocusFromUserGesture`
a per-element bit that records whether the element’s most recent focus originated from a genuine user gesture (e.g. mouse/touch) versus programmatic script.

Root Cause Analysis

In the focus-delegation branch of Element::Focus (third_party/blink/renderer/core/dom/element.cc), when focus is forwarded to GetFocusableArea(), the code constructed a fresh FocusParams that hardcoded mojom::blink::FocusType::kForward rather than propagating the caller’s original params_to_use.type. Because WasLastFocusFromUserGesture is derived from the FocusType, a script-initiated focus (kScript, which carries no user gesture) that was delegated into a shadow tree would be re-issued as kForward on the inner element, causing the delegated target to be incorrectly marked as focused via a user gesture. The invariant violated is that the “focus came from a user gesture” bit must reflect the real origin of the focus operation, not an artifact of internal re-dispatch.

The fix replaces the hardcoded mojom::blink::FocusType::kForward with params_to_use.type, so the true initiation type flows through to the delegated element. The shipped test DelegatesFocusWasLastFocusFromUserGesture confirms that a kScript host focus leaves the probe’s WasLastFocusFromUserGesture() false while only a kMouse host focus sets it true.

Key insight
The delegation path threw away the caller’s FocusType and substituted a fixed kForward, letting non-gesture (script) focus masquerade as gesture-originated focus on the delegated element; the fix simply forwards the original params_to_use.type.

Attack Path

  1. Build a delegatesFocus host An attacker page attaches a shadow root with delegatesFocus=true containing a focusable inner element (e.g. a contenteditable div), so GetFocusableArea() will redirect focus into the shadow tree.
  2. Trigger scripted focus The page calls host.focus() from script (FocusType::kScript, no user gesture), which enters the delegation branch of Element::Focus.
  3. Type is laundered to kForward The pre-patch code re-runs Focus() on the inner target with a hardcoded mojom::blink::FocusType::kForward, so the inner element’s WasLastFocusFromUserGesture bit is set as if a gesture occurred.
  4. Abuse the gesture state (inferred) Any behavior gated on WasLastFocusFromUserGesture on the delegated element could then be reached without a real user gesture, constituting the reported incorrect authorization in DOM.

Impact Assessment

An attacker-controlled page running in the renderer can cause a script-initiated focus into a delegatesFocus shadow tree to be recorded as originating from a user gesture, incorrectly satisfying gesture/authorization checks that rely on WasLastFocusFromUserGesture (inferred as to the specific gated feature). Preconditions are only that the page can script a shadow host with focus delegation, which any web content can do. The advisory classifies this as incorrect authorization in DOM; no exploited-in-the-wild status is stated.

Changed Functions

FunctionChangeNotes
TEST_F
third_party/blink/renderer/core/dom/element_test.cc
modified

Files Changed

  • third_party/blink/renderer/core/dom/element_test.cc
  • third_party/blink/renderer/core/dom/element.cc

Audit Directions

  • Hardcoded enum on re-dispatch
    Look for other places that reconstruct a FocusParams/FocusType (or similar provenance enums) with a literal value instead of propagating the caller’s, especially in delegation, redirection, or fallback paths.
  • User-gesture provenance flags
    Audit setters and consumers of WasLastFocusFromUserGesture and related “from user gesture” bits to ensure internal re-entry does not synthesize gesture state.
  • Shadow/delegation focus paths
    Review GetFocusableArea callers and shadow-DOM focus forwarding for other parameters (selection behavior, options, capabilities) that may be silently altered during re-dispatch.
From 46163c79cd4062bd154712e57e2fbbe2a66846f6 Mon Sep 17 00:00:00 2001
From: David Baron <dbaron@chromium.org>
Date: Tue, 21 Jul 2026 11:42:02 -0700
Subject: [PATCH] Propagate FocusType when forwarding Element::Focus.

Propagate FocusType when forwarding Element::Focus to the result of
GetFocusableArea() so that we don't incorrectly set
WasLastFocusFromUserGesture.

Both the fix and the test are AI-authored, though from different AI
tools.  (I shortened a verbose code comment in the fix.)

Fixed: 533079345
Change-Id: Ib44c0ea96a127ccbf5236449c96a0c77e59771e7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8116087
Reviewed-by: Mason Freed <masonf@chromium.org>
Commit-Queue: David Baron <dbaron@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1665655}
---
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index d484712..ade778c 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -8356,9 +8356,11 @@
     if (Element* new_focus_target = GetFocusableArea()) {
       // Unlike the specification, we re-run focus() for new_focus_target
       // because we can't change |this| in a member function.
-      new_focus_target->Focus(FocusParams(
-          SelectionBehaviorOnFocus::kReset, mojom::blink::FocusType::kForward,
-          /*capabilities=*/nullptr, params_to_use.options));
+      // Forward the caller's FocusType so we don't set
+      // WasLastFocusFromUserGesture incorrectly.
+      new_focus_target->Focus(
+          FocusParams(SelectionBehaviorOnFocus::kReset, params_to_use.type,
+                      /*capabilities=*/nullptr, params_to_use.options));
     }
     // 2. If new focus target is null, then:
     //  2.1. If no fallback target was specified, then return.
diff --git a/third_party/blink/renderer/core/dom/element_test.cc b/third_party/blink/renderer/core/dom/element_test.cc
index 42f169c..9ff0b1e1 100644
--- a/third_party/blink/renderer/core/dom/element_test.cc
+++ b/third_party/blink/renderer/core/dom/element_test.cc
@@ -1844,4 +1844,39 @@
   backdrop->DispatchEvent(*event);
 }
 
+TEST_F(ElementTest, DelegatesFocusWasLastFocusFromUserGesture) {
+  SetBodyContent("<div id='host'></div>");
+  ShadowRoot* shadow_root =
+      SetShadowContent("<div id='probe' contenteditable='true'></div>", "host");
+  shadow_root->SetDelegatesFocus(true);
+  UpdateAllLifecyclePhasesForTest();
+
+  Element* host = GetElementById("host");
+  Element* probe = shadow_root->getElementById(AtomicString("probe"));
+  ASSERT_TRUE(host);
+  ASSERT_TRUE(probe);
+
+  EXPECT_FALSE(probe->WasLastFocusFromUserGesture());
+
+  host->Focus();
+  EXPECT_EQ(probe, GetDocument().FocusedElement());
+  EXPECT_FALSE(probe->WasLastFocusFromUserGesture());
+
+  probe->blur();
+  EXPECT_NE(probe, GetDocument().FocusedElement());
+
+  host->Focus(FocusParams(SelectionBehaviorOnFocus::kRestore,
+                          mojom::blink::FocusType::kScript, nullptr));
+  EXPECT_EQ(probe, GetDocument().FocusedElement());
+  EXPECT_FALSE(probe->WasLastFocusFromUserGesture());
+
+  probe->blur();
+  EXPECT_NE(probe, GetDocument().FocusedElement());
+
+  host->Focus(FocusParams(SelectionBehaviorOnFocus::kRestore,
+                          mojom::blink::FocusType::kMouse, nullptr));
+  EXPECT_EQ(probe, GetDocument().FocusedElement());
+  EXPECT_TRUE(probe->WasLastFocusFromUserGesture());
+}
+
 }  // namespace blink
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/renderer/core/dom/element_test.cc b/third_party/blink/renderer/core/dom/element_test.cc
index 42f169c..9ff0b1e1 100644
--- a/third_party/blink/renderer/core/dom/element_test.cc
+++ b/third_party/blink/renderer/core/dom/element_test.cc
@@ -1844,4 +1844,39 @@
   backdrop->DispatchEvent(*event);
 }
 
+TEST_F(ElementTest, DelegatesFocusWasLastFocusFromUserGesture) {
+  SetBodyContent("<div id='host'></div>");
+  ShadowRoot* shadow_root =
+      SetShadowContent("<div id='probe' contenteditable='true'></div>", "host");
+  shadow_root->SetDelegatesFocus(true);
+  UpdateAllLifecyclePhasesForTest();
+
+  Element* host = GetElementById("host");
+  Element* probe = shadow_root->getElementById(AtomicString("probe"));
+  ASSERT_TRUE(host);
+  ASSERT_TRUE(probe);
+
+  EXPECT_FALSE(probe->WasLastFocusFromUserGesture());
+
+  host->Focus();
+  EXPECT_EQ(probe, GetDocument().FocusedElement());
+  EXPECT_FALSE(probe->WasLastFocusFromUserGesture());
+
+  probe->blur();
+  EXPECT_NE(probe, GetDocument().FocusedElement());
+
+  host->Focus(FocusParams(SelectionBehaviorOnFocus::kRestore,
+                          mojom::blink::FocusType::kScript, nullptr));
+  EXPECT_EQ(probe, GetDocument().FocusedElement());
+  EXPECT_FALSE(probe->WasLastFocusFromUserGesture());
+
+  probe->blur();
+  EXPECT_NE(probe, GetDocument().FocusedElement());
+
+  host->Focus(FocusParams(SelectionBehaviorOnFocus::kRestore,
+                          mojom::blink::FocusType::kMouse, nullptr));
+  EXPECT_EQ(probe, GetDocument().FocusedElement());
+  EXPECT_TRUE(probe->WasLastFocusFromUserGesture());
+}
+
 }  // namespace blink
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.