# How to Implement a Proper Content Security Policy (CSP) to Prevent XSS

> Prevent XSS attacks by implementing a Content Security Policy CSP. Learn how to whitelist trusted resources and block unauthorized scripts for enhanced web security.

- Repository: [David Dias/Front-End-Checklist](https://github.com/thedaviddias/Front-End-Checklist)
- Tags: how-to-guide
- Published: 2026-03-02

---

**A Content Security Policy (CSP) prevents XSS attacks by serving an HTTP response header that whitelists trusted resource origins and blocks unauthorized inline script execution, effectively neutralizing malicious code injections.**

To implement a proper Content Security Policy (CSP) and prevent XSS vulnerabilities in your web applications, you must configure strict resource loading rules that browsers enforce at runtime. The [thedaviddias/Front-End-Checklist](https://github.com/thedaviddias/Front-End-Checklist) repository flags CSP implementation as a medium-priority security requirement in its [`README.md`](https://github.com/thedaviddias/Front-End-Checklist/blob/main/README.md), emphasizing that developers should move beyond basic headers to adopt nonce-based policies for maximum protection.

## Understanding CSP Architecture for XSS Prevention

A **Content Security Policy** functions as a declarative allowlist that restricts which scripts, styles, and resources a browser may load or execute. Unlike reactive XSS filters, CSP operates proactively by blocking unauthorized resource requests before they reach the DOM, making it one of the most effective defense-in-depth mechanisms against injection attacks.

According to the Front-End Checklist source code, implementing CSP correctly requires four architectural steps: defining a restrictive baseline, eliminating unsafe inline code, serving the policy via HTTP headers, and iteratively testing configurations.

## Essential CSP Directives for XSS Mitigation

The following directives form the foundation of an XSS-resistant policy:

| Directive | Purpose | Recommended Value |
|-----------|---------|-------------------|
| `default-src` | Fallback for unspecified resource types | `'self'` |
| `script-src` | Controls JavaScript execution sources | `'self'` `'nonce-<random>'` |
| `style-src` | Restricts CSS loading and inline styles | `'self'` `'nonce-<random>'` |
| `object-src` | Blocks potentially dangerous plugins | `'none'` |
| `base-uri` | Restricts the `base` element | `'self'` |
| `frame-ancestors` | Prevents clickjacking via framing | `'self'` |
| `upgrade-insecure-requests` | Forces HTTPS for all resource loads | (no value needed) |

Setting `object-src 'none'` is critical because it disables Flash and other plugin content that attackers commonly exploit for XSS vectors.

## Step-by-Step Implementation Strategy

### Define a Baseline Policy

Start with the most restrictive default possible using `default-src 'self'`, which permits resources only from your own origin. Explicitly override this default only for specific resource types you absolutely need, such as external fonts or analytics scripts. This approach follows the **principle of least privilege** recommended in the Front-End Checklist security guidelines.

### Eliminate Inline Scripts and Styles

Move all JavaScript and CSS into external files to enable strict `script-src 'self'` and `style-src 'self'` directives. When inline code is unavoidable, implement **nonce-based exceptions** by generating a cryptographically random token on each server request and injecting it into both the CSP header and the corresponding HTML tags.

A nonce appears as `'nonce-abc123'` in the header and `nonce="abc123"` in the HTML attribute. Browsers execute only inline blocks matching the current page's nonce, rendering injected attacker scripts inert.

### Serve Policies via HTTP Headers

Always transmit CSP through the `Content-Security-Policy` HTTP response header rather than the `meta` tag equivalent. As noted in the Front-End Checklist analysis, HTTP headers cannot be overridden by attacker-injected meta tags, providing stronger security guarantees.

Configure your web server or application middleware to append this header on every response, including error pages that might otherwise serve as XSS vectors.

### Test and Iteratively Tighten

Deploy your initial policy in **Report-Only mode** using the `Content-Security-Policy-Report-Only` header to monitor violations without breaking functionality. Use tools like [securityheaders.io](https://securityheaders.io) and Mozilla Observatory to validate your configuration, and check browser console warnings for specific blocked resource violations before enforcing the policy.

## Code Implementation Examples

### Express.js with Nonce Generation

In [`server.js`](https://github.com/thedaviddias/Front-End-Checklist/blob/main/server.js) or your main application file, implement middleware that generates per-request nonces:

```javascript
const crypto = require('crypto');

app.use((req, res, next) => {
  // Generate cryptographically secure nonce
  const nonce = crypto.randomBytes(16).toString('base64');
  res.locals.cspNonce = nonce;
  
  // Construct strict CSP header
  const csp = [
    "default-src 'self'",
    `script-src 'self' 'nonce-${nonce}'`,
    `style-src 'self' 'nonce-${nonce}'`,
    "object-src 'none'",
    "base-uri 'self'",
    "frame-ancestors 'self'",
    "upgrade-insecure-requests"
  ].join('; ');
  
  res.setHeader('Content-Security-Policy', csp);
  next();
});

```

Then inject the nonce into your EJS or Pug templates:

```html
<style nonce="<%= cspNonce %>">
  body { font-family: system-ui; }
</style>

<script nonce="<%= cspNonce %>">
  console.log('This inline script executes because the nonce matches');
</script>

```

### Nginx Configuration

For static sites, add the header in your [`nginx.conf`](https://github.com/thedaviddias/Front-End-Checklist/blob/main/nginx.conf) or site configuration:

```nginx
server {
  listen 443 ssl;
  server_name example.com;
  
  add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' https://fonts.googleapis.com; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; upgrade-insecure-requests";
  
  location / {
    root /var/www/html;
  }
}

```

### HTML Meta Tag Fallback

Use this method only when server header configuration is impossible, as it offers weaker protection:

```html
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'">

```

## Validating Your CSP Implementation

According to the Front-End Checklist repository, which validates external security links via [`.github/workflows/links-checker.yml`](https://github.com/thedaviddias/Front-End-Checklist/blob/main/.github/workflows/links-checker.yml), you should verify your implementation using multiple tools:

- Visit [securityheaders.io](https://securityheaders.io) and scan your URL for a green CSP rating
- Use Mozilla Observatory to receive a quantitative security score
- Open browser DevTools and monitor the Console tab for `Content Security Policy` violation messages indicating blocked resources
- Review server logs for CSP reports if you configured a `report-uri` or `report-to` directive

## Summary

- **Content Security Policy** is an HTTP header that prevents XSS by controlling resource loading and script execution
- Implement `default-src 'self'` as a restrictive baseline and explicitly whitelist only necessary external origins
- Eliminate inline scripts or secure them with **cryptographic nonces** that change per request
- Always serve CSP via HTTP headers rather than `meta` tags to prevent attacker override
- Test configurations using securityheaders.io, Mozilla Observatory, and browser console validation before enforcing strict policies
- Reference the [`README.md`](https://github.com/thedaviddias/Front-End-Checklist/blob/main/README.md) in the [thedaviddias/Front-End-Checklist](https://github.com/thedaviddias/Front-End-Checklist) repository for ongoing security best practices

## Frequently Asked Questions

### What is the difference between CSP and XSS filters like X-XSS-Protection?

**CSP is a whitelist-based preventive control that stops malicious scripts from executing entirely**, while legacy XSS filters attempt to sanitize detected attacks reactively. Modern browsers have deprecated X-XSS-Protection due to bypass vulnerabilities, whereas CSP remains the industry standard for XSS mitigation as recommended by the Front-End Checklist security guidelines.

### Can I use CSP with inline event handlers like onclick?

No, strict CSP policies block inline event handlers entirely. You must move all event binding into external JavaScript files or use the `script-src 'unsafe-inline'` directive (not recommended). For inline scripts that cannot be externalized, use nonces or hashes in your `script-src` directive to authorize specific code blocks while blocking attacker injections.

### How do I handle third-party scripts like Google Analytics or Stripe?

Add the specific HTTPS origin to your `script-src` directive, such as `script-src 'self' https://www.google-analytics.com https://js.stripe.com`. Avoid using wildcards (`https:`) as they allow any HTTPS origin to execute scripts. For enhanced security, request that third-party providers support Subresource Integrity (SRI) hashes and include those hashes in your policy.

### Should I use Report-Only mode in production?

Initially, yes. Deploy `Content-Security-Policy-Report-Only` for several weeks to collect violation reports without breaking functionality. Once you confirm no legitimate resources are blocked, switch to the enforcing `Content-Security-Policy` header. Maintain a reporting endpoint to detect CSP bypass attempts or policy gaps in production environments.