Skip to content
Back to field notes
DeploymentJune 6, 20266 min read

Cloudflare Pages ships without security headers — add them before launch

A Cloudflare Pages site ships with no CSP, X-Frame-Options, or Permissions-Policy unless you add a _headers file, which AI builders rarely generate. This note shows how to confirm what your app sends before launch.

Cloudflare PagesSecurity headersDeployment

The default Cloudflare Pages response has no security headers

Cloudflare Pages will serve your static site over HTTPS with a valid certificate and a global CDN out of the box. What it does not add for you is any of the response headers that constrain how a browser treats your pages. By default the HTML response carries no Content-Security-Policy, no X-Frame-Options, no Referrer-Policy, and no Permissions-Policy. The site loads and looks finished, so the absence is easy to miss.

This is a configuration gap, not a live exploit. The honest framing matters here. Missing headers do not mean your app is being attacked, and they are not proof that any specific bug exists. What they mean is that the browser is running your page with weaker defaults than it could be: a missing Content-Security-Policy maps to the OWASP and CWE clickjacking and injection-mitigation classes, where the header is a defense-in-depth control, not the only thing standing between you and a problem.

What is observable is concrete and you can read it yourself. A request to your live Cloudflare Pages URL returns a set of response headers, and you can see exactly which security headers are present and which are absent. What is only possible — and depends on your actual code — is whether a missing header lets a real issue through. Keep those two separate as you check.

Why AI-built apps ship without a _headers file

On Cloudflare Pages, security headers are opt-in through a plain text file named _headers placed in your build output directory, or through a Pages Functions middleware. Neither is created automatically. If nothing in your project writes that file, the platform serves your assets with no custom headers, and that is the default an AI-generated project lands on.

The reason this slips through is the same pattern across AI-built deploys: the generated app works on the first try, so nobody opens the response headers. A prompt like "build me a landing page and deploy it to Cloudflare Pages" produces working HTML, CSS, and a green deploy. The model wires up the parts needed to make the page render. It rarely adds a _headers file, because the page works without one, and a working page reads as a finished page.

  • The build succeeds and the site loads, so the missing file is invisible from inside the app.
  • Header configuration is a separate, opt-in step that has no visible effect on the rendered page, so it is easy to skip.
  • Fast deploy means the public URL exists and gets shared before anyone inspects the response headers. This is one slice of a broader pattern covered in the security headers for AI-built apps note.

Check what your live URL actually sends

You can confirm your own headers without any attack and without third-party tools. Two reliable paths.

From the browser: open your live Cloudflare Pages URL, open DevTools, go to the Network tab, reload, and click the top document request (the HTML). Look at the Response Headers panel. You are reading exactly what every visitor's browser receives.

From the command line: run a request that prints only the response headers, for example curl -I against your URL, or curl with the -sI flags. The header names are case-insensitive; look for Content-Security-Policy, X-Frame-Options, Referrer-Policy, Permissions-Policy, and Strict-Transport-Security.

  • Suspicious result: none of Content-Security-Policy, X-Frame-Options, or Permissions-Policy appear in the response. That is the bare Cloudflare Pages default, and it means you have not added a _headers file.
  • Safer result: the headers you intended are present with the values you set. Read the actual values, not just the names — an X-Frame-Options of ALLOWALL, or a Content-Security-Policy that ends in default-src *, is present but is not protecting anything.
  • Check both the apex and any subpaths. A _headers file applies rules by path pattern, so a header set for the homepage may not cover an embedded route or an API path served by Pages Functions.

Add a _headers file, and know what it does not cover

The fix is small and lives in your repo. Create a file named _headers in the directory Cloudflare Pages publishes (commonly the build output folder, for example dist or public, depending on your framework). Each block names a path pattern, then lists header lines under it. A pattern of /* applies to every route. Under it you add the headers you want, such as X-Frame-Options: DENY to block framing, Referrer-Policy: strict-origin-when-cross-origin, a Permissions-Policy that disables APIs you do not use, and a Content-Security-Policy scoped to the origins your app actually loads from. Redeploy, then re-run the check above to confirm the live response changed.

A few honest limits on what this fixes:

  • A Content-Security-Policy is the one header that takes real effort to get right. A policy that is too loose (default-src * or unsafe-inline everywhere) is present but provides little protection; a policy that is too strict silently breaks your scripts and styles. Build it from your app's real origins and test that the page still works.
  • Headers are defense in depth. They reduce the impact of certain client-side problems; they do not fix the underlying code. A missing Content-Security-Policy does not create an injection bug, and adding one does not remove a bug that is already there. The header limits blast radius, not root cause.
  • Headers do nothing for server-side or data-layer problems. They are about how the browser treats the response, not about authorization, exposed secrets, or open data endpoints. Those are separate checks.
  • Headers are not the same as transport security. Strict-Transport-Security is its own concern tied to HTTPS and redirects, which is covered in the self-hosted custom domain TLS and HSTS note.

Adding security headers is a per-app decision, not a copy-paste default. The right Content-Security-Policy for your site depends on which origins your scripts, styles, fonts, and images load from. Copying someone else's policy will either break your page or leave gaps. Read your own Network tab, then write a policy that matches.

Where VibeCodeGuard fits, and what it does not prove

VibeCodeGuard's Launch Check reads your Cloudflare Pages URL from the outside and reports which security headers the response is missing — Content-Security-Policy, X-Frame-Options, Referrer-Policy, Permissions-Policy, and related headers — in seconds, with severity and fix direction. It checks the same thing your DevTools Network tab shows, so a missing _headers file surfaces as a flagged gap before the link gets shared. It is a public-surface scan, not a penetration test or a code review.

What it does not do is decide your policy for you. Whether a given Content-Security-Policy value is correct for your app depends on your real origins and your code, and that judgment belongs to you. The scan can tell you a header is absent or obviously permissive; it cannot confirm that the policy you wrote matches what your app actually needs.

Next step: run curl -I against your live Cloudflare Pages URL, note which of the four headers above are missing, then add a _headers file with a /* block and redeploy. Run a focused Launch Check against the public URL before sharing it, so a missing header shows up in your scan and not in someone else's.

A clean header check does not prove your app is secure. Headers are one layer. A site can return every recommended header and still have an exposed data endpoint, a leaked key in the bundle, or an open CORS policy — none of which a header fixes. Treat missing security headers as a quick, cheap thing to close, and treat a clean header result as one signal among several, not a guarantee.

> launch check

Scan the public surface before launch.

Get severity, evidence, and practical fix guidance for the checks VibeCodeGuard can run from the outside.