Chrome · Selection › iOS
CVE-2026-106309
Authorization Error in Selection (iOS)
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifios/web/js_features/clipboard/resources/clipboard.ts |
modified | |
TEST_Fios/web/js_features/clipboard/clipboard_js_unittest.mm |
modified |
Files Changed
ios/web/js_features/clipboard/clipboard_js_unittest.mmios/web/js_features/clipboard/resources/clipboard.ts
Patch
From 6b07961fcb16fe8ae38de65e3ea0a00d66d1e4b6 Mon Sep 17 00:00:00 2001 From: Yixi Wu <[email protected]> Date: Mon, 14 Sep 2026 14:06:53 -0700 Subject: [PATCH] [iOS] Fix Clipboard API bypasses User Activation check Fix the bypass of the user activation check in the Clipboard Java Script Feature by routing the clipboard request to the original function and let WebKit's implementation deny the request. Bug: 503708636 Change-Id: I32f984e4226045e7ca669fdded8430d7c2d83074 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8405947 Reviewed-by: Mike Dougherty <[email protected]> Reviewed-by: Ernesto Izquierdo Clua <[email protected]> Commit-Queue: Yixi Wu <[email protected]> Cr-Commit-Position: refs/heads/main@{#1697544} --- diff --git a/ios/web/js_features/clipboard/resources/clipboard.ts b/ios/web/js_features/clipboard/resources/clipboard.ts index c783e31..a8e216b8 100644 --- a/ios/web/js_features/clipboard/resources/clipboard.ts +++ b/ios/web/js_features/clipboard/resources/clipboard.ts @@ -75,6 +75,12 @@ command: string): (...args: A) => Promise<T> { // Create a new function that will replace the original clipboard method. return function(this: Clipboard, ...args: A): Promise<T> { + // Use the original clipboard function directly and let WebKit deny the + // clipboard action if there is no user activation. + if (!navigator.userActivation.isActive) { + return originalFunction.apply(this, args); + } + // If there are too many pending requests, evict the oldest one. if (pendingClipboardRequests.length >= MAX_PENDING_REQUESTS) { const oldestRequest = pendingClipboardRequests.shift(); diff --git a/ios/web/js_features/clipboard/clipboard_js_unittest.mm b/ios/web/js_features/clipboard/clipboard_js_unittest.mm index 031b528..2e55475 100644 --- a/ios/web/js_features/clipboard/clipboard_js_unittest.mm +++ b/ios/web/js_features/clipboard/clipboard_js_unittest.mm @@ -471,4 +471,46 @@ })); } +// Test that clipboard requests without user activation (e.g., triggered by an +// untrusted programmatic click in setTimeout) bypass the native handler and +// are rejected directly by WebKit's original clipboard function. +TEST_F(ClipboardJsTest, TestWriteTextWithoutUserActivationDenied) { + NSString* html = @"<html><body>" + "<button id=\"triggerBtn\">CLICK ME</button>" + "<script>" + "window.testState = {};" + "const btn = document.getElementById('triggerBtn');" + "btn.addEventListener('click', async (event) => {" + " window.testState.isTrusted = String(event.isTrusted);" + " window.testState.isActive = " + "String(navigator.userActivation.isActive);" + " try {" + " await navigator.clipboard.writeText('TEST-123');" + " window.testState.lastResult = 'resolved';" + " } catch (e) {" + " window.testState.lastResult = e.name;" + " }" + "});" + "window.onload = () => {" + " setTimeout(() => {" + " btn.click();" + " }, 0);" + "};" + "</script>" + "</body></html>"; + + ASSERT_TRUE(web::test::LoadHtml(web_view(), html, + [NSURL URLWithString:kHttpsTestUrl])); + ASSERT_TRUE(web::test::WaitForInjectedScripts(web_view())); + + EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{ + return !GetTestStateResult("lastResult").empty(); + })); + + EXPECT_EQ("false", GetTestStateResult("isTrusted")); + EXPECT_EQ("false", GetTestStateResult("isActive")); + EXPECT_EQ("NotAllowedError", GetTestStateResult("lastResult")); + EXPECT_FALSE(clipboard_handler_.lastReceivedScriptMessage); +} + } // namespace web
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/ios/web/js_features/clipboard/clipboard_js_unittest.mm b/ios/web/js_features/clipboard/clipboard_js_unittest.mm
index 031b528..2e55475 100644
--- a/ios/web/js_features/clipboard/clipboard_js_unittest.mm
+++ b/ios/web/js_features/clipboard/clipboard_js_unittest.mm
@@ -471,4 +471,46 @@
}));
}
+// Test that clipboard requests without user activation (e.g., triggered by an
+// untrusted programmatic click in setTimeout) bypass the native handler and
+// are rejected directly by WebKit's original clipboard function.
+TEST_F(ClipboardJsTest, TestWriteTextWithoutUserActivationDenied) {
+ NSString* html = @"<html><body>"
+ "<button id=\"triggerBtn\">CLICK ME</button>"
+ "<script>"
+ "window.testState = {};"
+ "const btn = document.getElementById('triggerBtn');"
+ "btn.addEventListener('click', async (event) => {"
+ " window.testState.isTrusted = String(event.isTrusted);"
+ " window.testState.isActive = "
+ "String(navigator.userActivation.isActive);"
+ " try {"
+ " await navigator.clipboard.writeText('TEST-123');"
+ " window.testState.lastResult = 'resolved';"
+ " } catch (e) {"
+ " window.testState.lastResult = e.name;"
+ " }"
+ "});"
+ "window.onload = () => {"
+ " setTimeout(() => {"
+ " btn.click();"
+ " }, 0);"
+ "};"
+ "</script>"
+ "</body></html>";
+
+ ASSERT_TRUE(web::test::LoadHtml(web_view(), html,
+ [NSURL URLWithString:kHttpsTestUrl]));
+ ASSERT_TRUE(web::test::WaitForInjectedScripts(web_view()));
+
+ EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
+ return !GetTestStateResult("lastResult").empty();
+ }));
+
+ EXPECT_EQ("false", GetTestStateResult("isTrusted"));
+ EXPECT_EQ("false", GetTestStateResult("isActive"));
+ EXPECT_EQ("NotAllowedError", GetTestStateResult("lastResult"));
+ EXPECT_FALSE(clipboard_handler_.lastReceivedScriptMessage);
+}
+
} // namespace web
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