Low chrome Authorization Error 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in PermissionElement
DescriptionIncorrect authorization in PermissionElement
ComponentPermissionElement
Sub-componentCE
Bug ClassAuthorization Error
Tracker540076586
Fix commite46ec753c3fd (chromium/src) +86/-4
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-06

Changed Functions

FunctionChangeNotes
if
third_party/blink/renderer/core/html/html_capability_element_base.cc
modified
TEST_F
third_party/blink/renderer/core/html/html_capability_element_base_test.cc
modified
HTMLCapabilityElementBaseClickingEnabledTest
third_party/blink/renderer/core/html/html_capability_element_base_test.cc
modified

Files Changed

  • third_party/blink/web_tests/external/wpt/html/semantics/permission-element/usermedia/no-focus.html
  • third_party/blink/renderer/core/html/html_capability_element_base.cc
  • third_party/blink/renderer/core/html/html_capability_element_base_test.cc
From e46ec753c3fd3f3982d091817762f9f7ecaba0f2 Mon Sep 17 00:00:00 2001
From: Thomas Nguyen <tungnh@chromium.org>
Date: Wed, 02 Sep 2026 13:59:19 -0700
Subject: [PATCH] [CE] Restrict script-initiated focus on capability elements

Internal programmatic focus paths, such as modal dialog transitions or
popover autofocus, reach this method with `FocusType::kNone`. This
allowed capability elements to acquire focus without transient user
activation, bypassing safeguards. By tracking `FocusTrigger` instead,
internal script-driven invocations are properly captured and blocked
unless user activation is present.

Fixed: 540076586, 344739595
Change-Id: I5caec2bab0d2817f17b3e9f458e6da773b6761de
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8328459
Reviewed-by: Dominic Farolino <dom@chromium.org>
Commit-Queue: Thomas Nguyen <tungnh@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1691080}
---
diff --git a/third_party/blink/renderer/core/html/html_capability_element_base.cc b/third_party/blink/renderer/core/html/html_capability_element_base.cc
index 67dc6b54..459b65d 100644
--- a/third_party/blink/renderer/core/html/html_capability_element_base.cc
+++ b/third_party/blink/renderer/core/html/html_capability_element_base.cc
@@ -389,10 +389,12 @@
   if (fallback_mode_) {
     return HTMLElement::Focus(params);
   }
-  // This will only apply to `focus` and `blur` JS API. Other focus types (like
+  // This will only apply to script-initiated focusing. Other focus types (like
   // accessibility focusing and manual user focus), will still be permitted as
-  // usual.
-  if (params.type == mojom::blink::FocusType::kScript &&
+  // usual. `FocusTrigger` (rather than `FocusType`) is used because internal
+  // script-driven paths such as dialog.showModal() / popover autofocus reach
+  // this with FocusType::kNone.
+  if (params.focus_trigger == FocusTrigger::kScript &&
       !LocalFrame::HasTransientUserActivation(GetDocument().GetFrame())) {
     return;
   }
diff --git a/third_party/blink/renderer/core/html/html_capability_element_base_test.cc b/third_party/blink/renderer/core/html/html_capability_element_base_test.cc
index bcaca0a6..13d58e0 100644
--- a/third_party/blink/renderer/core/html/html_capability_element_base_test.cc
+++ b/third_party/blink/renderer/core/html/html_capability_element_base_test.cc
@@ -24,9 +24,11 @@
 #include "third_party/blink/renderer/core/css/properties/longhands.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/document_init.h"
+#include "third_party/blink/renderer/core/dom/focus_params.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/geometry/dom_rect.h"
+#include "third_party/blink/renderer/core/html/html_dialog_element.h"
 #include "third_party/blink/renderer/core/html/html_geolocation_element.h"
 #include "third_party/blink/renderer/core/html/html_iframe_element.h"
 #include "third_party/blink/renderer/core/html/html_install_element.h"
@@ -665,6 +667,53 @@
             permission_element->permissionStatus());
 }
 
+TEST_F(HTMLCapabilityElementBaseTest, FocusRequiresUserActivation) {
+  auto* permission_element = CreatePermissionElement("camera");
+  EXPECT_EQ(nullptr, GetDocument().FocusedElement());
+
+  // 1. Script-initiated focus without transient user activation is blocked.
+  permission_element->Focus(FocusParams());
+  EXPECT_EQ(nullptr, GetDocument().FocusedElement());
+
+  permission_element->Focus(FocusParams(FocusTrigger::kScript));
+  EXPECT_EQ(nullptr, GetDocument().FocusedElement());
+
+  // 2. User-gesture focus succeeds even without transient user activation.
+  permission_element->Focus(FocusParams(FocusTrigger::kUserGesture));
+  EXPECT_EQ(permission_element, GetDocument().FocusedElement());
+
+  GetDocument().ClearFocusedElement();
+  EXPECT_EQ(nullptr, GetDocument().FocusedElement());
+
+  // 3. Script-initiated focus with transient user activation succeeds.
+  LocalFrame::NotifyUserActivation(
+      GetDocument().GetFrame(),
+      mojom::blink::UserActivationNotificationType::kTest);
+  permission_element->Focus(FocusParams());
+  EXPECT_EQ(permission_element, GetDocument().FocusedElement());
+}
+
+TEST_F(HTMLCapabilityElementBaseTest,
+       DialogAutofocusDoesNotFocusWithoutUserActivation) {
+  SetBodyContent(R"HTML(
+    <dialog id='d'>
+      <usermedia id='camera' type='camera' autofocus></usermedia>
+    </dialog>
+  )HTML");
+  GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
+  GetDocument().View()->UpdateAllLifecyclePhasesForTest();
+
+  auto* dialog = To<HTMLDialogElement>(
+      GetDocument().QuerySelector(AtomicString("dialog")));
+  auto* permission_element = To<HTMLCapabilityElementBase>(
+      GetDocument().QuerySelector(AtomicString("usermedia")));
+
+  // Open the dialog programmatically without user activation.
+  dialog->showModal(ASSERT_NO_EXCEPTION);
+  EXPECT_TRUE(dialog->IsOpenAndActive());
+  EXPECT_NE(permission_element, GetDocument().FocusedElement());
+}
+
 class HTMLCapabilityElementBaseClickingEnabledTest
     : public HTMLCapabilityElementBaseTest {
  public:
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/usermedia/no-focus.html b/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/usermedia/no-focus.html
index f8acda9..c7bd813 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/usermedia/no-focus.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/usermedia/no-focus.html
@@ -14,14 +14,34 @@
 
 <span tabindex="0" id="focusable_span">This is some text</span>
 
+<dialog id="dialog_without_activation">
+  <usermedia id="camera_dialog_no_activation" type="camera" autofocus></usermedia>
+</dialog>
+
+<dialog id="dialog_with_activation">
+  <usermedia id="camera_dialog_activation" type="camera" autofocus></usermedia>
+</dialog>
+
 <script>
   promise_test(async(t) => {
     await new Promise((r) => t.step_timeout(r, PEPC_CLICK_DELAY));
+    assert_false(navigator.userActivation.isActive, "Should start without user activation");
     focusable_span.focus();
     valid_usermedia_element.focus();
     assert_equals(document.activeElement, focusable_span,
       "UserMedia element should not be focused.");
 
+    const dialog = document.getElementById('dialog_without_activation');
+    const camera = document.getElementById('camera_dialog_no_activation');
+    dialog.showModal();
+    assert_true(dialog.open);
+    assert_not_equals(document.activeElement, camera,
+      "UserMedia element inside dialog should not receive focus without user activation");
+    dialog.close();
+  }, "UserMedia element is not focusable by script and does not autofocus in dialog without user activation");
+
+  promise_test(async(t) => {
+    await new Promise((r) => t.step_timeout(r, PEPC_CLICK_DELAY));
     focusable_span.focus();
     await test_driver.bless('Focus with user activation', () => {
       valid_usermedia_element.focus();
@@ -30,6 +50,17 @@
       "Focus is allowed with user activation");
 
     focusable_span.focus();
+    const dialog = document.getElementById('dialog_with_activation');
+    const camera = document.getElementById('camera_dialog_activation');
+    await test_driver.bless('Open dialog with user activation', () => {
+      dialog.showModal();
+    });
+    assert_true(dialog.open);
+    assert_equals(document.activeElement, camera,
+      "UserMedia element inside dialog should receive focus when opened with user activation");
+    dialog.close();
+
+    focusable_span.focus();
     actions = new test_driver.Actions()
        .pointerMove(1, 1, {origin: valid_usermedia_element})
        .pointerDown()
@@ -41,6 +72,6 @@
     focusable_span.focus();
     assert_equals(document.activeElement, focusable_span,
         "Other element should be focused");
-}, "UserMedia element is not focusable by script without user activation");
+  }, "UserMedia element and dialog autofocus are allowed with user activation");
 </script>
 </body>
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/renderer/core/html/html_capability_element_base_test.cc b/third_party/blink/renderer/core/html/html_capability_element_base_test.cc
index bcaca0a6..13d58e0 100644
--- a/third_party/blink/renderer/core/html/html_capability_element_base_test.cc
+++ b/third_party/blink/renderer/core/html/html_capability_element_base_test.cc
@@ -24,9 +24,11 @@
 #include "third_party/blink/renderer/core/css/properties/longhands.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/document_init.h"
+#include "third_party/blink/renderer/core/dom/focus_params.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/geometry/dom_rect.h"
+#include "third_party/blink/renderer/core/html/html_dialog_element.h"
 #include "third_party/blink/renderer/core/html/html_geolocation_element.h"
 #include "third_party/blink/renderer/core/html/html_iframe_element.h"
 #include "third_party/blink/renderer/core/html/html_install_element.h"
@@ -665,6 +667,53 @@
             permission_element->permissionStatus());
 }
 
+TEST_F(HTMLCapabilityElementBaseTest, FocusRequiresUserActivation) {
+  auto* permission_element = CreatePermissionElement("camera");
+  EXPECT_EQ(nullptr, GetDocument().FocusedElement());
+
+  // 1. Script-initiated focus without transient user activation is blocked.
+  permission_element->Focus(FocusParams());
+  EXPECT_EQ(nullptr, GetDocument().FocusedElement());
+
+  permission_element->Focus(FocusParams(FocusTrigger::kScript));
+  EXPECT_EQ(nullptr, GetDocument().FocusedElement());
+
+  // 2. User-gesture focus succeeds even without transient user activation.
+  permission_element->Focus(FocusParams(FocusTrigger::kUserGesture));
+  EXPECT_EQ(permission_element, GetDocument().FocusedElement());
+
+  GetDocument().ClearFocusedElement();
+  EXPECT_EQ(nullptr, GetDocument().FocusedElement());
+
+  // 3. Script-initiated focus with transient user activation succeeds.
+  LocalFrame::NotifyUserActivation(
+      GetDocument().GetFrame(),
+      mojom::blink::UserActivationNotificationType::kTest);
+  permission_element->Focus(FocusParams());
+  EXPECT_EQ(permission_element, GetDocument().FocusedElement());
+}
+
+TEST_F(HTMLCapabilityElementBaseTest,
+       DialogAutofocusDoesNotFocusWithoutUserActivation) {
+  SetBodyContent(R"HTML(
+    <dialog id='d'>
+      <usermedia id='camera' type='camera' autofocus></usermedia>
+    </dialog>
+  )HTML");
+  GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
+  GetDocument().View()->UpdateAllLifecyclePhasesForTest();
+
+  auto* dialog = To<HTMLDialogElement>(
+      GetDocument().QuerySelector(AtomicString("dialog")));
+  auto* permission_element = To<HTMLCapabilityElementBase>(
+      GetDocument().QuerySelector(AtomicString("usermedia")));
+
+  // Open the dialog programmatically without user activation.
+  dialog->showModal(ASSERT_NO_EXCEPTION);
+  EXPECT_TRUE(dialog->IsOpenAndActive());
+  EXPECT_NE(permission_element, GetDocument().FocusedElement());
+}
+
 class HTMLCapabilityElementBaseClickingEnabledTest
     : public HTMLCapabilityElementBaseTest {
  public:
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/usermedia/no-focus.html b/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/usermedia/no-focus.html
index f8acda9..c7bd813 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/usermedia/no-focus.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/usermedia/no-focus.html
@@ -14,14 +14,34 @@
 
 <span tabindex="0" id="focusable_span">This is some text</span>
 
+<dialog id="dialog_without_activation">
+  <usermedia id="camera_dialog_no_activation" type="camera" autofocus></usermedia>
+</dialog>
+
+<dialog id="dialog_with_activation">
+  <usermedia id="camera_dialog_activation" type="camera" autofocus></usermedia>
+</dialog>
+
 <script>
   promise_test(async(t) => {
     await new Promise((r) => t.step_timeout(r, PEPC_CLICK_DELAY));
+    assert_false(navigator.userActivation.isActive, "Should start without user activation");
     focusable_span.focus();
     valid_usermedia_element.focus();
     assert_equals(document.activeElement, focusable_span,
       "UserMedia element should not be focused.");
 
+    const dialog = document.getElementById('dialog_without_activation');
+    const camera = document.getElementById('camera_dialog_no_activation');
+    dialog.showModal();
+    assert_true(dialog.open);
+    assert_not_equals(document.activeElement, camera,
+      "UserMedia element inside dialog should not receive focus without user activation");
+    dialog.close();
+  }, "UserMedia element is not focusable by script and does not autofocus in dialog without user activation");
+
+  promise_test(async(t) => {
+    await new Promise((r) => t.step_timeout(r, PEPC_CLICK_DELAY));
     focusable_span.focus();
     await test_driver.bless('Focus with user activation', () => {
       valid_usermedia_element.focus();
@@ -30,6 +50,17 @@
       "Focus is allowed with user activation");
 
     focusable_span.focus();
+    const dialog = document.getElementById('dialog_with_activation');
+    const camera = document.getElementById('camera_dialog_activation');
+    await test_driver.bless('Open dialog with user activation', () => {
+      dialog.showModal();
+    });
+    assert_true(dialog.open);
+    assert_equals(document.activeElement, camera,
+      "UserMedia element inside dialog should receive focus when opened with user activation");
+    dialog.close();
+
+    focusable_span.focus();
     actions = new test_driver.Actions()
        .pointerMove(1, 1, {origin: valid_usermedia_element})
        .pointerDown()
@@ -41,6 +72,6 @@
     focusable_span.focus();
     assert_equals(document.activeElement, focusable_span,
         "Other element should be focused");
-}, "UserMedia element is not focusable by script without user activation");
+  }, "UserMedia element and dialog autofocus are allowed with user activation");
 </script>
 </body>
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.