web php

Content Security Policy

Content Security Policy, usually called CSP, is an HTTP response header that tells the browser which sources are allowed for scripts, styles, images, frames, and other resources.

CSP is defence-in-depth. It can reduce the damage from XSS, but it does not replace escaping output, validating rich text, avoiding unsafe inline JavaScript, or reviewing template code.

A basic policy

PHP example
<?php

declare(strict_types=1);

$policy = [
    "default-src 'self'",
    "script-src 'self'",
    "object-src 'none'",
    "base-uri 'self'",
];

header('Content-Security-Policy: ' . implode('; ', $policy));

echo implode('; ', $policy) . PHP_EOL;

// Prints:
// default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'

Start in report-only mode when introducing CSP to an existing app:

PHP example
<?php

declare(strict_types=1);

$policy = "default-src 'self'; script-src 'self'; object-src 'none'";

header('Content-Security-Policy-Report-Only: ' . $policy);

echo $policy . PHP_EOL;

// Prints:
// default-src 'self'; script-src 'self'; object-src 'none'

Report-only lets you see violations without breaking users immediately.

Common directives

default-src    fallback for resource types not listed separately
script-src     where scripts can load from
style-src      where styles can load from
img-src        where images can load from
connect-src    where fetch/WebSocket/EventSource can connect
object-src     plugin/object/embed sources, often 'none'
base-uri       allowed base URL sources
frame-ancestors who can frame the page

Avoid adding broad values such as *, unsafe-inline, or unsafe-eval without understanding why. They can remove much of the protection CSP was supposed to provide.

Inline Scripts Need A Deliberate Plan

Existing applications often contain inline scripts. Allowing all inline scripts with 'unsafe-inline' weakens CSP significantly. A stronger approach is to move scripts into static files or use a per-response nonce.

PHP example
<?php

declare(strict_types=1);

$nonce = base64_encode(random_bytes(16));
$policy = "default-src 'self'; script-src 'self' 'nonce-{$nonce}'; object-src 'none'; base-uri 'self'";

echo $policy . PHP_EOL;

// Prints:
// default-src 'self'; script-src 'self' 'nonce-...'; object-src 'none'; base-uri 'self'

The same nonce must be placed on the intended <script> element in the rendered HTML. Generate a fresh nonce for each response.

Framing And External Connections

Use frame-ancestors to control which sites may embed the page in a frame. Use connect-src when browser JavaScript calls APIs, WebSocket endpoints, or event streams.

A policy should match the actual application. Start with report-only mode, inspect violations, then tighten the enforced policy without adding broad exceptions merely to silence reports.

What you should be able to do

After this lesson, you should be able to explain CSP as a browser-enforced response policy, build a simple policy header, know when to use report-only mode, understand nonces and framing controls, and explain why CSP supports but does not replace secure output handling.

Practice

Task: Build A Starter CSP

Create a small PHP example or checklist for sending a starter Content Security Policy.

Requirements

  • Include default-src 'self'.
  • Include script-src 'self'.
  • Include object-src 'none'.
  • Include base-uri 'self'.
  • Show the final header value.
  • Add a short note explaining when report-only mode is useful and why CSP does not replace escaping.

Check your work

The policy should be understandable and not rely on broad wildcards.

Show solution
PHP example
<?php

declare(strict_types=1);

$policy = [
    "default-src 'self'",
    "script-src 'self'",
    "object-src 'none'",
    "base-uri 'self'",
];

$header = 'Content-Security-Policy: ' . implode('; ', $policy);

echo $header . PHP_EOL;

// Prints:
// Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'

Report-only mode is useful when introducing CSP to an existing application because it reports violations without blocking resources immediately. CSP reduces the impact of some injection bugs, but output still needs context-aware escaping because CSP is a browser defence layer, not validation or sanitisation.