Skip to content
intermediate Phase 8 · Application Security and Secure Coding

Security Headers and CSP

Ship browser-enforced policies that blunt XSS and clickjacking.

50m
0 problems
Topic Progress 0%

Security Headers: Browser-Enforced Policies

Security Headers: Telling the Browser How to Protect Your Application

Security headers are HTTP response headers that instruct the browser to enforce specific security policies. They provide a second layer of defense — even if an application has an XSS or clickjacking vulnerability, the browser may block the attack based on the header policies.

Content Security Policy (CSP)

CSP is the most powerful security header. It restricts which resources (scripts, styles, images, fonts, connections) the browser will load and execute for a given page. CSP is the most effective defense-in-depth against XSS.

Basic CSP:

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'

This policy:

  • default-src 'self': Only load resources from the same origin by default
  • script-src 'self': Only execute scripts from the same origin (blocks all inline scripts and external scripts)
  • style-src 'self': Only load stylesheets from the same origin
  • img-src 'self': Only load images from the same origin
  • connect-src 'self': Only make AJAX/fetch requests to the same origin
  • frame-ancestors 'none': Prevent the page from being embedded in frames (clickjacking protection)
  • base-uri 'self': Restrict the base URL for relative links
  • form-action 'self': Only submit forms to the same origin

CSP Directives:

Directive Controls
default-src Fallback for all resource types
script-src JavaScript sources
style-src CSS sources
img-src Image sources
font-src Font sources
connect-src AJAX, WebSocket, EventSource targets
frame-src Sources for nested frames
frame-ancestors Which pages can embed this page
base-uri Base URL for relative links
form-action Valid form submission targets

Nonce-Based CSP:

To allow specific inline scripts while blocking all others, use a nonce (number used once):

Content-Security-Policy: script-src 'nonce-abc123'
<script nonce="abc123">
  // This script is allowed because it has the correct nonce
  console.log('trusted inline script');
</script>

<script>
  // This script is blocked — no nonce
  alert('injected script');
</script>

The nonce must be cryptographically random and regenerated on every request.

CSP Reporting:

CSP can report violations to a specified endpoint without blocking (report-only mode):

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

This is useful for testing a CSP policy before enforcing it.

X-Frame-Options and frame-ancestors

Clickjacking is an attack where an attacker embeds a vulnerable page in a transparent iframe and tricks the user into clicking elements they cannot see.

X-Frame-Options:

X-Frame-Options: DENY
  • DENY: The page cannot be embedded in any frame
  • SAMEORIGIN: The page can only be embedded in frames from the same origin

frame-ancestors (CSP, modern replacement):

Content-Security-Policy: frame-ancestors 'none'
  • 'none': No page can embed this page
  • 'self': Only same-origin pages can embed
  • https://example.com: Only the specified origin can embed

frame-ancestors is more flexible than X-Frame-Options and supports multiple origins. Use both for backward compatibility.

HTTP Strict Transport Security (HSTS)

HSTS forces the browser to upgrade every future request to HTTPS for a specified duration, preventing SSL stripping attacks:

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
  • max-age=31536000: Remember the policy for one year (in seconds)
  • includeSubDomains: Apply to all subdomains
  • preload: Include in the browser's built-in HSTS list (requires submission to hstspreload.org)

Why HSTS matters: Without HSTS, an attacker on the same network can intercept the initial HTTP request and prevent the upgrade to HTTPS (SSL stripping). With HSTS, the browser automatically upgrades to HTTPS before any network request is made.

Other Important Security Headers

X-Content-Type-Options:

X-Content-Type-Options: nosniff

Prevents MIME type sniffing — the browser will not guess the content type, blocking attacks that rely on the browser interpreting a file as a different type.

Referrer-Policy:

Referrer-Policy: strict-origin-when-cross-origin

Controls how much referrer information is included with requests. strict-origin-when-cross-origin sends the full URL for same-origin requests, only the origin for cross-origin requests, and no referrer for HTTPS-to-HTTP requests.

Permissions-Policy:

Permissions-Policy: camera=(), microphone=(), geolocation=()

Restricts which browser features (camera, microphone, geolocation) the page can access.

Implementing Security Headers in Practice

Configuring Security Headers Across the Stack

Security headers can be set at multiple levels: the web server, the application framework, or a reverse proxy. The best practice is to set them at the outermost layer (reverse proxy or CDN) so they apply to all responses, including static files and error pages.

Setting Headers in Express.js

const express = require('express');
const helmet = require('helmet');
const app = express();

// Helmet sets multiple security headers by default
app.use(helmet());

// Customize CSP
app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'", (req, res) => `'nonce-${res.locals.nonce}'`],
    styleSrc: ["'self'", "'unsafe-inline'"], // unsafe-inline for styles is generally acceptable
    imgSrc: ["'self'", 'data:'],
    connectSrc: ["'self'"],
    frameAncestors: ["'none'"],
    baseUri: ["'self'"],
    formAction: ["'self'"]
  }
}));

// HSTS
app.use(helmet.hsts({
  maxAge: 31536000,
  includeSubDomains: true,
  preload: true
}));

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

Setting Headers in Nginx

server {
    listen 443 ssl;
    server_name example.com;

    # CSP
    add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" always;

    # HSTS
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

    # Clickjacking protection
    add_header X-Frame-Options "DENY" always;

    # MIME sniffing protection
    add_header X-Content-Type-Options "nosniff" always;

    # Referrer policy
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # Remove server version header
    server_tokens off;
}

Setting Headers in Flask

from flask import Flask, render_template_string
from flask_talisman import Talisman

app = Flask(__name__)

# Talisman sets security headers
Talisman(app,
    content_security_policy={
        'default-src': "'self'",
        'script-src': "'self'",
        'style-src': ["'self'", "'unsafe-inline'"],
        'img-src': ["'self'", 'data:'],
        'frame-ancestors': "'none'",
    },
    force_https=True,
    strict_transport_security=True,
    strict_transport_security_max_age=31536000,
    strict_transport_security_include_subdomains=True,
    x_frame_options='DENY',
    x_content_type_options='nosniff',
)

Testing Security Headers

Use these tools to verify your security headers:

# Check headers with curl
curl -I https://example.com

# Online scanner
# https://securityheaders.com/

# Command-line tool
npx lighthouse https://example.com --view

Practice: Write a CSP Policy

Write a CSP policy for a web application that:

  • Loads scripts only from its own CDN (cdn.example.com)
  • Allows inline styles but not inline scripts
  • Loads images from its own domain and data URIs
  • Prevents the page from being embedded in frames
  • Makes API calls only to api.example.com

Practice Problems

0 / 2 solved
Analyze a CSP Violation Report

Your CSP report-only mode receives the following violation: ```json { "csp-report": { "document-uri": "https://example.com/dashboard", "violated-directive": "script-src", "blocked-uri": "https://evil.com/inject.js", "effective-directive": "script-src", "original-policy": "default-src 'self'; script-src 'self'" } } ``` Explain what happened and whether this indicates an attack or a legitimate issue.

Solution
The violation indicates that the browser blocked a script load from `https://evil.com/inject.js` because the CSP policy only allows scripts from `'self'` (same origin). This could indicate:

1. **An XSS attack**: An attacker successfully injected a `<script src="https://evil.com/inject.js">` tag into the page, and CSP blocked it. This is CSP working as intended.
2. **A legitimate third-party script**: The application may have started using a third-party script without updating the CSP. Check with the development team.
3. **A browser extension**: Some browser extensions inject scripts that trigger CSP violations.

Investigate by checking the `document-uri` (which page), the `blocked-uri` (what was blocked), and correlating with server logs for that page. If it is an attack, the CSP prevented the XSS from executing.
Configure Security Headers in Nginx

Configure Nginx to set the following security headers for all responses: 1. CSP allowing scripts from self and cdn.example.com, styles from self with unsafe-inline, images from self and data: 2. HSTS with 1-year max-age, including subdomains 3. X-Frame-Options: DENY 4. X-Content-Type-Options: nosniff 5. Remove the Server header version information

Solution
```nginx
server {
    listen 443 ssl;
    server_name example.com;

    # CSP
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'" always;

    # HSTS
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    # Clickjacking protection
    add_header X-Frame-Options "DENY" always;

    # MIME sniffing protection
    add_header X-Content-Type-Options "nosniff" always;

    # Remove server version
    server_tokens off;

    # Additional headers
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}
```

The `always` parameter ensures the headers are added to all responses, including error pages (404, 500, etc.).

Quiz

1. What does the CSP directive `script-src 'self'` accomplish?

Question 1 options

2. What is the purpose of the `frame-ancestors 'none'` directive in CSP?

Question 2 options

3. What does HSTS (HTTP Strict Transport Security) protect against?

Question 3 options

4. What is the purpose of the `X-Content-Type-Options: nosniff` header?

Question 4 options

5. Why is `Content-Security-Policy-Report-Only` useful during CSP deployment?

Question 5 options

Flashcards

Question

What does `script-src 'self'` in CSP accomplish?

Answer

It restricts script execution to the same origin only, blocking all inline scripts and external scripts. Even if an attacker injects a script, the browser refuses to execute it.

Question

What is the purpose of `frame-ancestors 'none'`?

Answer

It prevents the page from being embedded in any iframe by any page, blocking clickjacking attacks. It is the CSP equivalent of X-Frame-Options: DENY.

Question

What does HSTS protect against?

Answer

SSL stripping attacks — it forces the browser to upgrade every request to HTTPS before any network request is made, preventing attackers from intercepting the initial HTTP request.

Question

What is CSP report-only mode?

Answer

A testing mode where the browser sends violation reports to a specified endpoint without blocking any resources. Used to identify legitimate resources that need to be added to the policy before switching to enforcing mode.

Question

What does `X-Content-Type-Options: nosniff` prevent?

Answer

MIME type sniffing — the browser will not guess the content type and will respect the server's declared content type. This blocks attacks that rely on the browser interpreting a file as a different type (e.g., executing an uploaded file as a script).

Revision Notes

Key Takeaways

  • 1. CSP restricts which resources the browser will load and execute — the most effective defense-in-depth against XSS
  • 2. X-Frame-Options and frame-ancestors block clickjacking by preventing the page from being embedded in frames
  • 3. HSTS prevents SSL stripping by forcing the browser to upgrade to HTTPS before any network request
  • 4. Use CSP report-only mode to test policies before enforcing them
  • 5. Set security headers at the outermost layer (reverse proxy/CDN) so they apply to all responses

Interview Tips

  • • Explain how CSP works with specific examples of directives and what each one controls
  • • Discuss the difference between X-Frame-Options and frame-ancestors, and why you would use both
  • • Describe how to deploy CSP safely using report-only mode to avoid breaking legitimate functionality
  • • Connect security headers to the OWASP Top 10 — they are defense-in-depth for XSS (A03) and clickjacking

Cheat Sheet

Security Headers and CSP Cheat Sheet

Phase: Application Security and Secure Coding | Level: intermediate

Essential Headers:

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'; base-uri 'self'; form-action 'self'
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()

CSP Directives:

  • script-src — JavaScript sources (blocks inline scripts and event handlers)
  • style-src — CSS sources
  • img-src — Image sources
  • connect-src — AJAX/WebSocket targets
  • frame-ancestors — Which pages can embed this page (clickjacking)
  • base-uri — Base URL for relative links
  • form-action — Valid form submission targets

Key points:

  • Content Security Policy restricts which script sources the browser will execute
  • X-Frame-Options and frame-ancestors block cross-site framing used in clickjacking
  • HSTS forces browsers to upgrade every future request to HTTPS for a set duration
  • Use CSP report-only mode to test before enforcing

Tags: web-security, headers

Continue Learning

Before You Start

  • No prerequisites listed.