Browser requirements
The console is a browser application, and some of what protects the break-glass key and the recovery kit is enforced by the browser rather than by the engine. This page lists what your browser has to support, and what the console does on each path when one of those features is not there.
What your browser must support
| Feature | What the console ships | Source |
|---|---|---|
| HTTPS in a secure context, on a custom domain | The console and the engine both disable workers.dev; the console is reached only on your custom domain, over HTTPS. strict-transport-security: max-age=63072000; includeSubDomains; preload is set on every console response, and the engine’s /admin surface carries the same max-age=63072000; includeSubDomains without preload (it has no public route by default). | withSecurityHeaders in console/src/worker.ts; SECURITY_HEADERS in engine/src/index.ts |
| Content-Security-Policy enforcement | A hash-based script-src (no unsafe-inline, no unsafe-hashes), connect-src 'self', frame-ancestors 'none', and violation reporting to a same-origin /csp-report sink. | buildCsp and the /csp-report route in console/src/worker.ts |
COOP and CORP (cross-origin-opener-policy, cross-origin-resource-policy) | Both set to same-origin on every console response, so a popup cannot reach back into this window and another site cannot embed an authenticated response as a subresource. | withSecurityHeaders in console/src/worker.ts |
| Permissions-Policy | Denies camera, microphone, geolocation, payment and USB, none of which the console uses. | withSecurityHeaders in console/src/worker.ts |
__Host--prefixed, SameSite=Strict session cookies, with the x-downpipes-csrf header | The passkey session cookie and its double-submit CSRF cookie are both __Host--prefixed (Secure, Path=/, no Domain) and SameSite=Strict; a cookie-authenticated mutating request must also carry the x-downpipes-csrf header echoing the CSRF cookie’s value. | SESSION_COOKIE_NAME, sessionSetCookie, CSRF_HEADER_NAME and csrfSetCookie in engine/src/admin/session.ts |
WebAuthn (navigator.credentials.create/get, PublicKeyCredential) | Passkey sign-in, step-up and enrolment all go through the WebAuthn API directly from the bundle. | webauthnSupported and readWebAuthnGlobals in console/src/screens/passkey/ceremony.ts |
Web Crypto (crypto.subtle and crypto.getRandomValues) | The break-glass key pair, the AES-256-GCM envelope for the offline custody path and the sealed export all run through Web Crypto in this browser; nothing is generated server-side. | randomBytes in console/src/bytes.ts; importAesKey in console/src/lib/envelope.ts |
Anchor download and navigator.clipboard | Every recovery artefact (identity.key, the encrypted key file and its wrapping key, the recipient and signer files, the recovery sheet, a custody share, the passkey recovery codes) leaves the browser as a Blob delivered through a transient <a download> click, or through the Clipboard API for a Copy control. | deliverFile and copyToClipboard in console/src/lib/file-delivery.ts |
What happens when a feature is missing
The console never lets an absent browser feature fail silently on these paths. Each one is a deliberate warn, silent degrade, block, or (for the two transport controls a browser could simply ignore) a stated absence of any signal at all.
| Feature | Outcome | What happens |
|---|---|---|
| No WebAuthn | Warn | The passkey screen shows the banner “This browser does not support passkeys (WebAuthn). Use a current browser on a secure (https) connection, or sign in via Cloudflare Access or the admin token instead.” and renders no sign-in or enrolment buttons: the if (!supported) branch in console/src/screens/passkey.ts returns the page before any ceremony button is built. Cloudflare Access and the shared admin token both remain available as sign-in paths. |
| Security key (WebAuthn PRF) wrapping key | Silent degrade | The optional security-key-derived wrapping key for the break-glass file is not offered anywhere in the console UI: the enrol and derive functions in console/src/lib/webauthn-prf.ts are unreferenced by any screen or component (see the comment in console/src/components/custody-step-panels.ts). Any wrapping key for the break-glass file is a random key made in this browser, with no banner marking the absence. On Tier 1/2 the encryption is optional, and it downloads that key as a separate file. A split divides the key into Shamir shares. No browser’s secure-context status changes anything an operator observes. |
No crypto.subtle (a plain-http self-host) | Block | The key ceremony produces no keys. The screen shows “Could not generate keys in this browser”, and a capability fault with outcome unavailable is written to the client diagnostics ring and appears in the support pack. The remedy is to serve the console over HTTPS. |
| Download or clipboard absent or refused | Block, per artefact | A refused file download shows a warning. During setup, the key file list marks a file your browser did not save as “Not downloaded”, with its own “Download again”, and Continue waits with “Download <name> first.” For the passkey recovery codes, it reads “This browser refused the download.” From console 0.2.7, the Tier 1/2 encryption step checks both of its files, identity.key.enc and identity.wrapping-key.txt. If one did not arrive, the status reads “Encrypted in this browser, but your browser did not deliver <name>”, tells you not to leave the page, and a Re-download button sits beside each file. A refused clipboard copy replaces the Copy button’s label with “Copy refused.” Both are recorded as a capability fault (outcome unavailable when the API is absent, refused when the browser declined it) so a support engineer can see exactly which artefact never landed. |
| CSP, HSTS, COOP or CORP ignored by the browser | No signal | None of these is detectable from the server side: a browser that ignores them is neither warned nor blocked, because there is no signal a server-delivered header can produce when the client simply does not honour it. A compliant browser that blocks a CSP violation reports it to the same-origin /csp-report sink; a non-compliant browser reports nothing and is not distinguishable from one that had nothing to report. |
Why there is no signal for CSP, HSTS, COOP or CORP
These four are enforced entirely by the browser after the console has served the header. The server-side mitigation is the HSTS preload directive plus the custom-domain-only, workers.dev-disabled topology: an operator who reaches the console at all is very likely on a browser that already has the preload entry, or has already been upgraded to HTTPS by it, before the console’s own HSTS header is ever read.
The closed set of capability faults
Every recorded browser-capability fault names one of a fixed set: blob-download, tab-open, clipboard, or webcrypto-keygen. A support pack shows the capability, the surface that asked for it (which ceremony or screen), and whether the browser lacked the capability outright or had it and refused. No error message, file name or stack trace ever travels into the record.
Where this fits
For why the console’s connect-src can be exactly 'self', and how the console reaches the engine without a public engine hostname, see topology. For the passkey, OIDC and SAML sign-in flows this page’s WebAuthn row feeds into, see sign-in flows.
Last updated .