Essential security headers for web applications.
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self' https://api.example.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()
X-XSS-Protection: 0
Content-Security-Policy: default-src 'self'
| Directive | Purpose | Example |
|---|---|---|
default-src |
Fallback for other directives | 'self' |
script-src |
JavaScript sources | 'self' https://cdn.example.com |
style-src |
CSS sources | 'self' 'unsafe-inline' |
img-src |
Image sources | 'self' data: https: |
font-src |
Font sources | 'self' https://fonts.gstatic.com |
connect-src |
AJAX, WebSocket, fetch | 'self' https://api.example.com |
frame-src |
iframe sources | 'none' |
frame-ancestors |
Who can embed this page | 'none' |
base-uri |
Restrict base element | 'self' |
form-action |
Form submission targets | 'self' |
upgrade-insecure-requests |
Upgrade HTTP to HTTPS | (no value) |
'self' - Same origin
'none' - Block all
'unsafe-inline' - Allow inline (avoid!)
'unsafe-eval' - Allow eval() (avoid!)
'strict-dynamic' - Trust scripts loaded by trusted scripts
'nonce-abc123' - Allow specific inline with nonce
'sha256-...' - Allow specific inline by hash
https: - Any HTTPS URL
data: - Data URLs
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self' https://api.yourapp.com
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
max-age=31536000 - Browser remembers for 1 yearincludeSubDomains - Apply to all subdomainspreload - Submit to browser preload lists# Flask
@app.after_request
def add_hsts(response):
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
return response
# Express
app.use(helmet.hsts({
maxAge: 31536000,
includeSubDomains: true,
preload: true
}))
X-Frame-Options: DENY
DENY - Never allow framingSAMEORIGIN - Only same origin can frameALLOW-FROM uri - Specific origin (deprecated, use CSP)X-Content-Type-Options: nosniff
Prevents MIME type sniffing. Always use this.
Referrer-Policy: strict-origin-when-cross-origin
| Value | Behavior |
|---|---|
no-referrer |
Never send referrer |
same-origin |
Only to same origin |
strict-origin |
Send origin only, not path |
strict-origin-when-cross-origin |
Full URL same-origin, origin cross-origin |
Permissions-Policy: accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()
Disable browser features you don't use:
Permissions-Policy:
camera=(), # Disable camera
microphone=(), # Disable microphone
geolocation=(self), # Only this origin
payment=* # Allow all
from flask import Flask
app = Flask(__name__)
@app.after_request
def add_security_headers(response):
response.headers['Content-Security-Policy'] = "default-src 'self'"
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'DENY'
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
return response
from fastapi import FastAPI
from starlette.middleware import Middleware
from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware
app = FastAPI()
@app.middleware("http")
async def add_security_headers(request, call_next):
response = await call_next(request)
response.headers["Content-Security-Policy"] = "default-src 'self'"
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
return response
const helmet = require('helmet');
app.use(helmet());
// Or with custom config
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "cdn.example.com"],
}
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
}
}));
add_header Content-Security-Policy "default-src 'self'" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Check headers with curl
curl -I https://example.com
# Security header scanner
# https://securityheaders.com
# Mozilla Observatory
# https://observatory.mozilla.org