secure-headers.md 6.2 KB

HTTP Security Headers

Essential security headers for web applications.

Complete Header Set

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 (CSP)

Basic CSP

Content-Security-Policy: default-src 'self'

Detailed CSP Directives

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)

CSP Values

'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

CSP for Common Frameworks

React/Vue/Angular (Production)

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

With CDN

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net

Report-Only Mode

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

Strict-Transport-Security (HSTS)

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
  • max-age=31536000 - Browser remembers for 1 year
  • includeSubDomains - Apply to all subdomains
  • preload - Submit to browser preload lists

Implementation

# 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

X-Frame-Options: DENY
  • DENY - Never allow framing
  • SAMEORIGIN - Only same origin can frame
  • ALLOW-FROM uri - Specific origin (deprecated, use CSP)

X-Content-Type-Options

X-Content-Type-Options: nosniff

Prevents MIME type sniffing. Always use this.

Referrer-Policy

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

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

Implementation Examples

Python Flask

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

Python FastAPI

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

Node.js Express (Helmet)

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,
    }
}));

Nginx

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;

Testing Headers

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

# Security header scanner
# https://securityheaders.com

# Mozilla Observatory
# https://observatory.mozilla.org

Quick Checklist

  • CSP with restrictive default-src
  • HSTS with 1 year max-age
  • X-Frame-Options: DENY
  • X-Content-Type-Options: nosniff
  • Referrer-Policy set
  • Permissions-Policy restricting unused features
  • No X-Powered-By header (remove it)
  • Test with securityheaders.com