TL;DR: Content Security Policy (CSP) is a security header that acts as a bouncer for your web page. It tells the browser exactly what is allowed to load — which scripts, styles, images, fonts, and API connections are permitted. Your AI adds it because it is a genuine security best practice that protects against cross-site scripting (XSS) attacks. The problem? AI often configures it too restrictively, blocking things your app actually needs — like CDN-hosted libraries, Google Fonts, or inline scripts. When something is blocked, it fails silently (no visible error on the page) but the browser console tells you exactly what happened. Fix it by adding the blocked domains to the right CSP directive, or remove CSP entirely for personal projects where it is causing more trouble than it is worth.

What CSP Actually Is (Plain English)

Imagine you are throwing a party at your house. You hire a bouncer and give them a guest list. Anyone on the list gets in. Anyone not on the list gets turned away at the door — no exceptions, no "but I know the host," nothing.

Content Security Policy works exactly like that, but for your web page.

CSP is a security header — a line of configuration that your server sends to the browser along with your web page. It says: "Here is a list of approved sources. Only load resources from these sources. Block everything else."

That "everything else" includes:

  • Scripts — JavaScript files that run on your page
  • Styles — CSS files that control how your page looks
  • Images — Every image displayed on your page
  • Fonts — Custom fonts loaded from services like Google Fonts
  • API connections — Fetch requests and AJAX calls your app makes
  • Frames — Embedded content like YouTube videos or iframes

Here is what a real CSP header looks like in code:

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; img-src 'self' data:

That looks intimidating, but it breaks down simply:

  • default-src 'self' — By default, only allow resources from my own domain
  • script-src 'self' — Only allow scripts from my own domain
  • style-src 'self' https://fonts.googleapis.com — Allow styles from my domain AND Google Fonts
  • font-src https://fonts.gstatic.com — Allow fonts from Google's font CDN
  • img-src 'self' data: — Allow images from my domain and data URIs (inline images)

Anything not on that list? The browser blocks it. No negotiation.

Why "silently"? When CSP blocks something, your page does not show an error message to the user. The image just does not appear. The script just does not run. The font falls back to a default. Your user sees a broken page with no explanation — but the browser console (F12 → Console tab) shows exactly what was blocked and why.

Why Your AI Puts CSP in Your Code

When you ask Claude Code, Cursor, or Copilot to build a web application, they are trained on millions of production codebases. Those codebases include security headers because every major security organization recommends them. OWASP lists CSP as a critical defense. Google requires it for Chrome extensions. Mozilla treats it as a baseline for web security.

Your AI is not being paranoid — it is following genuine best practices. CSP is the primary defense against cross-site scripting (XSS), which is consistently one of the most common web vulnerabilities. An XSS attack happens when someone injects malicious JavaScript into your page — stealing user data, redirecting users, or hijacking sessions. CSP prevents this by ensuring only approved scripts can run.

The problem is not that your AI adds CSP. The problem is that your AI does not always know what your specific app needs. It generates a CSP that looks correct in general but blocks resources your particular project depends on — like a CDN-hosted library, an analytics script, or an embedded widget.

This is where most vibe coders hit the wall. Everything was working. AI added a security header. Now things are broken. The AI meant well, but it did not account for your specific setup.

The Most Common CSP Directives (What Each One Controls)

CSP is made up of directives — each one controls a specific type of resource. Here are the ones you will see most often in AI-generated code:

default-src — The Fallback Rule

This is the catch-all. If a specific directive is not set, the browser uses default-src as the rule. Setting default-src 'self' means "unless I say otherwise, only allow resources from my own domain."

default-src 'self'

This is the most common starting point your AI will use. It is restrictive by default, which is the security-conscious approach — deny everything, then allow what you need.

script-src — Which Scripts Can Run

Controls where JavaScript can be loaded from. This is the most security-critical directive because malicious scripts are the primary threat CSP defends against.

script-src 'self' https://cdn.jsdelivr.net

This allows scripts from your own domain and from the jsDelivr CDN. If you are loading React, Vue, or any other library from a CDN, you need that CDN in script-src.

style-src — Which Stylesheets Can Load

Controls where CSS can come from. If you use Google Fonts, Bootstrap, or Tailwind from a CDN, you need those domains here.

style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net

img-src — Which Images Can Display

Controls where images can be loaded from. If your app displays user avatars from Gravatar, product images from an S3 bucket, or placeholder images from a service, those domains need to be listed.

img-src 'self' data: https://*.amazonaws.com

The data: part allows inline images (base64-encoded images embedded directly in HTML or CSS). Many UI libraries use these.

connect-src — Which APIs You Can Call

Controls where your JavaScript can make network requests — fetch calls, AJAX, WebSockets. If your front end talks to a backend API on a different domain, that domain needs to be here.

connect-src 'self' https://api.example.com wss://realtime.example.com

This is the one that breaks your app when your AI sets connect-src 'self' but your frontend calls an API on a different subdomain or service.

font-src — Which Fonts Can Load

Controls where custom fonts come from. If you use Google Fonts (and you probably do — your AI loves Google Fonts), you need:

font-src 'self' https://fonts.gstatic.com

Note: Google Fonts requires two entries — fonts.googleapis.com in style-src (for the CSS stylesheet) and fonts.gstatic.com in font-src (for the actual font files). Missing either one breaks your fonts.

The pattern: Each directive controls one type of resource. 'self' means "my own domain." Everything else is an explicit allowlist of domains you trust. If it is not listed, it is blocked.

What Happens When CSP Blocks Something

This is the part that trips up most vibe coders: CSP failures are silent on the page but loud in the console.

When CSP blocks a resource, here is what you see:

  • Blocked script: The feature that script powered just does not work. No error message on the page. Button clicks do nothing. Interactive elements are dead.
  • Blocked stylesheet: Your page loads with default browser styling — ugly, unstyled HTML. Or specific components look wrong because their CSS was blocked.
  • Blocked image: A broken image icon or empty space where the image should be.
  • Blocked font: Text renders in a fallback system font instead of the custom font you chose.
  • Blocked API call: Your app cannot communicate with its backend. Forms do not submit. Data does not load. Everything feels frozen.

And here is what you see in the browser console (press F12 or Cmd+Option+I on Mac):

Refused to load the script 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.min.js'
because it violates the following Content Security Policy directive: "script-src 'self'".

That error message is actually incredibly helpful. It tells you:

  1. What was blocked — the Bootstrap script from jsDelivr
  2. Which directive blocked itscript-src 'self'
  3. Why — the script's source (cdn.jsdelivr.net) is not in the allowed list (only 'self' is allowed)

The fix is right there in the error: add https://cdn.jsdelivr.net to your script-src directive.

How to Debug CSP Issues (Step by Step)

When something breaks and you suspect CSP, here is the exact process:

Step 1: Open the Browser Console

Press F12 (Windows/Linux) or Cmd+Option+I (Mac). Click the Console tab. CSP violations show up as red or yellow warnings starting with "Refused to..."

Step 2: Read the Error Message

Every CSP error follows the same pattern:

Refused to [action] '[resource URL]' because it violates the following
Content Security Policy directive: "[directive] [allowed sources]".

The error literally tells you which directive to update and what domain to add.

Step 3: Find Your CSP Header

Your CSP could be in several places depending on what your AI generated:

  • A meta tag in your HTML:
<meta http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self'">
  • A server header (in Express.js, Next.js, or your backend):
// Express.js
app.use((req, res, next) => {
  res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self'");
  next();
});
  • A middleware like Helmet (very common in AI-generated Node.js code):
const helmet = require('helmet');
app.use(helmet());  // This sets CSP automatically!

That last one is the sneakiest. When your AI adds helmet(), it enables CSP with strict defaults. You might not even realize CSP is active because the word "CSP" never appears in your code.

Step 4: Add the Blocked Domain

Once you know which directive is blocking and what domain needs access, add it:

// Before (blocking Bootstrap from CDN)
script-src 'self'

// After (allowing Bootstrap from CDN)
script-src 'self' https://cdn.jsdelivr.net

Step 5: Test Again

Reload the page. Check the console. If new CSP errors appear, repeat the process. It is common to need multiple rounds — one blocked resource reveals the next one.

Pro tip: Use Content-Security-Policy-Report-Only instead of Content-Security-Policy during development. This header reports violations in the console but does not actually block anything. It lets you see what would be blocked without breaking your app while you build up the right policy.

What AI Gets Wrong About CSP

AI coding tools are great at generating CSP headers. They are not great at generating the right CSP headers for your specific project. Here are the most common mistakes:

"default-src 'self'" with no exceptions

The problem: Your AI sets default-src 'self' and nothing else, which blocks every external resource — CDNs, APIs, fonts, analytics, everything. This is technically the most secure setting, but it breaks almost every real-world app.

The fix: Tell your AI: "Update the CSP to allow [specific CDN/service]. Here is the error from my console: [paste error]."

Using Helmet with default settings

The problem: In Node.js/Express projects, AI loves to add app.use(helmet()). Helmet's default CSP is very strict. Your AI adds it as a one-liner without configuring it for your project's actual needs.

The fix: Configure Helmet explicitly:

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "https://cdn.jsdelivr.net"],
      styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
      fontSrc: ["'self'", "https://fonts.gstatic.com"],
      imgSrc: ["'self'", "data:", "https:"],
      connectSrc: ["'self'", "https://api.yourdomain.com"]
    }
  }
}));

Blocking inline scripts and styles

The problem: By default, CSP blocks all inline <script> and <style> tags. Many AI-generated apps use inline scripts (like onclick handlers) and inline styles. The AI writes the CSP and the inline code in the same project without realizing they conflict.

The fix: Either add 'unsafe-inline' to script-src and style-src (quick and easy), or refactor inline code to external files (more secure). See the unsafe-inline section below.

Forgetting Google Fonts needs two domains

The problem: AI adds fonts.googleapis.com to style-src but forgets fonts.gstatic.com in font-src. The CSS stylesheet loads fine, but the actual font files are blocked. Your text renders in Times New Roman instead of Inter.

The fix: Always pair them:

style-src 'self' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;

Not accounting for WebSocket connections

The problem: Your app uses WebSockets for real-time features (chat, live updates). AI sets connect-src 'self' which only allows HTTP connections to your own domain. WebSocket URLs (wss://) are blocked.

The fix: Add the WebSocket URL to connect-src:

connect-src 'self' wss://your-websocket-server.com

The 'unsafe-inline' and 'unsafe-eval' Question

These two keywords come up constantly in CSP discussions, and your AI will have opinions about them. Here is what they actually mean:

'unsafe-inline'

By default, CSP blocks all inline code — any <script> tag written directly in your HTML, any onclick="doSomething()" attribute, any <style> tag in the page body. Adding 'unsafe-inline' to a directive turns that protection off.

// Blocks inline scripts (secure but may break your app)
script-src 'self'

// Allows inline scripts (less secure but works)
script-src 'self' 'unsafe-inline'

Why is it called "unsafe"? Because inline scripts are the main way XSS attacks work. An attacker injects a <script> tag into your page (through a comment form, search field, or any user input). If 'unsafe-inline' is enabled, that injected script runs. If it is disabled, CSP blocks it.

When to use it:

  • Personal projects and prototypes: Use it freely. The risk is minimal and the convenience is real.
  • Production apps with user input: Avoid it. Use nonces or hashes instead (your AI can generate these if you ask).
  • Static sites with no user input: Generally safe to use. No user input means no injection vector.

'unsafe-eval'

This allows JavaScript's eval() function and similar dynamic code execution. Some libraries and frameworks (especially older ones) use eval() internally. If your app breaks with a CSP error mentioning eval, a library you are using needs this.

script-src 'self' 'unsafe-eval'

When to use it:

  • If a library requires it: Some template engines and older build tools need eval. Add it if you must, but understand it weakens your CSP.
  • If you can avoid it: Modern libraries generally do not need eval. If your AI suggests it, ask: "Is there a way to configure this library without requiring unsafe-eval?"

The practical reality: Security purists will tell you never to use either of these. That advice is correct for banks and healthcare apps. For a personal project, a portfolio site, or a prototype you are showing to investors? Use 'unsafe-inline' for styles, be cautious with it for scripts, and avoid 'unsafe-eval' unless a library explicitly requires it. Shipping a working app is better than shipping a perfectly secured app that nobody can use.

When to Use CSP vs. When to Skip It

Not every project needs CSP. Here is a practical breakdown:

Use CSP when:

  • Your app handles user data — login forms, payment info, personal data. CSP is a critical layer of defense against XSS attacks that could steal this data.
  • You are going to production — real users, real traffic, real stakes. CSP belongs in your security headers alongside HTTPS and CSRF protection.
  • You accept user-generated content — comments, reviews, profile bios, forum posts. Any place users can input HTML or text that gets rendered on the page is an XSS risk.
  • You are building a SaaS product — your customers expect security. CSP is a checkbox on security audits.

Skip CSP (for now) when:

  • You are prototyping — move fast, break things, worry about security headers later.
  • It is a personal project — a portfolio site, a tool you built for yourself, a learning project. The attack surface is minimal.
  • CSP is blocking you from shipping — if you have spent an hour debugging CSP on a project that has zero users, remove it and come back to it. Your time is better spent elsewhere.
  • You do not understand what it does — a misconfigured CSP can be worse than no CSP. A false sense of security is dangerous. Learn it properly (you are doing that right now) before relying on it.

The middle ground: Use Content-Security-Policy-Report-Only during development. It logs violations without blocking anything. When you are ready for production, swap it to Content-Security-Policy to enforce the rules. This way you can build your policy gradually without breaking anything along the way.

CSP vs. CORS — They Are Not the Same Thing

Vibe coders often confuse CSP and CORS because both are browser security features that produce confusing console errors. Here is the difference:

  • CSP (Content Security Policy): Rules YOU set on YOUR page about what can load. "My page is only allowed to load scripts from these domains." It is like a guest list you give to the bouncer at your party.
  • CORS (Cross-Origin Resource Sharing): Rules set on a SERVER about who can request its data. "Only these websites are allowed to call my API." It is like a restaurant deciding which delivery apps can place orders.

CSP errors say: "Refused to load..." (your page tried to load something it is not allowed to). CORS errors say: "Access to fetch... has been blocked by CORS policy" (another server refused your request).

Different problem, different fix, different header. But both show up as red errors in the console, which is why they get confused.

What to Learn Next

CSP is one piece of the browser security puzzle. Here is the roadmap for understanding the full picture:

  • What Is XSS (Cross-Site Scripting)? — The attack CSP is designed to prevent. Understanding XSS makes CSP click immediately — you will see exactly why each directive exists.
  • Security Basics for AI Coders — The big-picture guide to web security when you are building with AI. CSP fits into a broader security posture.
  • Common Security Vulnerabilities — Where CSP sits in the landscape of web security threats. XSS is just one of many.
  • What Is CORS? — The other browser security feature that produces confusing console errors. Learn the difference so you can debug faster.
  • What Is HTTPS/SSL? — Another security layer your AI adds automatically. HTTPS encrypts data in transit; CSP controls what loads on the page.
  • What Is CSRF? — Cross-Site Request Forgery is another common attack that works differently from XSS. Understanding both helps you see the full threat model.

🤖 Prompt to try with your AI:

"I'm getting Content Security Policy errors in my browser console. Here are the errors: [paste your errors]. Update my CSP header to allow these resources while keeping the policy as restrictive as possible. Explain each change you make."

Frequently Asked Questions

What is Content Security Policy (CSP)?

Content Security Policy is a security header that tells the browser exactly what resources are allowed to load on your web page — which scripts can run, which images can display, which fonts can load, and where your page can make network requests. Think of it as a bouncer for your website. If a resource is not on the guest list, the browser blocks it. CSP is the primary defense against cross-site scripting (XSS) attacks, where an attacker tries to inject malicious scripts into your page.

Why does my AI add CSP headers to my code?

AI coding tools like Claude Code and Cursor are trained on production-quality codebases that include security best practices. CSP is one of the most recommended security headers — OWASP, Google, and Mozilla all recommend it. So AI tools add it by default, the same way they add HTTPS or input validation. It is genuinely good practice for production apps. The problem is that AI sometimes configures it too restrictively for your specific project, blocking resources your app actually needs.

What does a CSP error in my browser console mean?

A CSP error means the browser blocked something from loading because your Content Security Policy did not allow it. The error message tells you exactly what was blocked and which directive blocked it. For example: "Refused to load the script because it violates the Content-Security-Policy directive: script-src 'self'." This means a script tried to load from an external source, but your CSP only allows scripts from your own domain. The fix: add that external source to script-src.

What is 'unsafe-inline' and should I use it?

The 'unsafe-inline' keyword tells CSP to allow inline scripts and styles — code written directly in your HTML with <script> or <style> tags instead of loaded from separate files. It is called "unsafe" because inline scripts are the primary vector for XSS attacks. For personal projects and prototypes, using 'unsafe-inline' is fine and saves you from debugging headaches. For production apps handling user data, avoid it and use nonces or hashes instead — ask your AI to help set those up.

How do I fix CSP blocking my CDN resources?

Add the CDN domain to the appropriate CSP directive. If Bootstrap CSS from cdn.jsdelivr.net is blocked, add it to style-src. If a Google Fonts stylesheet is blocked, add fonts.googleapis.com to style-src and fonts.gstatic.com to font-src. The browser console error tells you exactly which directive to update and which domain to allow. Copy the error and paste it to your AI — it will generate the corrected CSP for you.

Can I just remove CSP entirely?

Yes, and for personal projects or prototypes, that is perfectly fine. CSP is a defense-in-depth measure — it adds a layer of protection against XSS attacks but it is not the only defense. Input sanitization, output encoding, and using frameworks that auto-escape HTML all help too. If CSP is causing you more debugging pain than the security is worth at your current stage, remove it. Add it back when you go to production with real users and real data. See our Security Basics for AI Coders guide for the full picture.

What is the difference between CSP and CORS?

CSP and CORS are both browser security features but they protect different things. CSP controls what resources YOUR page is allowed to load — it is a policy you set on your own site. CORS controls whether OTHER sites are allowed to request data from your server — it is about cross-origin API access. CSP is like a guest list for your party (who can come in). CORS is like deciding whether to answer the door when a stranger knocks (who you let talk to you). Different problem, different fix, different header.