Medium CVSS 6.5 webkit Bypass 🔧 Commit mapped

Overview

Medium
Severity
6.5
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionVisiting a website may leak sensitive data
ComponentWebCore Async-clipboard
Bug ClassBypass
Tracker314806
Fix commit73645abad282 (WebKit/WebKit) +255/-11
CWECWE-284
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N
CISA KEVNot listed
CreditedJody Ritonga
Disclosed2026-06-29

Background

Transient activation
A short-lived, per-window state granted by a genuine user interaction that authorizes gated APIs and is deliberately not propagated to cross-origin iframes.
User gesture (UserGestureIndicator)
WebKit’s stack-scoped notion of a user gesture being processed, whose token can be carried across frames (e.g. via postMessage), unlike transient activation.
Async Clipboard API
navigator.clipboard read/readText/write/writeText methods that expose clipboard contents to web pages under activation and permission gating.
Cross-origin iframe
An embedded frame from a different origin that is subject to isolation rules and, per spec, does not inherit the top-level page’s transient activation.
NotAllowedError
The DOM exception the Clipboard API rejects with when the caller is not permitted to access the clipboard (e.g. lacking activation).

Root Cause Analysis

The async Clipboard API in Source/WebCore/Modules/async-clipboard/Clipboard.cpp gated clipboard access on UserGestureIndicator::processingUserGesture() rather than on the relevant global object’s transient activation. The W3C Clipboard API spec requires the calling document’s global object to have transient activation to read or write the clipboard. UserGestureIndicator::processingUserGesture() reports whether a user gesture is being processed anywhere in the current call stack, and crucially a user gesture / gesture token can propagate across frames via mechanisms like postMessage, whereas transient activation is deliberately NOT propagated to cross-origin iframes. The violated invariant is that clipboard access must be authorized by the transient activation of the accessing frame’s own global object, not by any in-flight user gesture that may have originated in a different (top-level, cross-origin) context. Because shouldProceedWithClipboardWrite returned true for the RequiresUserGesture policy whenever processingUserGesture() was true, a cross-origin iframe could have a top-level page relay a user click to it via postMessage and, while that gesture was still being processed, invoke navigator.clipboard.write/writeText and pass the gesture check even though the iframe itself never received transient activation. The read paths (readText and read) had a separate gap: they only checked that a document existed, not that it had transient activation, so they too could be invoked without proper activation.

The fix introduces frameHasTransientActivation(frame) and documentHasTransientActivation(document) helpers that query window->hasTransientActivation(), replaces the processingUserGesture() check in shouldProceedWithClipboardWrite with frameHasTransientActivation, and adds !documentHasTransientActivation(*document) rejection guards (throwing NotAllowedError) to both readText and read. It also drops the now-unused UserGestureIndicator include and adds LocalDOMWindow. This restores the invariant by keying every clipboard operation to the accessing frame’s own transient activation, which cross-origin frames cannot borrow.

Key insight
Clipboard access was gated on ‘is a user gesture being processed anywhere’ (UserGestureIndicator::processingUserGesture) instead of ‘does this frame’s own global object have transient activation’, letting a cross-origin iframe launder a top-level click through postMessage; transient activation, unlike a user-gesture token, does not cross the origin boundary.

Attack Path

  1. Embed a cross-origin iframe An attacker page embeds a cross-origin iframe (the new test uses http://localhost:8080 inside a page at a different origin) whose script is prepared to invoke navigator.clipboard.read/readText/write/writeText on demand.
  2. Obtain a top-level user gesture The top-level page gets the user to perform one legitimate interaction (a button click), producing an in-flight user gesture on the top-level frame.
  3. Relay the gesture via postMessage During that click handler the top-level page postMessages the iframe to trigger the clipboard call; the user-gesture/token is still being processed on the stack even though transient activation never propagated to the cross-origin child.
  4. Pass the flawed gesture check In the iframe, navigator.clipboard.write/writeText passes shouldProceedWithClipboardWrite because UserGestureIndicator::processingUserGesture() returns true, and (pre-fix) read/readText only checked for a document, so the clipboard operation proceeds without the iframe having its own activation.
  5. Exfiltrate clipboard contents The iframe reads the user’s clipboard (potentially containing passwords, tokens, or other sensitive data the user copied) and posts it back to the attacker, or silently overwrites the clipboard, leaking/altering sensitive data as the advisory describes.

Impact Assessment

This is a security-policy / logic bug, not a memory-safety issue: the primitive is unauthorized clipboard read (information disclosure of whatever sensitive data the user copied) and clipboard write from a cross-origin iframe that only borrowed a top-level gesture. There is no memory corruption and no path to code execution from this bug alone; the impact ceiling is confidentiality/integrity of clipboard contents. It operates within the WebContent process and requires one genuine user interaction on the top-level page plus attacker-controlled iframe content to exploit.

Changed Functions

FunctionChangeNotes
frameHasTransientActivation
Source/WebCore/Modules/async-clipboard/Clipboard.cpp
added New static helper returning whether the frame's window has transient activation (window && window->hasTransientActivation()).
documentHasTransientActivation
Source/WebCore/Modules/async-clipboard/Clipboard.cpp
added New static helper returning whether the document's window has transient activation, used to gate the clipboard read paths.
shouldProceedWithClipboardWrite
Source/WebCore/Modules/async-clipboard/Clipboard.cpp
modified For the RequiresUserGesture policy, replaced UserGestureIndicator::processingUserGesture() with frameHasTransientActivation(frame), so a cross-origin iframe can no longer borrow a propagated user gesture.
Clipboard::readText
Source/WebCore/Modules/async-clipboard/Clipboard.cpp
modified Added !documentHasTransientActivation(*document) to the guard so the promise is rejected with NotAllowedError when the document's window lacks transient activation.
Clipboard::read
Source/WebCore/Modules/async-clipboard/Clipboard.cpp
modified Added the same transient-activation guard; on failure it clears m_activeSession and rejects with NotAllowedError.

Files Changed

  • LayoutTests/editing/async-clipboard/clipboard-change-data-while-getting-type.html
  • LayoutTests/editing/async-clipboard/clipboard-get-type-with-old-items.html
  • LayoutTests/editing/async-clipboard/resources/async-clipboard-helpers.js
  • LayoutTests/http/tests/security/clipboard/clipboard-access-in-cross-origin-iframe-denied-expected.txt
  • LayoutTests/http/tests/security/clipboard/clipboard-access-in-cross-origin-iframe-denied.html
  • LayoutTests/http/tests/security/clipboard/resources/clipboard-access-from-iframe.html
  • LayoutTests/imported/w3c/web-platform-tests/clipboard-apis/async-navigator-clipboard-basics.https-expected.txt
  • LayoutTests/imported/w3c/web-platform-tests/clipboard-apis/async-navigator-clipboard-basics.https.html
  • LayoutTests/imported/w3c/web-platform-tests/clipboard-apis/resources/user-activation.js
  • Source/WebCore/Modules/async-clipboard/Clipboard.cpp

Audit Directions

  • Other processingUserGesture gates in clipboard/permission code
    Grep WebCore for UserGestureIndicator::processingUserGesture() at security decision points and check each against whether the spec actually requires per-frame transient activation (hasTransientActivation) instead.
  • Write vs read parity for activation checks
    Within Clipboard.cpp and related async-clipboard code, verify every public entry point (read, readText, write, writeText, and getType/ClipboardItem paths) enforces documentHasTransientActivation; the read paths originally lacked it, so audit for other entry points missing the guard.
  • Gesture-token propagation across frames as an auth signal
    Search the codebase for gated features (autoplay, downloads, popups, fullscreen, payment) that authorize on user-gesture tokens which can propagate via postMessage or nested frames, and confirm they instead use window->hasTransientActivation() where cross-origin isolation is intended.

Original Bug Report

The reporter's bug is still restricted on the tracker.