CVE-2026-43713
Overview
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.
Attack Path
- 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.
- 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.
- 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.
- 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.
- 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
Changed Functions
| Function | Change | Notes |
|---|---|---|
frameHasTransientActivationSource/WebCore/Modules/async-clipboard/Clipboard.cpp |
added | New static helper returning whether the frame's window has transient activation (window && window->hasTransientActivation()). |
documentHasTransientActivationSource/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. |
shouldProceedWithClipboardWriteSource/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::readTextSource/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::readSource/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.htmlLayoutTests/editing/async-clipboard/clipboard-get-type-with-old-items.htmlLayoutTests/editing/async-clipboard/resources/async-clipboard-helpers.jsLayoutTests/http/tests/security/clipboard/clipboard-access-in-cross-origin-iframe-denied-expected.txtLayoutTests/http/tests/security/clipboard/clipboard-access-in-cross-origin-iframe-denied.htmlLayoutTests/http/tests/security/clipboard/resources/clipboard-access-from-iframe.htmlLayoutTests/imported/w3c/web-platform-tests/clipboard-apis/async-navigator-clipboard-basics.https-expected.txtLayoutTests/imported/w3c/web-platform-tests/clipboard-apis/async-navigator-clipboard-basics.https.htmlLayoutTests/imported/w3c/web-platform-tests/clipboard-apis/resources/user-activation.jsSource/WebCore/Modules/async-clipboard/Clipboard.cpp
Audit Directions
- Other processingUserGesture gates in clipboard/permission codeGrep 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 checksWithin 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 signalSearch 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.