JavaScript Content Security Policy (CSP): A Practical Guide
Content Security Policy, or CSP, is a browser security feature that lets you control which sources of scripts, styles, images, and other resources a page is allowed to load. It is one of the most effective defenses against cross-site scripting attacks and a useful way to reduce the damage from injected content.
Quick answer: CSP works by sending the browser a policy that lists what is allowed. If a resource does not match the policy, the browser blocks it and reports a violation instead of loading it.
Difficulty: Intermediate
You'll understand this better if you know: basic HTML, how browsers load scripts and styles, and the difference between the server response and the page DOM.
1. What Is Content Security Policy?
Content Security Policy is a set of rules that tells the browser where content may come from. You can use it to allow only your own scripts, permit images from a trusted CDN, or block inline code altogether.
- It is enforced by the browser, not by JavaScript code in the page.
- It helps reduce the risk of cross-site scripting, malicious injections, and unauthorized resource loading.
- It is usually delivered as an HTTP response header, though a meta tag can also be used in some cases.
- It uses directives such as default-src, script-src, and img-src.
CSP does not replace secure coding practices. It adds a strong browser-side layer of defense after you have already tried to prevent injection bugs.
2. Why Content Security Policy Matters
Modern web apps often pull in scripts, fonts, analytics, widgets, and images from many places. Every additional source increases the chance of loading something untrusted. CSP gives you a centralized way to limit that exposure.
It matters because many attacks try to inject a script tag, inline event handler, or unexpected external resource. A strict policy can block those attacks even if an attacker finds a way to place markup into your page.
CSP is also valuable during development and auditing. Violation reports reveal which parts of an application still depend on weak patterns such as inline scripts or broad third-party permissions.
3. Basic Syntax or Core Idea
The browser reads CSP from a response header like Content-Security-Policy. The value is made of directives, each followed by allowed source expressions.
Minimal example
This policy allows resources only from the current origin by default:
Content-Security-Policy: default-src 'self'Here, default-src acts as a fallback for many resource types. The 'self' source expression means the same origin that served the page.
Common directive patterns
You can make policies more specific by setting different rules for different resource types:
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; img-src 'self' https://images.example.comThis policy allows scripts from your own site and one CDN, while images can also come from a dedicated image host.
4. Step-by-Step Examples
Example 1: Blocking all external scripts except your site
If your application should only run scripts you host, start with a narrow script-src rule.
Content-Security-Policy: default-src 'self'; script-src 'self'This blocks scripts loaded from other domains. It is a strong baseline for many applications.
Example 2: Allowing a trusted CDN for scripts
Sometimes a library is served from a CDN. CSP can allow that one source without opening the door to everything else.
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.comWith this policy, scripts from the CDN are allowed, but scripts from unknown domains are still blocked.
Example 3: Preventing inline scripts
Inline scripts are a common injection target. CSP can stop them unless you explicitly allow them.
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">With this policy, a tag like <script>alert('x')</script> would be blocked if it is inline and not covered by a nonce or hash.
Example 4: Allowing images from a separate host
Images often live on a different host from scripts. CSP lets you treat them separately.
Content-Security-Policy: default-src 'self'; img-src 'self' https://images.example.comThis keeps your JavaScript policy strict while still allowing product images or avatars from a trusted image service.
5. Practical Use Cases
- Blocking injected scripts in pages that accept user-generated content.
- Restricting a single-page application to known API endpoints, asset hosts, and analytics providers.
- Hardening login pages, admin dashboards, and checkout flows where XSS would be especially dangerous.
- Auditing third-party dependencies by limiting the domains they may load from.
- Gradually removing inline scripts and event handlers from an older application.
6. Common Mistakes
Mistake 1: Confusing CSP with JavaScript validation
CSP is not a function you call from your app. It is a browser-enforced policy sent by the server or embedded in the page metadata.
Problem: This code tries to set CSP from JavaScript, but the browser does not use a DOM property called contentSecurityPolicy for enforcement.
document.contentSecurityPolicy = "default-src 'self'";Fix: Send the policy as an HTTP response header, or use a supported meta tag in the document head.
Content-Security-Policy: default-src 'self'The corrected approach works because the browser reads CSP during page loading, not from a made-up JavaScript property.
Mistake 2: Allowing too much with 'unsafe-inline'
Beginners often add 'unsafe-inline' to make the page work quickly. That weakens the protection against script injection.
Problem: This policy allows inline scripts and inline event handlers, which makes XSS much easier to exploit.
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'Fix: Remove the broad inline allowance and use a nonce or hash for the specific inline code that truly needs it.
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-r4nd0mValue'The corrected version is safer because only scripts with the matching nonce can run inline.
Mistake 3: Forgetting that default-src is a fallback
Many developers expect default-src to govern everything all the time. In practice, a specific directive overrides it for that resource type.
Problem: This policy appears strict, but it still allows fonts or images from places you did not intend if you do not set their specific directives.
Content-Security-Policy: default-src 'self'; script-src 'self'Fix: Add explicit directives for the resource types you care about, such as img-src, font-src, and connect-src.
Content-Security-Policy: default-src 'self'; script-src 'self'; img-src 'self'; font-src 'self'; connect-src 'self'The fixed policy is clearer because each resource type gets an explicit rule.
7. Best Practices
Practice 1: Start in report-only mode
When you are introducing CSP to an existing app, first observe violations before enforcing them. This helps you find broken resources without immediately blocking them.
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'This approach is useful because it lets you collect data and fix issues before users see breakage.
Practice 2: Prefer nonces or hashes for necessary inline scripts
If a small inline script is unavoidable, allow only that exact script instead of all inline code.
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-a1b2c3'Nonces are better than broad inline exceptions because they are specific and harder to abuse.
Practice 3: Set resource-specific directives explicitly
Clear policies are easier to audit than policies that rely on default-src for everything.
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'; connect-src 'self'This reduces surprises when a new asset type is added later.
8. Limitations and Edge Cases
- CSP only works in browsers that implement it; it does not secure server-side code.
- Policies can break pages that depend on inline scripts, inline styles, or dynamic third-party widgets.
- Some browser console messages can be verbose, such as Refused to load the script because it violates the following Content Security Policy directive.
- A policy in a meta tag is applied later than a response header and cannot protect the earliest bytes of the document as strongly.
- Different browsers may expose slightly different violation wording, but the meaning is usually the same: a source did not match the policy.
- Using CSP with many third-party services can require careful maintenance whenever provider URLs or loading behavior changes.
9. Practical Mini Project
Here is a small example of a page that uses a restrictive CSP and a nonce to permit one safe inline script. This pattern is common in real applications that want strong protection without giving up all inline behavior.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'nonce-r4nd0m'; style-src 'self'">
<title>CSP Example</title>
</head>
<body>
<h1>Secure counter</h1>
<button id="countBtn">Count</button>
<p id="output">0</p>
<script nonce="r4nd0m">
const button = document.getElementById("countBtn");
const output = document.getElementById("output");
let count = 0;
button.addEventListener("click", () => {
count += 1;
output.textContent = String(count);
});
</script>
</body>
</html>This mini project shows the main idea: the browser accepts only the resources and inline code that match the policy. If you remove the nonce, the inline script is blocked.
10. Key Points
- CSP is a browser security policy that restricts where page resources can be loaded from.
- It is most effective against script injection and unsafe third-party resource loading.
- default-src provides a fallback, while directives like script-src and img-src fine-tune specific resource types.
- Inline scripts are usually the hardest part of adopting CSP, so nonces and hashes matter.
- Report-only mode is a practical way to adopt CSP without breaking the site immediately.
11. Practice Exercise
- Write a CSP for a page that should load scripts only from its own origin.
- Allow images from the same origin and from https://images.example.com.
- Block inline scripts unless they have a nonce.
Expected output: A policy string that enforces those three rules.
Hint: Use default-src as a fallback, then add explicit script-src and img-src directives.
Solution:
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-r4nd0mValue'; img-src 'self' https://images.example.com12. Final Summary
Content Security Policy gives you a powerful browser-enforced way to control which sources a page may use. In JavaScript-heavy applications, it helps limit the impact of injected content by blocking unexpected scripts, styles, images, and network destinations.
The key to using CSP well is to start strict, then open only the sources you truly need. Prefer explicit directives, use report-only mode when introducing a policy to an existing app, and avoid broad exceptions like 'unsafe-inline' unless you fully understand the tradeoff.
Once you can read violation messages and map them back to the relevant directive, CSP becomes a practical part of your security workflow rather than a mystery. A good next step is to review your app’s current script and resource loading patterns, then draft a report-only policy and tighten it gradually.