Security Headers Deep Dive Cheat Sheet
Details essential HTTP security response headers including CSP, HSTS, and X-Frame-Options with correct syntax and configuration examples.
Essential Security Headers
The most impactful HTTP response headers for hardening a web application.
- Content-Security-Policy- Restricts which sources scripts, styles, and other resources can load from, mitigating XSS
- Strict-Transport-Security- Forces browsers to only connect via HTTPS for a specified duration (HSTS)
- X-Content-Type-Options- Set to 'nosniff' to prevent MIME-type sniffing attacks
- X-Frame-Options- Prevents clickjacking by controlling whether the page can be framed
- Referrer-Policy- Controls how much referrer information is sent with outgoing requests
- Permissions-Policy- Restricts which browser features/APIs (camera, geolocation) the page can use
- Cross-Origin-Opener-Policy- Isolates the browsing context to mitigate cross-origin attacks like Spectre
Content-Security-Policy Example
A restrictive CSP allowing scripts/styles only from trusted sources.
Content-Security-Policy: \ default-src 'self'; \ script-src 'self' https://cdn.example.com; \ style-src 'self' 'unsafe-inline'; \ img-src 'self' data: https:; \ connect-src 'self' https://api.example.com; \ frame-ancestors 'none'; \ base-uri 'self'; \ object-src 'none'; \ report-uri /csp-violation-report
Setting Headers in Express (helmet)
Apply a secure baseline of headers in a Node.js Express app.
const express = require('express');const helmet = require('helmet');const app = express();app.use(helmet()); // sets sane defaults for most headers belowapp.use(helmet.contentSecurityPolicy({ directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", 'https://cdn.example.com'], objectSrc: ["'none'"], },}));app.use(helmet.hsts({ maxAge: 31536000, // 1 year, in seconds includeSubDomains: true, preload: true,}));
Setting Headers in Nginx
Adding security headers at the reverse proxy layer.
add_header X-Content-Type-Options "nosniff" always;add_header X-Frame-Options "DENY" always;add_header Referrer-Policy "strict-origin-when-cross-origin" always;add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;add_header Permissions-Policy "geolocation=(), camera=(), microphone=()" always;add_header Content-Security-Policy "default-src 'self'; frame-ancestors 'none'" always;
Verifying Headers
Tools and commands to check which headers a site actually sends.
- curl -I- Quick manual check of response headers from the command line
- securityheaders.com- Free online scanner that grades a site's header configuration
- Mozilla Observatory- Detailed analysis and remediation guidance for HTTP security posture
- CSP Evaluator- Google tool for identifying weaknesses in a Content-Security-Policy
Nonce-Based CSP (avoiding unsafe-inline)
Generating a per-request nonce server-side so inline scripts can be allow-listed without weakening the policy with unsafe-inline.
const crypto = require('crypto');app.use((req, res, next) => { res.locals.nonce = crypto.randomBytes(16).toString('base64'); res.setHeader( 'Content-Security-Policy', `default-src 'self'; ` + `script-src 'self' 'nonce-${res.locals.nonce}' 'strict-dynamic'; ` + `object-src 'none'; base-uri 'self'` ); next();});// in the template:// <script nonce="<%= nonce %>">/* trusted inline code */</script>
Cross-Origin Isolation Headers
Headers required to enable powerful-but-dangerous browser APIs safely, and to isolate a page from cross-origin attacks like Spectre.
- Cross-Origin-Opener-Policy: same-origin- Prevents cross-origin windows from holding a reference to your window object
- Cross-Origin-Embedder-Policy: require-corp- Blocks loading cross-origin resources that don't explicitly opt in via CORP or CORS
- Cross-Origin-Resource-Policy: same-site- Set on the resource itself to declare who is allowed to embed it
- crossOriginIsolated (JS)- Becomes true only when COOP+COEP are both correctly set, unlocking SharedArrayBuffer and high-resolution timers
Subresource Integrity (SRI)
Ensuring a third-party script hasn't been tampered with, even if the CDN is compromised.
<script src="https://cdn.example.com/lib/chart.min.js" integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxJ8mHKgvo6pk1J6r6f6p8XlgJj" crossorigin="anonymous"></script><!-- generate the hash: --><!-- openssl dgst -sha384 -binary chart.min.js | openssl base64 -A -->
CSP Level 3 Advanced Directives
Directives beyond the basic *-src allow-lists, for tightening a policy that already covers script/style sources.
- 'strict-dynamic'- Trust propagates from a nonce/hash-approved script to scripts it dynamically loads, without needing them individually allow-listed
- trusted-types- Declares which named Trusted Types policies may create DOM-XSS-sensitive values (innerHTML, script URLs)
- require-trusted-types-for 'script'- Forces all DOM XSS sink assignments to go through a Trusted Types policy, blocking string-based injection
- upgrade-insecure-requests- Rewrites http: sub-resource requests to https: automatically, useful during an HTTP-to-HTTPS migration
- frame-ancestors- Modern replacement for X-Frame-Options; supports multiple origins and wildcards, unlike the legacy header
Clear-Site-Data and Network Error Logging
Headers for forcing logout-time cleanup and collecting client-side network/CSP failure reports.
# Sent on the logout response to wipe cookies, cache, and storage for the originClear-Site-Data: "cache", "cookies", "storage"# Register a reporting endpoint and opt in to network error loggingReport-To: {"group":"default","max_age":86400,"endpoints":[{"url":"https://example.com/reports"}]}NEL: {"report_to":"default","max_age":86400,"include_subdomains":true}# CSP violations can be routed to the same reporting group (CSP Level 3)Content-Security-Policy: default-src 'self'; report-to default
Roll out a new CSP in Content-Security-Policy-Report-Only mode first, pointed at a report-uri collector, so you can see what legitimate resources would be blocked before enforcing it and breaking production.