CVE-2025-31205 in 60 seconds
A website may load a stylesheet from another origin for rendering, but the same-origin policy should
stop JavaScript from reading its CSS rules. CVE-2025-31205 broke that boundary after a lifecycle
change: script could keep a reference to a cross-origin CSSStyleSheet, remove its owning element
from the document, and then ask WebKit for the rules.
The attack path was:
malicious website
-> load a cross-origin stylesheet
-> keep the CSSStyleSheet object and remove its owner element
-> ownerDocument() becomes null
-> WebKit's fallback returns "allowed"
-> read or modify rules that should remain cross-origin
Apple describes the impact as cross-origin data exfiltration. The bug did not make the stylesheet same-origin; WebKit lost the document context used for the comparison and interpreted “unknown” as “allow.”
What are the same-origin policy, CSSOM, and origin-clean state?
The same-origin policy prevents script from freely reading resources belonging to a different scheme, host, or port. A page can often use a cross-origin stylesheet to render, while the CSS Object Model (CSSOM) still denies access to its rule list. Rendering permission and script-readable data are different capabilities.
WebKit’s CSSStyleSheet::canAccessRules() decides whether JavaScript may read or modify those rules.
An origin-clean value can preserve the authorization result with the stylesheet object. Without that
stored state, the old fallback tried to recover the owning Document and compare its origin with the
stylesheet URL.
What happened?
JavaScript could retain a stylesheet obtained from a <link>, <style>, or imported sheet and then
remove the owning structure from the document. The stylesheet object survived, but
ownerDocument() returned null. In the vulnerable code, that missing document caused
canAccessRules() to return true, permitting reads and rule changes without an origin comparison.
WebKit reversed the unsafe default to false, propagated explicit origin-clean state through more
construction paths, and made insertRule() and deleteRule() use the same access decision. The new
layout tests cover attached and detached same-origin, cross-origin, CORS-enabled, redirected, and
imported stylesheets.
Who was actually affected?
Apple fixed the issue in Safari 18.5 and corresponding May 2025 platform updates. The Apple advisory lists Safari on macOS Ventura and Sonoma and states that a malicious website could exfiltrate data cross-origin. WebKitGTK and WPE WebKit fixed the corresponding issue in 2.48.2. Exposure required a vulnerable WebKit build and a page able to drive the affected stylesheet lifecycle; no browser extension or local account was required for the web-content entry point.
What I verified
- In the vulnerable branch, a missing
ownerDocument()ended the check withreturn true; the patch changes that exact fallback toreturn false. - The change adds an explicit origin-clean value so the access decision can survive after the stylesheet loses its document owner.
insertRule()anddeleteRule()now consult the same access decision used for reading rules.- I inspected the added layout tests. They cover same-origin and cross-origin sheets while attached
and detached, plus imported sheets; denied operations are expected to raise
SecurityError. - This is a static reproduction of the security decision and its fix. I inspected the patch and regression cases; I did not claim discovery or run an exfiltration exploit.
Deep dive: the security decision had two paths
WebKit did not always calculate stylesheet access from scratch. When a CSSStyleSheet was created
with an explicit origin-clean value, canAccessRules() could use that recorded result. Otherwise,
the method used a fallback:
- read the stylesheet’s base URL;
- find the document that owned the stylesheet;
- compare the document’s security origin with the stylesheet URL;
- decide whether script could read or modify the rules.
The dangerous branch sat between steps two and three. If there was no owner document, the method returned true. That answer converted “I no longer have enough context to compare origins” into “access is allowed.”
Detaching the element changed context, not the stylesheet’s origin
The public commit describes two routes to the ownerless state. Script could keep a reference to a
stylesheet obtained through a <link> or <style> element and then remove that element from the
document. A similar lifetime transition was possible for a stylesheet reached through an
@import rule when the containing stylesheet was removed.
The security property should survive that lifecycle change. Detaching a DOM node may change rendering and ownership relationships, but it must not make previously cross-origin rules readable. The stylesheet did not become same-origin. Only the convenient object used to perform the origin comparison became unavailable.
This makes the issue a useful variant-hunting model. In browser engines, security decisions are often distributed across an object graph. A node, frame, document, loader, or execution context may carry the authority information. If the protected object outlives that context, fallback behavior becomes part of the security boundary.
The fix changes missing context to denial
The most important line in the patch reverses one default: when the fallback cannot obtain an
owner document, access is denied. WebKit also strengthens the surrounding construction paths so
more stylesheets carry an explicit same-origin flag, and it applies canAccessRules() consistently
to rule insertion and deletion as well as reading.
This combination matters. Changing only null to false would protect cross-origin sheets but could break legitimate detached same-origin stylesheets created without an explicit flag. Recording the origin result at construction reduces dependence on a later owner lookup; denial remains the safe fallback when that record is absent.
Variant analysis: search for lost authority context
The reusable pattern is broader than CSS:
A protected object remains reachable after the object that supplied its security context has been detached, destroyed, navigated, or replaced.
A focused WebKit or browser-engine review can search for access-control helpers that retrieve an owner or context through nullable relationships. High-value candidates include code shaped like:
context = object.ownerDocument() | frame() | scriptExecutionContext() | page()
if context is missing:
allow, skip check, or return a permissive default
Not every such branch is a vulnerability. Some objects are intentionally safe when detached. Candidates become meaningful only when all three conditions hold:
- the surviving object still exposes data or state-changing operations;
- the missing context previously supplied origin, permission, or principal information;
- an attacker can deliberately trigger the lifecycle transition while retaining the object.
Safe validation does not need real cross-origin data
WebKit’s added layout tests provide the right model. A controlled harness can serve two synthetic origins, each containing non-sensitive test CSS. The test records access while the stylesheet is attached, removes the owning element, and repeats the same operation through the retained object.
The minimum matrix should include:
- same-origin stylesheet, attached and detached: access remains available;
- cross-origin stylesheet without CORS, attached and detached: access throws
SecurityError; - cross-origin stylesheet with successful CORS: access follows the explicit origin-clean state;
- imported stylesheet after its parent is detached: the same rules remain enforced;
cssRules,insertRule(), anddeleteRule(): all use the same access decision.
This proves the boundary without reading a third party’s content. It also avoids an incomplete
test that checks only cssRules while mutation methods follow a different path.
Evidence matrix
| Signal | What it proves | What it does not prove | My check |
|---|---|---|---|
| Apple maps CVE-2025-31205 to Bugzilla 290992 | The vendor connects the CVE to WebKit’s cross-origin data impact | The complete discovery history or weaponized exploit | I matched the Safari 18.5 advisory with the public commit |
ownerDocument() == nullptr changed from allow to deny | Missing ownership context was a permissive access branch | That every ownerless stylesheet was attacker controlled | I inspected CSSStyleSheet::canAccessRules() before and after the change |
| Construction paths now supply origin-clean state | The fix preserves valid decisions beyond document lifetime | That all browser objects use the same lifetime model | I reviewed the call-site changes and added layout tests |
| Read, insert, and delete operations share the gate | The patch closes both disclosure and mutation routes | The exact data selected in any private exploit | I traced all three operations to the shared access decision |
| WebKitGTK/WPE advisory fixes versions before 2.48.2 | The issue affected ports beyond Safari | Identical exploitability on every embedding application | I compared the port advisory and fixed-version record |
My conclusion
Users should update to Safari 18.5 or the corresponding fixed Apple platform release. WebKitGTK and WPE WebKit consumers should use 2.48.2 or a distributor build that explicitly backports the fix. Version strings alone can mislead when distributions backport patches, so the package security record is the deciding source.
My conclusion is that the bug was not simply “detached CSS.” The unsafe part was the fallback that treated missing ownership information as permission. When an object outlives the context that authorized it, the implementation must carry the earlier decision forward or deny the operation. A null context is a reason to stop, not a successful origin comparison.
Public sources
- Apple: Safari 18.5 security content
- WebKit commit: Tighten up cross-site access to CSSStyleSheet
- Canonical WebKit commit 295342@main
- WebKitGTK and WPE WebKit advisory WSA-2025-0004
Evidence used for this analysis
Version and exploitation status can change; follow the linked vendor records.
The author completed a technical review. This does not, by itself, claim lab reproduction.
