Secure Your Web Apps with Content Security Policy (CSP) in 2026: A Practical Developer Guide

Content Security Policy (CSP) is one of the most powerful — yet underused — browser security mechanisms available to web developers today. By defining exactly which resources your page is allowed to load, CSP effectively shuts down entire classes of attacks including Cross-Site Scripting (XSS), clickjacking, and data injection. In this hands-on guide, you’ll learn how to implement CSP headers from scratch, build a policy that works in production, and debug violations like a pro.

Why CSP Matters More Than Ever in 2026

XSS remains in the OWASP Top 10 year after year. Modern frameworks like React and Vue help by escaping output, but they don’t eliminate the risk entirely — especially when you use dangerouslySetInnerHTML, inline event handlers, or load third-party scripts. CSP acts as your last line of defense: even if an attacker injects a script tag, the browser refuses to execute it if it violates your policy.

With browser support now universal (including full Level 3 support in Chrome, Firefox, Safari, and Edge), there’s no reason to skip CSP in 2026.

How CSP Works: The Basics

CSP is delivered as an HTTP response header. The browser reads it and enforces rules about where scripts, styles, images, fonts, and other resources can be loaded from.

Here’s the simplest possible CSP header:

Content-Security-Policy: default-src 'self'

This tells the browser: only load resources from the same origin as the page. Any inline scripts, external CDN links, or data URIs will be blocked.

Key Directives You Need to Know

  • default-src — Fallback for all resource types
  • script-src — Controls JavaScript sources
  • style-src — Controls CSS sources
  • img-src — Controls image sources
  • connect-src — Controls fetch, XHR, WebSocket endpoints
  • font-src — Controls web font sources
  • frame-src — Controls iframe sources
  • object-src — Controls plugins (Flash, Java — set to 'none')
  • base-uri — Restricts the <base> tag
  • form-action — Restricts where forms can submit to

Building a Real-World CSP Policy

Let’s build a CSP for a typical web app that uses Google Fonts, a CDN for scripts, and makes API calls to its own backend.

Step 1: Start with Report-Only Mode

Never deploy CSP in enforcement mode on day one. Start with Content-Security-Policy-Report-Only to see what would break:

Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report

Step 2: Define Your Policy

Here’s a production-ready policy for a modern web app:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.jsdelivr.net 'nonce-{RANDOM}';
  style-src 'self' https://fonts.googleapis.com 'nonce-{RANDOM}';
  font-src 'self' https://fonts.gstatic.com;
  img-src 'self' data: https:;
  connect-src 'self' https://api.example.com;
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';
  upgrade-insecure-requests;

Step 3: Implement Nonces for Inline Scripts

Instead of allowing 'unsafe-inline' (which defeats the purpose of CSP), use nonces. Generate a unique random value per request:

Node.js / Express example:

import crypto from 'crypto';
import express from 'express';

const app = express();

app.use((req, res, next) => {
  // Generate a unique nonce for each request
  const nonce = crypto.randomBytes(16).toString('base64');
  res.locals.cspNonce = nonce;

  res.setHeader('Content-Security-Policy', [
    "default-src 'self'",
    `script-src 'self' 'nonce-${nonce}'`,
    `style-src 'self' 'nonce-${nonce}' https://fonts.googleapis.com`,
    "font-src 'self' https://fonts.gstatic.com",
    "img-src 'self' data: https:",
    "connect-src 'self'",
    "object-src 'none'",
    "base-uri 'self'",
    "frame-ancestors 'none'"
  ].join('; '));

  next();
});

app.get('/', (req, res) => {
  res.send(`
    <html>
    <head>
      <script nonce="${res.locals.cspNonce}">
        console.log('This inline script is allowed!');
      </script>
    </head>
    <body><h1>CSP Protected Page</h1></body>
    </html>
  `);
});

app.listen(3000);

Python / Django example:

# middleware.py
import secrets

class CSPMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        nonce = secrets.token_urlsafe(16)
        request.csp_nonce = nonce
        response = self.get_response(request)
        
        csp = (
            f"default-src 'self'; "
            f"script-src 'self' 'nonce-{nonce}'; "
            f"style-src 'self' 'nonce-{nonce}'; "
            f"object-src 'none'; "
            f"base-uri 'self'; "
            f"frame-ancestors 'none'"
        )
        response['Content-Security-Policy'] = csp
        return response

Then in your Django template:

<script nonce="{{ request.csp_nonce }}">
  // Your inline JavaScript here
</script>

Setting Up CSP Violation Reporting

CSP can send JSON reports when violations occur. This is invaluable for catching issues before they affect users:

// Express endpoint to collect CSP reports
app.post('/csp-report', express.json({ type: 'application/csp-report' }), (req, res) => {
  const report = req.body['csp-report'];
  console.warn('CSP Violation:', {
    blockedURI: report['blocked-uri'],
    violatedDirective: report['violated-directive'],
    documentURI: report['document-uri'],
    sourceFile: report['source-file'],
    lineNumber: report['line-number']
  });
  res.status(204).end();
});

Add the reporting directive to your header:

Content-Security-Policy: ...; report-uri /csp-report; report-to csp-endpoint

Common CSP Pitfalls and How to Fix Them

1. Google Tag Manager / Analytics

GTM dynamically injects scripts, which clashes with strict CSP. Use strict-dynamic with a nonce:

script-src 'nonce-{RANDOM}' 'strict-dynamic'

The 'strict-dynamic' keyword lets nonced scripts load additional scripts without listing every domain.

2. Inline Styles from JavaScript Libraries

Many UI libraries inject inline styles. Options:

  • Use nonces for style tags (preferred)
  • Use 'unsafe-hashes' with specific style hashes
  • As a last resort, 'unsafe-inline' for style-src only (lower risk than script-src)

3. WebSocket Connections

Don’t forget to allow WebSocket URLs in connect-src:

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

Testing Your CSP

Use these tools to validate your policy:

  • Browser DevTools: The Console tab shows all CSP violations in real time
  • Google CSP Evaluator: Paste your header for an instant security audit
  • Observatory by Mozilla: Full security header scan at observatory.mozilla.org
  • curl check: Verify headers are actually being sent:
curl -I https://yoursite.com | grep -i content-security

Quick CSP Checklist for Production

  1. Start in Report-Only mode for at least one week
  2. Monitor violation reports and whitelist legitimate sources
  3. Never use 'unsafe-inline' for script-src
  4. Use nonces or hashes for inline scripts
  5. Set object-src 'none' and base-uri 'self' always
  6. Add frame-ancestors 'none' to prevent clickjacking
  7. Include upgrade-insecure-requests to auto-upgrade HTTP to HTTPS
  8. Switch to enforcement mode once violations are resolved

Wrapping Up

Content Security Policy is not optional in 2026 — it’s a fundamental part of shipping secure web applications. Start with report-only mode, iterate on your policy using real violation data, and graduate to enforcement once you’re confident. Combined with other headers like Strict-Transport-Security, X-Content-Type-Options, and Permissions-Policy, CSP forms the backbone of a defense-in-depth strategy that protects both your users and your reputation.

Got questions about implementing CSP in your stack? Drop a comment below — I’d love to help you lock things down.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

Privacy Policy · Contact · Sitemap

© 7Tech – Programming and Tech Tutorials