No Content-Security-Policy
Your page sends no Content-Security-Policy, so any script that gets onto it can run and send data anywhere.
- Severity
- Medium
- Who can exploit it
- Anyone who gets a script onto your page
- Found by
- PatchLabs Live posture: your home page’s response headers
- Lab proof
- Not lab-proven: read from your home page’s response
What it is
A Content-Security-Policy (CSP) is a response header that lists where your page may load scripts, styles, images and connections from. The browser blocks everything else. PL-030 means your home page sends no CSP at all.
A CSP doesn’t fix a bug; it limits the damage of one. If user content reaches the page as HTML (PL-024), or a third-party script is compromised, a good policy stops the injected script from running or from sending what it reads to another server. That matters more than usual in a Supabase app, because supabase-js keeps the signed-in session in localStorage by default, where any script on the page can read it.
Why AI builders produce it
A CSP has to name every place your app talks to, and that list is specific to each app: your Supabase project, your analytics, your fonts. Generators can’t know it, and a wrong guess breaks the app, so the safe default for a tool is to leave it out.
What we see
A synthetic example for app.example.com, shortened. Not taken from any real site.
$ curl -sI https://app.example.com
HTTP/2 200
content-type: text/html; charset=utf-8
# no content-security-policy line: any script may run and connect anywhere
Check it yourself
curl -sI https://your-app.com | grep -iE 'content-security-policy'
# also check index.html for <meta http-equiv="Content-Security-Policy" ...>
In the browser, open DevTools → Console after you add a policy: every blocked load is listed with the directive that blocked it, which is how you tune it.
The fix
Start in report-only mode. Content-Security-Policy-Report-Only takes the same value, blocks nothing and prints violations in the console. Use the app for a few days, add the sources it genuinely needs, then switch the header name to Content-Security-Policy. A starter policy for a Vite or React app on Supabase, with your project ref in place of YOUR-PROJECT:
default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data: blob: https://YOUR-PROJECT.supabase.co;
font-src 'self' data:;
connect-src 'self' https://YOUR-PROJECT.supabase.co wss://YOUR-PROJECT.supabase.co;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
object-src 'none'
The wss:// entry is for Realtime; storage downloads come from the same supabase.co host. Add what your app uses on top, for example https://fonts.googleapis.com to style-src and https://fonts.gstatic.com to font-src for Google Fonts, or your analytics host to script-src and connect-src. Avoid 'unsafe-inline' in script-src: it lets injected scripts run, which is what the policy is for.
Vercel
{
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "Content-Security-Policy-Report-Only", "value": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://YOUR-PROJECT.supabase.co; font-src 'self' data:; connect-src 'self' https://YOUR-PROJECT.supabase.co wss://YOUR-PROJECT.supabase.co; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'" }
]
}
]
}
Next.js apps can set the same header from headers() in next.config.js, or per request in middleware when you need a nonce for inline scripts.
Netlify
# public/_headers (Vite copies public/ into the publish folder)
/*
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://YOUR-PROJECT.supabase.co; font-src 'self' data:; connect-src 'self' https://YOUR-PROJECT.supabase.co wss://YOUR-PROJECT.supabase.co; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'
The file must end up in your publish directory (dist for Vite). Bolt projects deployed to Netlify use this file too. The same rule can live in netlify.toml under [[headers]] if you prefer one config file.
Cloudflare
For a site proxied through Cloudflare (orange cloud), add a Response Header Transform Rule (Rules → Transform Rules in the dashboard): match all incoming requests to your hostname, then Set static header Content-Security-Policy-Report-Only to:
default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://YOUR-PROJECT.supabase.co; font-src 'self' data:; connect-src 'self' https://YOUR-PROJECT.supabase.co wss://YOUR-PROJECT.supabase.co; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'
On Cloudflare Pages or Workers static assets, a _headers file in your build output works the same way as on Netlify.
Lovable
Lovable’s documentation (checked 10 Nov 2026) doesn’t describe a way to set your own response headers on Lovable hosting. It does let you connect a custom domain through Cloudflare: choose “Uses Cloudflare or similar proxy” when you add the domain. Cloudflare can then add the header for you, as in the Cloudflare tab. If that changes, the header and value above are the same wherever you set them.
A CSP can also go in index.html as a meta tag, which works on any host. Meta-tag policies can’t use frame-ancestors or report-only mode, so test the policy in a local build first.
<!-- index.html, inside <head>, before any script -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://YOUR-PROJECT.supabase.co; font-src 'self' data:; connect-src 'self' https://YOUR-PROJECT.supabase.co wss://YOUR-PROJECT.supabase.co; base-uri 'self'; form-action 'self'; object-src 'none'">
Live posture findings come with the header or setting to change. PatchLabs never changes your hosting or your repo.
How PatchLabs finds it
[LOAD] https://app.example.com/ → 200 content-security-policy not sent FLAG report-only seen: no
Live posture reads the final response of its one page load and looks for a Content-Security-Policy header, or a <meta http-equiv="Content-Security-Policy"> tag in the HTML. A report-only header on its own doesn’t pass, because it blocks nothing. It doesn’t grade how strict the policy is: a policy with 'unsafe-inline' scripts passes this check, so review yours against the starter above.
When the pattern is intended
A static page with no sign-in, no user content and no third-party scripts gains little from a CSP. Any app with accounts gains a lot.
Why it’s rated medium
Medium: nothing leaks from a missing CSP alone, but it is the difference between a cross-site scripting bug that reads every session and one that can’t send anything anywhere. 15 points in the Live posture grade.
Related findings
User content rendered as raw HTML
Text one user wrote goes into the page as HTML, so it can run scripts in other users' browsers.
PL-031Any site can frame the page
Neither frame-ancestors nor X-Frame-Options is set, so another site can load your app invisibly inside its own.
PL-034Page loads scripts or styles over plain HTTP
Your HTTPS page pulls a script or stylesheet from an http:// address, which anyone on the network can replace.
Guides
Free check
Does your site have PL-030?
Type your site’s address. The free headers check loads your public home page once, like any visitor, and grades HTTPS, the certificate, security headers, cookie flags and mixed content. No sign-up, and the result isn’t stored.
Last reviewed 10 Nov 2026. Finding IDs are stable: link to them freely. Spotted a mistake? Tell us.