Low chrome Authorization Error 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in Selection
DescriptionIncorrect authorization in Selection
ComponentSelection
Sub-componentiOS
Bug ClassAuthorization Error
Tracker503708636
Fix commit6b07961fcb16 (chromium/src) +48/-0
CISA KEVNot listed
CreditedPutra Mahardika | Instagram @mhrdkaa._
Disclosed2026-10-06

Changed Functions

FunctionChangeNotes
if
ios/web/js_features/clipboard/resources/clipboard.ts
modified
TEST_F
ios/web/js_features/clipboard/clipboard_js_unittest.mm
modified

Files Changed

  • ios/web/js_features/clipboard/clipboard_js_unittest.mm
  • ios/web/js_features/clipboard/resources/clipboard.ts
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.