A copied CSP either breaks payments or protects nothing
A content security policy is a response header the browser enforces: it lists which origins are allowed to load scripts, frame the page, connect over the network, and so on. The launch risk with third-party embeds is narrow and concrete. You add Stripe Checkout, PostHog, and a hosted auth widget, paste a CSP you found in a starter template, and one of two things happens. Either the policy is too strict and the browser blocks the Stripe iframe or the analytics script outright, so checkout fails or events never fire. Or the policy is so permissive, often a single wildcard, that it allows any origin and gives you almost none of the protection a CSP exists to provide.
Both outcomes are observable on your own app. A blocked script logs a CSP violation in the browser console and the feature visibly does not work. An overly broad policy is harder to notice because nothing breaks, which is exactly why it ships. A header that reads default-src * or script-src with a wildcard is technically present and does nothing useful against script injection.
The goal of this note is not a perfect locked-down policy. It is the minimum set of source directives that lets payments, analytics, and auth iframes work while still constraining everything else.
Why AI-built apps land on a broken or wide-open CSP
AI coding tools generate working app code quickly, but the CSP is almost never derived from the actual embeds in your project. There are two common paths and both end badly.
The first is a copied starter policy. The model emits a CSP it has seen in training data, scoped for a generic app with no payment or analytics integrations. When you later add Stripe.js or PostHog, the new origins are not in the policy, the browser blocks them, and the failure shows up only at runtime in the deployed app, not at build time.
The second is the escape hatch. When a builder hits a CSP error, the fast fix the assistant often suggests is to widen the policy until the error stops, which usually means a wildcard or removing the directive. That makes the console error disappear and the feature work, so it looks finished, while the policy now allows scripts from anywhere.
- The generated CSP rarely matches the embeds you actually added, so it breaks them or was never enforcing anything.
- Third-party SDKs need specific origins, and Stripe and others change or document these per integration, so a guessed list is usually wrong.
- Fast deploy means the public URL exists before anyone reviews the header against the real list of embeds. This is one slice of a larger header gap; see the broader security headers for AI-built apps note.
Map directives to your three embed categories
You can verify your own policy from the outside, with no attack. Fetch the production response headers and read the content-security-policy value, or open DevTools, load the page, and watch the Console for CSP violation messages while you exercise checkout, the auth flow, and any analytics-triggering action. A violation tells you exactly which directive and origin the browser blocked.
Match the directives to what each category needs. Treat the specific origins below as the shape to confirm against current vendor docs, not a fixed list to paste, because vendors update them.
Suspicious results: a wildcard in script-src or default-src; no content-security-policy header at all; or a console full of blocked requests for an embed you depend on. Safer result: explicit, named origins per directive, no wildcard in script-src, and your checkout, login, and analytics all working with a clean console.
- Payments (Stripe Checkout and Stripe.js): script-src must allow the Stripe script origin (js.stripe.com), and frame-src must allow the Checkout and Elements iframe origins so the payment iframe is not blocked. connect-src must allow Stripe's API origin for the network calls. If frame-src omits Stripe, the payment iframe will not render.
- Analytics (PostHog and similar): script-src must allow the analytics script origin, and connect-src must allow the ingestion endpoint your project is configured to use, including any reverse-proxy path. A missing connect-src entry is the usual reason events silently stop.
- Auth embeds (hosted login widgets and OAuth iframes): script-src for the widget script, frame-src for any iframe the provider renders, and connect-src for the token or session endpoint it calls.
Distinguish two separate jobs of a CSP here. frame-src controls what your page is allowed to embed, which is what makes payment and auth iframes load. frame-ancestors controls who is allowed to embed your page, which is a clickjacking control covered in the X-Frame-Options and frame-ancestors note. Allowing Stripe in frame-src does not loosen your clickjacking protection.
Build the policy from your real embed list, not a template
Start from a restrictive base and open exactly the origins your embeds require. Set default-src to self, then add per-directive allowances for each third party you actually use. Do not reach for a wildcard to make an error go away; add the specific origin the violation names instead.
For inline scripts that a framework injects, prefer a nonce or a hash over unsafe-inline. A nonce is a random value generated per response, placed on the script-src directive and on each allowed inline script tag, so only scripts carrying that value run. This is the safer way to permit the small inline snippets some embeds use without opening script-src to all inline code. Generating a fresh nonce per request is what makes it meaningful; a static nonce is the same as no nonce.
What this does not cover: a CSP constrains what the browser loads and runs. It does not authenticate users, authorize what a request is allowed to do on the server, or validate the data an embed sends back. It reduces the impact of injected or third-party script in the browser; it does not replace server-side authorization or input validation. A correct CSP also does not vouch for the third party itself: if you allow an origin, you are trusting code from that origin.
- Begin with default-src self, then widen one directive at a time to the named origins your payment, analytics, and auth embeds need.
- Replace unsafe-inline with a per-request nonce or a hash where a framework or embed requires inline script.
- Set frame-src to the specific payment and auth iframe origins rather than a wildcard, so only those providers can be embedded.
Where VibeCodeGuard fits, and what it does not prove
VibeCodeGuard's Launch Check reads the content-security-policy header on your production URL and flags the two failure shapes this note is about: a missing CSP, and wildcard or overly broad directives that leave payment and auth iframes effectively unsandboxed. It reports the gap with severity and fix direction so you see, from the outside, whether the header you shipped actually constrains anything. It checks the policy a visitor's browser receives.
What it does not do is run your checkout, log in, or fire an analytics event to confirm every embed still works under a tightened policy. That functional pass is yours to run in the browser, and authoring the exact origin list for your integrations is a manual task against current vendor docs. The scan reads the policy; it does not exercise your payment or auth flows.
Next step: open DevTools on your production page, exercise checkout, login, and one analytics action, and confirm a clean console with named origins in the header. Then run a focused Launch Check before sharing the URL publicly, so a missing or wide-open CSP shows up in your scan and not after launch.
A clean CSP result on the public surface does not prove your app is secure, only that the header is present and not trivially wide open. It does not test that your embeds function, and it says nothing about server-side authorization or the trustworthiness of the origins you allowed. Treat a missing or wildcard CSP as a launch blocker, and treat a clean header as one signal among several.
> launch check
Scan the public surface before launch.
Get severity, evidence, and practical fix guidance for the checks VibeCodeGuard can run from the outside.