13. COEP credentialless over require-corp
Status
Accepted — 2026-04-19
Context
The blog and sludge-report hosting targets serve strict cross-origin isolation headers: Cross-Origin-Opener-Policy: same-origin, Cross-Origin-Resource-Policy: same-origin, and Cross-Origin-Embedder-Policy: require-corp. These were added in commit 3f6945f based on a Bastion security header audit, which recommended the strongest available posture.
require-corp tells the browser to block any cross-origin resource whose response does not include a Cross-Origin-Resource-Policy: cross-origin header. This works when you control every server your site talks to. The blog does not: it loads Google Tag Manager, Firebase Performance Monitoring telemetry, Firestore realtime listener channels, Google Fonts, and content pipeline WASM modules (Shiki/Mermaid). None of these Google-operated endpoints send CORP headers.
The result was five categories of runtime error in production:
- Google Tag Manager script blocked — COEP rejected the gtag.js fetch.
- CSP
base-uri 'none'violation — Angular’s<base href="/">was blocked (a CSP issue surfaced during the same investigation). - WebAssembly instantiation failure — content pipeline WASM blocked by COEP and missing
wasm-unsafe-evalin CSPscript-src. - Firebase logging 504 spiral —
firebaselogging-pa.googleapis.comPOST blocked by COEP, then retried with exponential backoff, producing tens of thousands of 504 stack traces per session. - Firestore listener terminate blocked — realtime listener close requests rejected by CORP via the service worker fetch handler.
Errors 4 and 5 were silent to users but generated enormous console log volume (~39K lines per session).
Decision
Replace Cross-Origin-Embedder-Policy: require-corp with Cross-Origin-Embedder-Policy: credentialless on both hosting targets. Fix the related CSP directives: base-uri 'none' → base-uri 'self', add 'wasm-unsafe-eval' to script-src.
Update the Bastion agent’s audit rubric to recommend credentialless as the default COEP posture, reserving require-corp for sites that control every cross-origin resource.
credentialless still provides cross-origin isolation (enabling SharedArrayBuffer, performance.measureUserAgentSpecificMemory(), and high-resolution timers) but does not require third-party servers to send CORP headers. Instead, it strips credentials (cookies, client certs) from cross-origin requests that don’t explicitly opt in — a weaker but operationally viable constraint.
Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Resource-Policy: same-origin remain unchanged. COOP protects the browsing context. CORP on our own responses prevents other sites from embedding our resources. Neither causes the third-party compatibility issues that COEP require-corp does.
Consequences
- All five error categories are resolved. Google Tag Manager, Firebase telemetry, Firestore listeners, and WASM modules load without COEP interference.
- Cross-origin isolation is preserved.
credentialless+same-originCOOP still qualifies the page as cross-origin isolated in all major browsers. - The security trade-off:
credentiallessdoes not prevent a cross-origin server from reading its own response when loaded by our page —require-corpwould have blocked that fetch entirely. For a site that only loads resources from Google-operated CDNs and APIs, this is not a meaningful regression. - Bastion’s rubric now accounts for third-party compatibility. Future audits will recommend
credentiallessby default and only suggestrequire-corpwhen the site controls all cross-origin endpoints. - The CSP
base-urichange from'none'to'self'is a minor relaxation. It allows<base>tags pointing to the same origin, which Angular requires. It does not allow cross-origin base URI injection. - The
wasm-unsafe-evaladdition toscript-srcallows WebAssembly compilation without opening the door to JavaScripteval(). This is the standard CSP directive for WASM-dependent libraries.