[2] HTMLResourcePreloader CSP bypass via empty-nonce preloading
Severity: High | Component: WebCore HTML parser — HTMLResourcePreloader | 88d6ffd
Rated High because the observable effect is a complete Content Security Policy bypass for redirected resources loaded after a meta CSP tag, reachable from web content by controlling HTML structure around the meta tag — the empty-nonce evaluation against an absent policy set is confirmed by the fix pattern with confidence 0.92.
When parser-blocking scripts are declared before a meta CSP tag, the preload scanner fetches subsequent resources before the CSP policy is parsed. For each preloaded script/stylesheet, HTMLResourcePreloader calls allowScriptWithNonce/allowStyleWithNonce with an empty nonce, and because no policies exist yet these functions return true, which HTMLResourcePreloader interprets as a valid nonce match and sets ContentSecurityPolicyImposition::SkipPolicyCheck on the resource's ResourceLoaderOptions. This persists on the SubresourceLoader for the resource's lifetime and results in redirect targets that violate the CSP policy loading without being blocked.
Source/WebCore/html/parser/HTMLResourcePreloader.cpp
bool skipContentSecurityPolicyCheck = false;
- if (m_resourceType == CachedResource::Type::Script || m_resourceType == CachedResource::Type::JSON)
- skipContentSecurityPolicyCheck = protect(document.contentSecurityPolicy())->allowScriptWithNonce(m_nonceAttribute);
- else if (m_resourceType == CachedResource::Type::CSSStyleSheet)
- skipContentSecurityPolicyCheck = protect(document.contentSecurityPolicy())->allowStyleWithNonce(m_nonceAttribute);
+ if (!m_nonceAttribute.isEmpty()) {
+ if (m_resourceType == CachedResource::Type::Script || m_resourceType == CachedResource::Type::JSON)
+ skipContentSecurityPolicyCheck = protect(document.contentSecurityPolicy())->allowScriptWithNonce(m_nonceAttribute);
+ else if (m_resourceType == CachedResource::Type::CSSStyleSheet)
+ skipContentSecurityPolicyCheck = protect(document.contentSecurityPolicy())->allowStyleWithNonce(m_nonceAttribute);
+ }
Patch Details
The fix adds an m_nonceAttribute.isEmpty() guard in PreloadRequest::resourceRequest() so that allowScriptWithNonce / allowStyleWithNonce are only called when the preload request actually carries a nonce. Before the fix, these functions were called unconditionally with an empty nonce string, and when no CSP policies existed yet (because the meta CSP tag had not been parsed), they returned true, causing SkipPolicyCheck to be set on the resource's ResourceLoaderOptions. The test expectation files for ios-site-isolation and mac-site-isolation remove the [Timeout] markers for script-redirect-blocked.html and stylesheet-redirect-blocked.html, which were timing out due to this bug.
Premature CSP nonce evaluation during speculative preloading against an empty policy set, causing a permanent policy-check bypass flag to be set on the resource loader.
Background
Content Security Policy (CSP) can be delivered via a <meta http-equiv="Content-Security-Policy"> tag in addition to HTTP headers. When delivered via meta tag, the policy only takes effect once the HTML parser reaches and processes that tag. WebKit's preload scanner (HTMLResourcePreloader) runs ahead of the main HTML parser to discover and speculatively fetch subresources (scripts, stylesheets, images) earlier, reducing page load time — this is a performance optimization that creates a timing window where resources may be fetched before security policies are installed.
CSP nonce-based allowlisting lets a resource bypass CSP checks if it carries a nonce attribute matching one declared in the policy. allowScriptWithNonce() and allowStyleWithNonce() return true if the nonce matches any policy's allowlist — or if no policies exist at all (vacuously true). ContentSecurityPolicyImposition::SkipPolicyCheck is a flag on ResourceLoaderOptions that tells the resource loading infrastructure to skip all CSP checks for that resource, including checks on redirect targets. Once set, this flag persists on the SubresourceLoader for the resource's entire lifetime.
Analysis
The root cause is a semantic mismatch in the interpretation of allowScriptWithNonce(""). Before the fix, PreloadRequest::resourceRequest() unconditionally called allowScriptWithNonce(m_nonceAttribute) even when m_nonceAttribute was empty. The preload scanner runs ahead of the HTML parser, so when parser-blocking scripts appear before a <meta http-equiv="Content-Security-Policy"> tag, the preload scanner fetches resources declared after the meta tag before the CSP policy has been parsed. At this point, document.contentSecurityPolicy() has no policies. Calling allowScriptWithNonce("") against an empty policy set returns true — vacuously, because no policy exists to deny it (if the function behaves as the fix pattern strongly implies). HTMLResourcePreloader interprets this true as a valid nonce match and sets SkipPolicyCheck. This flag persists across redirects on the SubresourceLoader (as evidenced by the test names script-redirect-blocked and stylesheet-redirect-blocked). When the meta CSP tag is later parsed and the resource follows a redirect to a URL that violates the CSP, the redirect check is skipped.
Aaa Aaaaaa Aaaaaaa Aaaaaaaa a Aaaaaaaa Aaaaaaaaaaaaa Aaaa Aaa Aaaaaaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Aaaaaaa
Aaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Aaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Aaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaa
Aaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Aaa
Aaa Aaaa Aaaaaa Aa Aaaaaaaa Aaaaa Aaa Aaaa Aaa Aaa Aaa Aaaaaa Aa Aaaaaaa Aaaaaaa Aaa Aaaaaaaa Aaaaa Aaaaaaa Aaaaaaaaaaaa Aaaaa Aaa Aaa Aaaa Aaa Aaaaaaa Aaa Aaa Aaaaaaa Aaaaaaa Aaaaaaa Aa Aaaaaa Aaa Aaaa Aaa Aa Aaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaa Aaaaaa Aaa Aaaaaaaa Aaaaa Aaaaaaaaaaaaaaaaa Aa Aaaa Aaa Aaa Aaaaaaaa Aa Aaaaaaaaaaa Aaaaaaaa Aaaaaaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaa Aaaaa Aaaaaa Aaa Aaa Aaaa Aaaaa Aaa Aaaaaaa Aaa Aaaaaaaaaaaaaaa
Aaaa Aa Aaaaaaaaaaa Aaa Aaa Aaaaaa Aaaa Aaa Aaaaaaaa Aa Aaaaaaaa Aaa Aaa Aaaaaaa Aaaa Aaaaaaaaa Aaaaaa a Aaaa Aaa Aaa a Aaaaaaaaaa Aaa Aaaa Aaaaaaaaaa a Aaa Aaaaaaaaa Aa Aaa Aaaaaaaa Aaaaa Aaa Aaaaaaaa Aaa Aaaaa Aaaaaa Aaaaaa Aaa Aaaaa Aaa Aaaa Aaa a Aaa Aaaa Aaaaaaa Aaaa Aaaaaaa Aaa Aaaaaa Aaaaaaaaaa Aaaaaaaa Aaa Aaaaaaaa Aaaaaaaaaa a Aaaa Aaaa Aaaaaaaaaaaaaaa Aaaaaaa Aaaaaa Aaa Aaaa Aaa Aaaa Aaaaaaaa Aa a Aaaaaa Aaaaa Aaaaa Aaaaaa Aa Aa Aaaaaaaaaaaaaaaaaaa Aaaaaaaa Aaaaaaaaa Aaa Aaaaaaaa Aaaaaa Aaaaaa a Aaaaaaaaaaaa Aaaaaaaaa Aaaaaaa Aaaaa Aaaaaaa Aaa Aaaaaaaaaaaa
Aaaa Aaaaaaaaaaaaa Aaaaaaa Aaa Aaaaaaa Aaaaaaaa Aaaaaa Aaaaa Aaaaaaaaa Aaaaa Aaaaaaaaa Aa Aaaa Aaa Aaaaaaaaaaa Aaaaa a Aaaaaaaaa Aaaaaaaa Aaaaaaa a Aaa Aaaaaaa Aaaaaaa Aaa Aaaaaa Aaaaaaa Aaaaaa Aaa Aaaa Aa Aaaaaaaa Aaa Aaaaaaaa a Aaaaaa Aaaaaaaaa Aaaaa Aaaaaaa Aaa Aaaaaaa Aa Aaaaaaaaaaa Aa Aa Aaaaaaaaaaaaa Aaaaaaaaa Aaaa Aaaaaaaaaaaaaaaaaa Aaaa Aaaaaaaa Aaaa Aa Aaaaaaaaa Aaaaaaa Aaaa Aaa Aaaaaa Aaaaaa Aaaa Aaaaaaaaaaa Aaaaaaaa Aaaaaaaaaaaaaaaaaa Aaa Aaa Aaaaaaaaaa Aaaaaaaaaa Aaaaaaaa Aaaaaaaaaaaa Aaaaaa Aaaaaaaaa Aaaa Aaa Aaaa Aaaaaa Aaaaaaaaaa Aaaaaaaa Aa Aaaaaaaaa
Aaa Aaaaaa Aaaaaa Aaaaa Aa Aaaa Aaaaaaaaaaaaaaaaa Aa a Aaaaaaa Aaaaaaaaaaaaaaaaaa Aaaa Aaa Aa Aaaaaaa Aaaa Aaaaa Aa a Aaa Aaaaa Aaaa Aaa Aaa Aaa Aaaaaaa Aaa Aaaaa Aaaaaaa Aaa Aaaaa Aaaa Aaaa Aaaa Aaaa Aaaaaaaaaaaaaaaaa Aaaaa Aa Aaaaa Aa Aaaaaaaaaa Aaaaaa Aaaaa Aaaaa Aaaa a Aaaaaaa Aaaaaaaaaaaaaa Aaaa Aaa Aaa Aaaaaa Aaaaa Aaaaaaa Aaaaaaaaaa Aaaaaaa a Aaa Aaaaaa Aaaaaaa Aaaaa Aaaa Aaa Aaaaaaaa Aaaaaa Aaaaa Aaaa Aaaaaa Aaaa Aaa Aaaaaaaaaaaaa Aa Aaa Aaaaaaaa Aaa Aaaaaaaaaaaaaaaaa Aaaa Aaa Aaaaa Aaaaaaaaaaa Aaa Aaaaaa Aaaaaaaaaaa Aaaaaaaaaaa
Aaaaaaaaa Aaaa Aaaaaaaaaaaaaa Aaaaaaa a Aaaaaaaaaaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaa Aaaaaa Aaaa Aa Aaa Aaaaaaaa Aaaaa Aaa Aaaa Aaaaaaaaaaaaaaaaa Aaaaaaaa Aaaaaa Aaaaaaaaa a Aaa Aaaaaaaa Aaaa Aaa Aaa Aaaaaaa Aaa Aaaa Aaaaaaaaaa Aaa Aaaa Aaaaaa Aaaaaaaaa Aa Aaaaaaaaaaaa Aaaaaaaaa Aa Aaa Aaaaaa
🔒Explores how the speculative preloader's interaction with CSP timing creates a permanent policy bypass, and the conditions under which this is exploitable
Subscribe to read more
Audit directions
a Aaaaaaaa Aaaaaaaaaaaaaaa Aaaaa Aaa Aaaaaa Aaaaaaaaaaaaaaaaa Aaaaaaaaaa Aaaa Aaaaaaa Aaaaa Aaa Aaaaaaaa Aaaaaaa Aaaaaaaaaa Aaaaa Aaa Aaaa Aaaaa Aaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa a Aaaa Aaa Aaaaaaaaaaaaaaaaa Aaaaaa Aaaaaaa Aaa Aaaaaa Aaaa Aaaa Aaaa Aaaa Aaaaaaaaa Aaa Aaaaaaa Aaa Aaaaaa Aaaaaaaa Aaaaaa Aaaaaa Aaaa Aa Aaaaaaaaaaaa Aaaaaa Aaa Aaaaaaa Aaaaaaa Aa Aaa Aaaa Aaaaa Aaaaa Aaaaaaa Aaaa Aaaaaaaa Aaaaaaaaaaaaaa Aa Aaaaaaaa Aaaa Aaaaaaa Aaaaaaaaaaaaaaaa Aaaaaa
a Aaaaaaaaaaaaaa Aaaaaaa Aaaa Aaaaaaaa Aaaaa Aaaaaaaaa Aaaa Aa Aaaaaa Aaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaaaaaa Aaaaaa Aaaaaa Aaaa Aaa Aaaaaa Aaa Aa Aaaaaa Aaaaa Aa Aaaaaaa Aaa Aaa Aaaa Aaaaaaaaa Aaa Aaaaaaaaa Aaaa Aaa Aaaaaa Aaaaaaaaaa Aaaaaa Aa Aaaaa Aaa Aaaa Aaaaaaaaaa Aaaaaaaaaaa Aaaaa Aaaaa Aaa Aaaaa Aaaaaaaaa Aaaaaaaaaaaaaaa Aaaaaaaaaaaa Aaaaaaaaaaaaaaaaa Aaa Aaaaaaa Aaaa Aaaaa a Aaaaaaa Aaaaaa Aa Aa Aaaaaaaaaaa Aaaaaaaa Aaaaaaaaaa Aaaaaaaaaa Aaaaaa Aaaaaaaa Aaaaaaaaa Aaaaaa Aaaaa Aaaaaaaa Aaa Aaa Aaa Aa Aaaaa Aaaaaaa
a Aaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaa Aaaaaaaaa Aaa Aaaaaaa Aaaaaaa Aaa Aaa Aaaaaa Aaaa Aaaaaaaa Aaaaaaa Aaa Aaaaa Aaaaaaaaa Aa Aaaa Aaaaaaaaa Aaaaaaaaaa Aaaaa Aaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aa Aaaaaaaaaaaaaaaaaaa Aaaaaaaa Aaa Aaaa Aaaa Aaa Aaaaaa Aaaaaaaaaaaaa Aaa Aaaaaaa Aaaaaaaaaaaaaaaaa Aaaaa Aa Aaa Aaaaa Aa Aa Aaaaaaaaaa Aaaaaaaaaaaaaaaa Aaaaaaa
🔒Multiple audit patterns identified around early security-flag decisions and vacuous policy checks, applicable across WebKit's resource loading infrastructure
Subscribe to read more