A Content Security Policy Starter You Can Actually Ship
CSP looks scary until you build one in report-only mode, tighten script sources, and iterate. Here’s a starter path that doesn’t brick your site on day one.
Part of the Business Cybersecurity Basics series. Start here: Cybersecurity checklist for small businesses.
CSP is one of the few headers that can both stop a class of XSS and take down your marketing site if you copy a strict example from a blog dated 2018. The goal isn’t a perfect policy on Friday afternoon. It’s a deliberate path from “nothing” to “tight enough.”
Start in report-only
Send Content-Security-Policy-Report-Only first. Browse the critical flows: login, checkout, editor, docs. Collect violations. Fix real issues (inline scripts you forgot, third-party widgets) before flipping to enforcing mode.
Draft the policy with CSP Builder, then review the rest of your header set in Security Headers and Headers Checklist.
script-src tactics that scale
- Nonces: server generates a per-request nonce; every legitimate inline script carries it. Fits SSR apps well.
- Hashes: good for a handful of static inline snippets; painful if they change often.
- Avoid
unsafe-inlinelong term. It’s the escape hatch that undoes much of CSP’s XSS value.
Third parties without despair
Analytics, chat widgets, and payment SDKs each need explicit hosts. Prefer loading fewer vendors. Every new host is a trust decision, not a checkbox. Document why each domain is on the allowlist so the next engineer doesn’t “temporarily” add *.
A sane starter set of directives
default-src 'self'as the fallback.script-srcwith nonces/hashes + known hosts.style-srccarefully - many apps still need a temporaryunsafe-inlinefor CSS; plan to shrink it.img-src 'self' data: https:or a tighter CDN allowlist.frame-ancestors 'self'(complement clickjacking defenses; pair with your framing headers).base-uri 'self'andobject-src 'none'as cheap wins.
Rollout plan
- Report-only in staging for a week.
- Report-only in production with sampling if traffic is huge.
- Enforce on a canary population.
- Enforce everywhere; keep a break-glass path documented.
This sits beside the basics in cybersecurity for small businesses - CSP isn’t a substitute for MFA and patches, but it’s a strong web layer.
Inventory inline scripts before you tighten
Search the codebase for <script> without src, for onclick= handlers, and for tag-manager snippets that inject more scripts. Each is a CSP negotiation. Prefer external files with nonces. Where a CMS forces inline, isolate those templates and hash them deliberately.
Marketing teams adding a new pixel should have a mini-review: which directive changes, who approved the host, when we’ll revisit.
SPAs and CSP
Client-side routers are fine under CSP. Eval-heavy charting libraries are not. If a dependency requires unsafe-eval, treat that as a vendor defect: sandbox it, replace it, or confine it to an admin route with a different policy if your architecture allows partitioned policies.
Measuring success
Success looks like declining violation noise, no rise in user-reported blank pages, and a policy that no longer needs unsafe-inline for scripts. Keep a before/after note in your security changelog so audits see progress, not only a header dump.
Field notes from teams who shipped this
The pattern that keeps showing up: write the constraint first, then the steps, then the failure modes. Teams that only publish happy-path screenshots create tickets. Teams that document the ugly path create trust.
Schedule a short review ninety days after publishing. Check whether product UI names still match, whether linked tools still exist, and whether support still hears the same questions. Update the page or merge it. Standing still is how useful posts become interchangeable again.
If you adapt this article for internal wikis, keep the examples tied to your stack names. The moment you generalize back to “best practices for organizations,” you’ve started erasing the specificity that made the piece worth saving.
What CSP does not cover
CSP protects web document loads in browsers. It does not secure native apps, email HTML, or server-side template injection in PDF generators. Keep the scope honest in security reviews so nobody thinks one header closes XSS everywhere.
Combine CSP with solid output encoding, dependency updates, and authz checks. Headers are layers, not laurels.
When a violation report spikes after a release, sort by blocked-uri and document-uri. One misconfigured marketing page shouldn’t loosen policy for the authenticated app - split policies by app tier if needed.
Keep a rollback snippet of the previous header values in your deploy notes. CSP mistakes are highly visible; rollbacks should be boring.
Name an owner
CSP without an owner drifts into unsafe-inline again within a year. Assign a human, put reviews on calendar, and require a ticket for every new host allowlisted. Treat allowlist growth like dependency growth - justified or rejected.
Share monthly violation highlights in eng forum so the policy stays visible, not archaeological.
FAQ
Will CSP break browser extensions?
Sometimes users see extension-injected script violations. Don’t weaken policy globally for that; educate support that some noise is normal.
Where should reports go?
A small collector you own, or a reputable reporting service. Don’t log raw report bodies into a public Slack channel forever.