CSP has frame-ancestors and form-action and no wildcard script-src
The policy names frame-ancestors, form-action, and does not allow scripts from everywhere.
- Severity
- medium
- Tier
- Passive: runs on any URL
- Category
- Security
- Fix
- Summary below; full runbook in the Fix Kit, Module 01: Ship it
- Id
- csp-strength
What this means
Passing means your policy names frame-ancestors, names form-action, and does not let scripts load from everywhere. Failing means script-src contains * or a bare https:, which allows any origin to run code in your page. A policy that exists but allows anything gives you the header line without the protection.
How we test it
We read the content-security-policy header on the home page response and look for three things: a frame-ancestors directive, a form-action directive, and a script-src whose sources do not include * or a bare https: scheme. All three present passes. A missing frame-ancestors or form-action warns. A wildcard script source fails. If there is no CSP header at all, the previous check fails and this one is not scored.
Why it matters
A CSP with a wildcard script-src protects nothing. frame-ancestors is what stops another site from loading yours inside an invisible frame; form-action is what stops an injected form from posting your users' input elsewhere. Browsers check form-action against the final destination after redirects, which is how a sports data product's billing button died silently behind a correct-looking policy; the fix was one domain in one directive. The kit's template ships with frame-ancestors 'none', form-action 'self' plus the payment provider's checkout and billing domains, and a script-src that lists origins by name.
The fix
In the CSP value in vercel.json or the csp constant in next.config.ts, keep frame-ancestors 'none', keep form-action 'self' plus every domain a form can reach after a redirect (for Stripe, https://checkout.stripe.com https://billing.stripe.com), and replace any * or bare https: in script-src with the named origins the site loads, found with the kit's grep over the built HTML, JS and CSS. Confirm with curl -sI https://YOUR_DOMAIN | grep -i '^content-security-policy' | grep -c "form-action" (expected 1), the same with "frame-ancestors 'none'" (expected 1), and the kit's wildcard test grep -cE "script-src[^;]*(\*|https:)[ ;]" (expected 0), then check the browser console for any "Refused" line.
The full runbook is in the Fix Kit, Module 01.
Prompt for your AI
Paste this into the coding agent that built the app. It reproduces the finding and stops before changing anything; the fix steps are in the kit.
Do not change anything. This is a verification pass. Run: curl -sI https://YOUR_DOMAIN | grep -i '^content-security-policy' and show me the full header value. Run: curl -sI https://YOUR_DOMAIN | grep -i '^content-security-policy' | grep -c "form-action" and show me the number. Run: curl -sI https://YOUR_DOMAIN | grep -i '^content-security-policy' | grep -c "frame-ancestors 'none'" and show me the number. Run: curl -sI https://YOUR_DOMAIN | grep -i '^content-security-policy' | grep -cE "script-src[^;]*(\*|https:)[ ;]" and show me the number. Tell me which of the three conditions fails and quote the exact directive from the header. List the origins script-src would need to name instead of a wildcard, and which domains form-action would need, and stop there. Run the three grep -c commands again and show me the output. Expected on a passing site: 1, 1, 0.
Grade your own URL
Every grade lists this check with its evidence for your site. Two free grades a day.