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
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
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
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
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.