June 14, 2026
Content Security Policy (CSP) Tutorial for Beginners
Learn what Content Security Policy (CSP) is, why it matters, and how to add a CSP header step by step to protect your web apps from XSS…
Anshika Tripathi
6 min read
Learn what Content Security Policy (CSP) is, why it matters, and how to add a CSP header step by step to protect your web apps from XSS without breaking everything.
This story was written with the assistance of an AI writing program.
Why does Content Security Policy (CSP) matter?
Imagine this: You ship a small front‑end change on Friday. You add "just one more" script from a third‑party CDN. On Monday, users report strange pop‑ups and stolen sessions. Someone found a cross‑site scripting (XSS) bug and used it to run their own JavaScript on your site.
Content Security Policy (CSP) exists to limit the damage from exactly this situation. Instead of letting any script or style on the page run, CSP lets you tell the browser: "Only load and execute content from these trusted places." If an attacker injects their own script, the browser can simply refuse to run it.
What is Content Security Policy (CSP)?
A Content Security Policy (CSP) is a set of security rules that your website sends to the browser. These rules tell the browser where it is allowed to load resources from and which kinds of content it can run.
A CSP header can say things like:
- "Only load scripts from my domain and this one CDN."
- "Never run inline scripts."
- "Only load images from these domains."
Most XSS attacks depend on making the browser run attacker‑controlled JavaScript. With a good CSP in place, many of those attacks fail because the browser blocks the untrusted script before it runs.
How CSP is Delivered
There are two fundamental ways to deliver a CSP to the browser.
1. CSP as an HTTP response header (preferred)
This is the standard, recommended way in real apps:
Content-Security-Policy: default-src 'self';Content-Security-Policy: default-src 'self';Your server sends this header with the HTTP response. The browser reads the header and enforces the policy for that page.
This approach is better for production because:
- It applies immediately before the browser parses HTML.
- It is easier to manage in code and configuration.
- It is harder for attackers to modify.
2. CSP in a <meta> tag (good for static sites and demos)
You can also define CSP directly in the HTML:
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self';"
/><meta
http-equiv="Content-Security-Policy"
content="default-src 'self';"
/>This is useful when:
- You host static HTML files.
- You cannot change server headers.
- You are building demos or prototypes.
However, the CSP meta tag only takes effect after the browser has parsed it in the HTML. For serious apps, prefer the HTTP header and treat the <meta> approach as a fallback.
CSP concepts: directives and sources
Two key ideas, directives and sources, build a CSP header.
Directives
A directive is a rule for a specific type of content. Common examples:
default-src– fallback rule for everything not covered by other directives.- script-src specifies the sources from which JavaScript can be loaded.
style-src– where CSS can be loaded from.img-src– where images can be loaded from.
Sources
A source tells the browser where content is allowed to come from. Examples:
'self'– the same origin as your page (same scheme, host, and port).https://cdn.example.com– a specific allowed domain.'none'– nothing is allowed.- Schemes like
data:orblob:if you really need them.
A simple CSP example
Content-Security-Policy: default-src 'self'; img-src 'self' https://images.example.com; script-src 'self';Content-Security-Policy: default-src 'self'; img-src 'self' https://images.example.com; script-src 'self';What this means:
default-src 'self';By default, everything must come from the same origin as the page.img-src 'self' https://images.example.com;Images can load from your own origin and fromhttps://images.example.com. Any other image source is blocked.script-src 'self';Scripts can only load from your own origin. No other domains are allowed for JavaScript.
With just this, you already get a basic "allow list" that protects against many accidental or malicious resource loads.
Step‑by‑step: adding a basic CSP
Adding CSP to a simple static site
Say you have a static portfolio site with HTML, CSS, and a bit of JavaScript. Everything is served from the same domain.
You can start with a CSP meta tag in your <head>:
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self';"
/><meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self';"
/>This policy says:
- Only your own domain is allowed for scripts, styles, and images.
- No external CDNs are allowed.
- Inline scripts and styles will be blocked unless you loosen the policy.
If something breaks:
- Open your browser's dev tools.
- Check the console tab.
- Look for "Refused to load" or "violates the following Content Security Policy directive" messages.
- Adjust the CSP based on what the browser tried to block.
Adding a CSP header in a server‑rendered app
For a typical back‑end app (Node/Express, Django, Rails, Laravel, etc.), you configure the CSP as an HTTP header.
Conceptually, you send:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'Implementation examples (conceptual, not full code):
- Node/Express: add the header in middleware or use a security library that supports CSP.
- Nginx: use
add_header Content-Security-Policy "..." always;. - Other frameworks: check their security or header configuration docs.
Start with a strict but simple policy. Then, as you discover blocked but legitimate resources, carefully add only the domains you truly trust.
Why CSP blocks inline scripts and eval
Many older or quick‑and‑dirty apps rely heavily on inline JavaScript:
<script>
console.log('inline script');
</script>
<button onclick="submitForm()">Submit</button><script>
console.log('inline script');
</script>
<button onclick="submitForm()">Submit</button>From a security point of view, this is risky:
- If an attacker can inject HTML into your page, they can easily add inline scripts.
- CSP's job is to stop unknown inline code from running.
By default, a strong script-src directive without 'unsafe-inline' will block:
<script>...</script>blocks inside HTML.- Event handler attributes like
onclick,onload, etc. - Some uses of
eval()and similar functions.
This is why many apps "break" when you first enable a strict CSP: they rely on inline scripts that CSP does not trust.
Nonces: a safer way to allow specific inline scripts
A nonce (number used once) is a random string your server generates for each request or page load.
You use it in two places:
- In your CSP header:
Content-Security-Policy: script-src 'self' 'nonce-ABC123';Content-Security-Policy: script-src 'self' 'nonce-ABC123';- In specific'
<script>tags you want to allow:
<script nonce="ABC123">
// this inline script is allowed by CSP
</script><script nonce="ABC123">
// this inline script is allowed by CSP
</script>The browser checks that:
- The CSP header includes
'nonce-ABC123'inscript-src. - The inline script has
nonce="ABC123".
If both are true, the script runs.
If an attacker injects their own <script> without the right nonce, the browser blocks it.
Important points:
- Generate a fresh, random nonce per request.
- Do not reuse the same nonce across users or over time.
- Never log nonces where they could leak.
A quick note on hashes
Another way to allow specific inline scripts is with hashes. You compute a cryptographic hash of the script content, then list that hash in your CSP header. The browser recomputes the hash and only runs the script if it matches.
Hashes are powerful but harder to manage when script content changes. For many teams starting a CSP tutorial or implementing CSP for beginners, nonces are easier to adopt.
Report‑only mode: learn without breaking your site
Turning on a strict CSP in production can be scary. Report‑only mode gives you a safe way to learn.
Instead of this:
Content-Security-Policy: default-src 'self';Content-Security-Policy: default-src 'self';In report‑only mode:
- The browser logs CSP violations.
- The browser does not block the resource.
You can also configure a reporting endpoint (with report-uri or report-to) to receive JSON reports about violations. This allows you to:
- Deploy a candidate CSP.
- See which resources would be blocked.
- Update the policy to allow legitimate resources.
- Switch to a real
Content-Security-Policyheader once things look safe.
Practical CSP tips and gotchas
When working with Content Security Policy (CSP), keep these points in mind:
- Start with
Content-Security-Policy-Report-Onlyin production and watch reports before enforcing. - Avoid *,
'unsafe-inline', and'unsafe-eval'except as temporary, narrow exceptions. - Map each third‑party service to its correct directive:
- Analytics scripts →
script-srcand maybeimg-src - Fonts →
font-src - Frames/embeds →
frame-srcorchild-src - Move inline scripts and styles into external files where possible.
- Keep your CSP configuration under version control and review changes like any other code.
- Test on different browsers and environments (dev, staging, production) because resource paths can differ.
A small real‑world CSP example
Consider a small SPA with this setup:
- App served from https://app.example.com
- JavaScript from:
- https://app.example.com
- https://cdn.jsdelivr.net
- CSS from https://app.example.com
- Images from:
- https://app.example.com
- https://images.example.com
A suitable CSP header might be:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.jsdelivr.net;
style-src 'self';
img-src 'self' https://images.example.com;Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.jsdelivr.net;
style-src 'self';
img-src 'self' https://images.example.com;Explanation:
default-src 'self';Only your own origin is allowed by default.script-src 'self' https://cdn.jsdelivr.net;Scripts can come from your domain and from JSDelivr. Any other script source is blocked.style-src 'self';Only your own CSS is allowed. External styles and inline styles are blocked (unless you add exceptions).img-src 'self' https://images.example.com;Images can come from your domain and from the image CDN.
If you later add analytics or other third‑party services, you use dev tools or CSP reports to see what they try to load, then extend your policy only for those specific, trusted domains.
Closing: making CSP manageable
Content Security Policy (CSP) can look overwhelming the first time you see a long header full of directives and quotes. But the core idea is simple: you define where your site is allowed to load content from, and the browser enforces that for you.
Start small:
- Add a very basic CSP to a side project.
- Use report‑only mode in production.
- Gradually tighten your policy as you understand what your app really needs.
Over time, CSP becomes just another part of your front‑end security toolbox, right next to input validation and escaping.