← All issues

[8] safari-web-extension url masking bypass

Severity: Medium | Component: WebCore markup serialization | 3f52ce5

Rated Medium because the observable effect is a privacy-boundary leak of Safari Web Extension URLs through XMLSerializer and other DOM-to-string sinks, and impact is bounded to extension enumeration/fingerprinting (no memory-safety or sandbox-escape primitive).

Source/WebCore/dom/Element.h

- String resolveURLStringIfNeeded(const String& urlString, ResolveURLs = ResolveURLs::Yes, const URL& base = URL()) const;
- virtual String completeURLsInAttributeValue(const URL& base, const Attribute&, ResolveURLs = ResolveURLs::Yes) const;
+ String resolveURLStringIfNeeded(const String& urlString, ResolveURLs = ResolveURLs::YesExcludingURLsForPrivacy, const URL& base = URL()) const;
+ virtual String completeURLsInAttributeValue(const URL& base, const Attribute&, ResolveURLs = ResolveURLs::YesExcludingURLsForPrivacy) const;

Source/WebCore/editing/markup.h

-WEBCORE_EXPORT String serializeFragment(const Node&, SerializedNodes, Vector<Ref<Node>>* = nullptr, ResolveURLs = ResolveURLs::No, ...);
+WEBCORE_EXPORT String serializeFragment(const Node&, SerializedNodes, Vector<Ref<Node>>* = nullptr, ResolveURLs = ResolveURLs::NoExcludingURLsForPrivacy, ...);

Default ResolveURLs arguments across markup-serialization APIs flip from No/Yes to NoExcludingURLsForPrivacy/YesExcludingURLsForPrivacy. Touched declarations include Element::resolveURLStringIfNeeded, Element::completeURLsInAttributeValue, serializeFragment/serializePreservingVisualAppearance, HTMLImageElement::completeURLsInAttributeValue, LegacyWebArchive::createInternal/createFromSelection, and PageSerializer::SerializerMarkupAccumulator. A new XMLSerializer test asserts that webkit-masked-url://hidden/ survives and test-scheme:// does not.

Inconsistent application of a privacy-masking policy across sibling serialization sinks: the masking transform was applied on some DOM-to-string paths but skipped on others sharing the same input.

Safari Web Extensions can inject DOM nodes whose attribute URLs resolve to extension-internal resources. To prevent web content from fingerprinting installed extensions, WebKit replaces extension URLs with webkit-masked-url://hidden/ whenever they are exposed back to web content. The ResolveURLs enum in Element.h has four members — No, NoExcludingURLsForPrivacy, Yes, YesExcludingURLsForPrivacy — where the *ExcludingURLsForPrivacy variants apply the masking transform during serialization.

The masking policy was opt-in — ExcludingURLsForPrivacy had to be threaded explicitly through every DOM-to-string sink. The default was the non-privacy-aware variant, so any serializer that accepted the default leaked the underlying extension URL.

🔒

Walks through how a privacy-masking policy applied at some serialization sinks but not others created a uniform bypass, and what an attacker observes in practice.

Subscribe to read more

🔒

Multiple audit patterns identified covering opt-in security defaults, DOM-to-string sinks beyond the patched ones, and non-markup leakage paths for the same sensitive data.

Subscribe to read more