Medium CVSS 5.3 webkit Cross Origin 🔧 Commit mapped

Overview

Medium
Severity
5.3
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionA website may be able to track users through Safari web extensions
ComponentWebCore XML
Bug ClassCross Origin
Tracker305020
Fix commit0f4832ce0eea (WebKit/WebKit) +4/-1
CWECWE-400
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
CISA KEVNot listed
CreditedTom Van Goethem
Disclosed2026-02-11

Background

webkit-masked-url:// scheme
A special URL scheme WebKit substitutes for resource URLs injected by Safari web extensions so page-visible APIs cannot observe the true (extension) URL.
ResolveURLs serialization mode
An enum controlling how attribute URLs are emitted during markup serialization — No leaves them literal, Yes resolves to absolute, and NoExcludingURLsForPrivacy leaves them literal but masks privacy-sensitive ones.
XMLSerializer.serializeToString
A DOM API exposed to page JavaScript that serializes a node subtree to an XML string, a code path that must obey the same privacy-masking rules as innerHTML.
Browser fingerprinting / extension tracking
Techniques by which a page detects installed extensions or injected content to build a persistent identifier for a user across sessions and sites.

Root Cause Analysis

The patch changes a single argument in XMLSerializer::serializeToString: the ResolveURLs mode passed to serializeFragment is changed from ResolveURLs::No to ResolveURLs::NoExcludingURLsForPrivacy. WebKit supports ‘masked’ URLs (the webkit-masked-url:// scheme) that Safari web extensions use to hide the true resource URL of content they inject into a page, so that the page’s own script cannot observe which extension is present or what real URL a resource has. The serialization layer therefore distinguishes several URL-handling modes: ResolveURLs::No leaves attribute URLs exactly as-is (which, for a masked attribute, means emitting the underlying real URL text rather than the masked placeholder), while ResolveURLs::NoExcludingURLsForPrivacy additionally suppresses/masks privacy-sensitive URLs during serialization.

The invariant that was violated is that DOM-serialization APIs reachable from page script must never reveal a privacy-masked URL’s true value. HTML serialization (innerHTML) already honored masking — the pre-existing test asserts document.body.innerHTML contains ‘<iframe src=“webkit-masked-url://hidden/">’ — but XMLSerializer.serializeToString used ResolveURLs::No and thus did not apply the privacy exclusion, so calling (new XMLSerializer()).serializeToString(document.body) leaked the real URL behind masked resources.

The fix restores the invariant by making the XML serializer use the privacy-aware mode, so the added test now expects the masked iframe to serialize as ‘webkit-masked-url://hidden/’ while a genuinely non-masked iframe still serializes as its real ‘http://apple.com/baz.html' URL. Because the vulnerable behavior of ResolveURLs::No vs NoExcludingURLsForPrivacy lives in the shared serializeFragment/markup-serialization code (not shown in this diff), the precise masking logic there is inferred, but the change of enum value and the new expectation string establish that XMLSerializer was previously emitting unmasked URLs.

Key insight
A privacy guarantee (URL masking for extension-injected content) was enforced in the HTML serialization path but not in the parallel XMLSerializer path, so an alternate serialization API leaked exactly the data the masking scheme was designed to hide — a classic ‘one API hardened, a sibling API forgotten’ consistency gap.

Attack Path

  1. Victim has a Safari web extension that injects masked content The extension injects resources (e.g. an <iframe> or element) whose URLs are rewritten to webkit-masked-url:// so the page cannot see the true extension resource URL.
  2. Attacker page runs script in the same document A malicious or fingerprinting web page executes JavaScript in the page where the extension has injected masked content.
  3. Serialize the DOM via XMLSerializer The page calls (new XMLSerializer()).serializeToString(document.body) (or serializeToString on any subtree containing the masked node).
  4. Read the leaked real URL Because XMLSerializer used ResolveURLs::No, the returned XML string contains the true underlying URL instead of the webkit-masked-url:// placeholder, unlike innerHTML which correctly masks it.
  5. Fingerprint / track the user The attacker parses the serialized string to detect the presence and identity of specific extensions or extension-injected resources, using that as a stable signal to track or de-anonymize the user across visits.

Impact Assessment

This is a pure information-disclosure/privacy bug, not a memory-safety primitive: it leaks the true URL behind extension-masked resources to page script via XMLSerializer, enabling detection and fingerprinting of Safari web extensions and thereby user tracking/de-anonymization. There is no memory corruption, code execution, or crash, and no escalation path toward RCE; the leaked data is limited to URLs the extension intended to hide. The disclosure occurs within the WebContent process to ordinary page JavaScript (no sandbox or cross-process boundary is broken), with real-world impact being loss of the anti-tracking guarantee the masking mechanism exists to provide.

Changed Functions

FunctionChangeNotes
XMLSerializer::serializeToString
Source/WebCore/xml/XMLSerializer.cpp
modified Changed the ResolveURLs argument to serializeFragment from ResolveURLs::No to ResolveURLs::NoExcludingURLsForPrivacy so XML serialization suppresses privacy-masked (webkit-masked-url://) URLs the way HTML/innerHTML serialization already did.
TestWebViewConfiguration (WKWebViewConfiguration.mm test)
Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebViewConfiguration.mm
modified Added an assertion that serializeToString(document.body) keeps the masked iframe as webkit-masked-url://hidden/ while a non-masked iframe keeps its real apple.com URL, pinning the corrected behavior.

Files Changed

  • Source/WebCore/xml/XMLSerializer.cpp
  • Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebViewConfiguration.mm

Audit Directions

  • Other serializeFragment callers passing ResolveURLs::No
    Grep WebCore for ‘ResolveURLs::No’ and for serializeFragment/createMarkup call sites (e.g. innerHTML, outerHTML, DOMParser round-trips, drag-and-drop and copy/paste markup) to find any that should use NoExcludingURLsForPrivacy but do not.
  • All page-reachable paths that can reveal masked URLs
    Search for ‘webkit-masked-url’, ‘maskedURL’, and ‘NoExcludingURLsForPrivacy’ and enumerate every API that can surface an element’s URL to script (getAttribute, attributes iteration, cssText, computed style, Range.toString, clipboard) to confirm each masks correctly.
  • Privacy-masking consistency across serialization/URL-resolution enums
    Audit the definition and use of the ResolveURLs enum and MarkupAccumulator to ensure the privacy mode is applied uniformly; grep ‘ResolveURLs’, ‘MarkupAccumulator’, and ‘appendURLAttribute’ for branches that bypass the privacy exclusion.

Original Bug Report

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