--- name: "tech-access-missing-app-fails-open" title: "Cloudflare Access fails OPEN when the app is missing — clients keep sending headers nobody checks" description: "A service-token-protected origin becomes publicly reachable if the Access application is absent; clients still send CF-Access-Client-* headers, so nothing errors and it looks healthy" type: "reference" category: "cloudflare" tags: ["reference", "cloudflare", "access", "security", "zero-trust", "migration"] related: ["[[tech-zone-account-transfer-breaks-worker-routes]]"] sources: ["https://developers.cloudflare.com/cloudflare-one/policies/access/"] aliases: ["Access service token not enforced", "Access app missing public origin"] --- Cloudflare Access enforcement lives in the **Access application**, not in the client. If the application is missing — deleted, or never recreated after an account move — the hostname is served **without any check at all**. **Why it hides:** well-behaved clients keep sending `CF-Access-Client-Id` / `CF-Access-Client-Secret`. Requests succeed, dashboards look normal, no error is logged anywhere. The credentials are present; nothing is verifying them. An origin whose only remaining defence is its own application password is now on the open internet. **Detection** — one unauthenticated request: ``` curl -sI https:/// # expect 302 -> .cloudflareaccess.com, or 403 ``` A `200` means there is no Access app in front. Service tokens are account-scoped, so after an account migration the old token id is meaningless on the new account even though clients still transmit it. **Verify both directions.** Proving the gate blocks anonymous traffic says nothing about whether it still admits the legitimate client. Check the pair: | Request | Expected | |---|---| | no credentials | `302` / `403` | | app credentials, no Access headers | `302` / `403` | | app credentials **+** Access headers | `200` | **Ordering when rotating Access credentials:** create them → propagate to every client (Worker vars, secrets, `.env`) → **then** create or update the Access application. Reversed, every client is locked out for the length of the gap. That matters most where the client fails open by design (backup/telemetry sinks that swallow errors): the data silently stops arriving instead of raising anything. **A wildcard app covers subdomains, but a more specific app wins.** Before relying on `*.example.com` to protect a new hostname, confirm no narrower app exists for it. Related trap: a token that lacks read scope on `access/identity_providers` or on the `access/apps/{id}/policies` sub-resource returns an **empty list rather than 403**, so "no identity providers configured" and "cannot see them" are indistinguishable. Policies are readable inline from the `access/apps` list response. And Access's built-in email one-time-PIN needs **no** identity provider object at all, so an empty provider list does not mean login is broken.