Chrome · PermissionElement › CE
CVE-2026-106259
Authorization Error in PermissionElement (CE)
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifthird_party/blink/renderer/core/html/html_capability_element_base.cc |
modified | |
TEST_Fthird_party/blink/renderer/core/html/html_capability_element_base_test.cc |
modified | |
HTMLCapabilityElementBaseClickingEnabledTestthird_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.htmlthird_party/blink/renderer/core/html/html_capability_element_base.ccthird_party/blink/renderer/core/html/html_capability_element_base_test.cc
Patch
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.
References
On This Page