Medium CVSS 6.5 webkit Bypass 🔧 Commit mapped

Overview

Medium
Severity
6.5
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionA malicious website may be able to silently hijack clipboard data
ComponentWebKit NetworkProcess
Bug ClassBypass
Tracker313478
Fix commitd8576e6cceeb (WebKit/WebKit) +106/-22
CWECWE-732
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N
CISA KEVNot listed
CreditedIdan Masas
Disclosed2026-06-29

Background

Storage Access API
A Web API (document.requestStorageAccess()) by which an embedded third-party frame asks for access to its first-party cookies/storage, subject to a permission decision that may or may not show a prompt.
Transient user activation
A short-lived per-frame state set by a genuine user interaction that gates powerful APIs (clipboard, popups, autoplay) to prevent abuse by unattended script.
StorageAccessPromptWasShown
A result flag indicating whether the storage-access decision involved a user-facing prompt; a No value can mean the request was auto-decided without any interaction.
Prevalent resource
In WebKit’s Intelligent Tracking Prevention, a domain classified as a cross-site tracker whose storage access is restricted unless prior user interaction exists.
Completion handler capture
The async lambda that runs when the cross-process storage-access decision returns; state it needs (like the original gesture status) must be captured at call time to be available later.

Root Cause Analysis

The vulnerable path is DocumentStorageAccess::requestStorageAccess in Source/WebCore/dom/DocumentStorageAccess.cpp. When the Storage Access API request completed with StorageAccessWasGranted::No, the completion handler decided whether to keep (preserve) the calling frame’s transient user activation with the single test ‘shouldPreserveUserGesture = result.promptWasShown == StorageAccessPromptWasShown::No’. The intended invariant is that user activation may only be preserved/consumed across the async round-trip when the caller actually had a user gesture (or the request legitimately did not require one). But the old test keyed only on whether a UI prompt was shown, ignoring whether the original call carried a gesture. A no-prompt, no-gesture rejection therefore left the frame with transient user activation it never legitimately earned. The diff shows the request now captures the gesture state at call time in a renamed enum value ‘hasUserGestureOrNoUserGestureRequired’ (previously the ambiguous ‘HasOrShouldIgnoreUserGesture’), and that value is captured into the completion lambda.

The fix replaces the test with ‘promptNotShownButMayHaveUserGesture = result.promptWasShown == StorageAccessPromptWasShown::No && hasUserGestureOrNoUserGestureRequired == HasUserGestureOrNoUserGestureRequired::Yes’, so activation is only preserved when a gesture (or no-gesture-required condition) was actually present. The enum rename is threaded end-to-end through ChromeClient, WebChromeClient, WebPage, the NetworkConnectionToWebProcess IPC message, WebResourceLoadStatisticsStore, the serialization list and messages.py, but those are mechanical renames; the behavioral fix is the added conjunct. The new layout test confirms the intended behavior: an iframe calls document.requestStorageAccess() with no user gesture, the request is rejected, and in the rejection handler window.open must be blocked (no synthesized activation). Per the CVE text the consequence is that spuriously preserved activation lets a page perform gesture-gated actions such as writing the clipboard without a real user interaction, hence ‘silently hijack clipboard data’.

Key insight
Preserving transient user activation was conditioned on the wrong signal — ’no prompt shown’ — instead of ’the caller actually had a gesture’, so a no-gesture, no-prompt rejection leaked activation the frame never earned; the fix restores the invariant by requiring the gesture state captured at call time.

Attack Path

  1. Set up a rejectable request The attacker page (or an embedded third-party iframe) is a prevalent resource with no prior user interaction, so a Storage Access API request will be denied and, critically, no permission prompt will be shown.
  2. Call requestStorageAccess without a gesture Script calls document.requestStorageAccess() outside any user-gesture context (e.g. from a timer or onload), so hasUserGesture is No.
  3. Receive a no-prompt rejection The network process returns StorageAccessWasGranted::No with StorageAccessPromptWasShown::No; the pre-patch completion handler sets shouldPreserveUserGesture = true purely because no prompt was shown.
  4. Inherit transient activation in the rejection handler The promise’s rejection callback now runs with a preserved/synthesized transient user activation that the frame never earned from a real interaction.
  5. Invoke a gesture-gated API Within that handler the page performs an action normally requiring activation — e.g. navigator.clipboard writes/reads or window.open — silently, without the user having clicked anything, achieving the clipboard-hijack impact described.

Impact Assessment

This is a logic/security-policy bug, not a memory-safety primitive: it grants a page unearned transient user activation, which the CVE ties to silent clipboard access (and generically enables other gesture-gated actions such as popups). There is no memory corruption, OOB, or UAF here, so it does not escalate toward code execution; the realistic outcome is a privacy/integrity violation (reading or overwriting clipboard contents, launching a popup) driven entirely from the WebContent process within the normal renderer sandbox. Severity is medium: user-visible harm without a crash or sandbox escape.

Changed Functions

FunctionChangeNotes
DocumentStorageAccess::requestStorageAccess
Source/WebCore/dom/DocumentStorageAccess.cpp
modified Renames the gesture flag, captures it into the completion lambda, and gates shouldPreserveUserGesture on both promptWasShown==No AND a real/na gesture; this is the core fix.
DocumentStorageAccess::requestStorageAccessQuirk
Source/WebCore/dom/DocumentStorageAccess.cpp
modified Passes the renamed enum value HasUserGestureOrNoUserGestureRequired::Yes; mechanical rename only.
WebResourceLoadStatisticsStore::requestStorageAccess
Source/WebKit/NetworkProcess/Classifier/WebResourceLoadStatisticsStore.cpp
modified Signature/parameter renamed and the No-branch check updated to the new enum name; behavior unchanged.
NetworkConnectionToWebProcess::requestStorageAccess
Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp
modified Forwards the renamed enum to resourceLoadStatistics; mechanical rename.
WebChromeClient::requestStorageAccess
Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp
modified Renamed parameter forwarded to WebPage; mechanical rename.
WebPage::requestStorageAccess
Source/WebKit/WebProcess/WebPage/WebPage.cpp
modified Sends the renamed enum over the RequestStorageAccess IPC message; mechanical rename.
ChromeClient::requestStorageAccess (declaration)
Source/WebCore/page/ChromeClient.h
modified Virtual signature updated to the renamed enum type.
HasOrShouldIgnoreUserGesture -> HasUserGestureOrNoUserGestureRequired (enum)
Source/WebCore/dom/DocumentStorageAccess.h
modified Enum renamed to make its true meaning (caller had a gesture / none required) explicit rather than 'ignore gesture'.

Files Changed

  • LayoutTests/http/tests/storageAccess/request-storage-access-rejected-without-gesture-should-not-activate-expected.txt
  • LayoutTests/http/tests/storageAccess/request-storage-access-rejected-without-gesture-should-not-activate.html
  • LayoutTests/http/tests/storageAccess/resources/request-storage-access-without-gesture-check-activation-iframe.html
  • Source/WebCore/dom/DocumentStorageAccess.cpp
  • Source/WebCore/dom/DocumentStorageAccess.h
  • Source/WebCore/page/ChromeClient.h
  • Source/WebKit/NetworkProcess/Classifier/WebResourceLoadStatisticsStore.cpp
  • Source/WebKit/NetworkProcess/Classifier/WebResourceLoadStatisticsStore.h
  • Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp
  • Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h
  • Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.in
  • Source/WebKit/Scripts/webkit/messages.py
  • Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in
  • Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp
  • Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.h
  • Source/WebKit/WebProcess/WebPage/WebPage.cpp
  • Source/WebKit/WebProcess/WebPage/WebPage.h

Audit Directions

  • Other branches of the same completion handler
    In DocumentStorageAccess.cpp re-check every assignment to shouldPreserveUserGesture (the StorageAccessWasGranted::Yes/YesWithException cases) to confirm none preserve activation without a genuine gesture; grep for ‘shouldPreserveUserGesture’ and ‘promptWasShown’.
  • Callers of the renamed enum
    Audit all sites still passing HasUserGestureOrNoUserGestureRequired::Yes unconditionally (e.g. requestStorageAccessQuirk) to ensure ‘Yes’ is justified and not masking a missing gesture check; grep ‘HasUserGestureOrNoUserGestureRequired::Yes’.
  • Gesture-gated APIs reachable from async rejection handlers
    Look for promise rejection/resolution paths that resume with preserved activation and reach powerful APIs; grep for ‘UserGestureIndicator’, ‘processingUserGesture’, and clipboard/window.open entry points to find similar activation-preservation logic.
  • Prompt-vs-gesture conflation across permission flows
    Search other permission subsystems (getUserMedia, notifications, geolocation) for decisions keyed on whether a prompt was shown rather than on actual user activation; grep ‘PromptWasShown’ and ‘wasGranted’ patterns.

Original Bug Report

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