← All reports

CORS enforcement for app-registered custom URL schemes

The scheme the app opted into CORS never got a CORS check

Component: WebKit custom URL schemes | f2a07be

registerURLSchemeAsCORSEnabled() lets an app opt a custom scheme into the same CORS rules as http and https. CORS is normally enforced in two places — in the NetworkProcess, and in WebProcess loader code (DocumentThreadableLoader for XHR and fetch, CachedResourceLoader for subresources) — but requests handled by a WKURLSchemeHandler are resolved entirely inside the WebProcess and never reach the NetworkProcess's CORS machinery at all.

This commit closes both halves of the resulting gap. The NetworkProcess was never told about registerURLSchemeAsCORSEnabled() registrations, so those registrations are now forwarded to it. On the WebProcess side, DocumentThreadableLoader and CachedResourceLoader no longer allow no-cors cross-origin subresource loads and redirects to WKURLSchemeHandler-backed schemes to skip CORS checks entirely.

Cross-origin web content loaded into a WKWebView could read opaque cross-origin data from an app's custom-scheme resources through ordinary no-cors requests. No-cors requests require neither a preflight nor an exposed response, so nothing in the pre-fix path forced the check that the app had opted into. Apps register custom URL schemes with WKURLSchemeHandler to serve bundled or local content, so the resources behind those schemes are frequently the app's own privileged data.

The forward-facing pattern is a set of security carve-outs keyed on frame-tree position, where the tree can be reshaped by the attacker. Narrow: the opaque-origin branch walks frame->tree().parent(), frame->opener() and frame->tree().top() to decide whether the document is app-loaded — stress it with nested iframes, blob: and srcdoc documents, and opener chains, asking in each case whether a cross-origin document can arrange for topFrame->frameURLProtocol() to match the custom scheme while its own origin does not. Wider: the WebProcess-side check and the newly-forwarded NetworkProcess-side registration are two enforcement points for one policy, so audit whether they agree on which schemes are CORS-enabled at every point in the registration lifecycle — a scheme registered after a load begins, or registered in one process but not yet the other, is the shape where two enforcement points disagree. Widest: any policy split across a renderer-side check and a network-side check needs an explicit answer to which one is authoritative when they diverge; in code review, a check whose exemptions are all expressed as early return false deserves a walk through each exemption asking who controls its precondition.