HTTP Security Headers
Configure HTTP security headers to protect against XSS, clickjacking, MIME sniffing, and information leakage
When to Use
- Setting up a new web application or API
- Hardening an existing application's HTTP responses
- Implementing Content Security Policy (CSP)
- Preventing clickjacking, MIME sniffing, and protocol downgrade attacks
- Preparing for a security audit or penetration test
Instructions
- Use Helmet.js to set secure defaults. Helmet configures most security headers with sensible defaults in one line.
npm install helmet
import helmet from 'helmet';
app.use(helmet());
This sets: Content-Security-Policy, Cross-Origin-Embedder-Policy, Cross-Origin-Opener-Policy, Cross-Origin-Resource-Policy, X-DNS-Prefetch-Control, X-Frame-Options, Strict-Transport-Security, X-Download-Options, X-Content-Type-Options, Origin-Agent-Cluster, X-Permitted-Cross-Domain-Policies, Referrer-Policy, X-XSS-Protection (disabled — see below).
- Configure Content-Security-Policy (CSP) explicitly. CSP is the most important security header. It controls which resources the browser is allowed to load, preventing XSS and data injection.
app.use(
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'strict-dynamic'"],
styleSrc: ["'self'", "'unsafe-inline'"], // Inline styles needed for most UI frameworks
imgSrc: ["'self'", 'data:', 'https://cdn.example.com'],
fontSrc: ["'self'", 'https://fonts.gstatic.com'],
connectSrc: ["'self'", 'https://api.example.com'],
frameSrc: ["'none'"],
objectSrc: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"],
upgradeInsecureRequests: [],
},
},
})
);
- Enable Strict-Transport-Security (HSTS) to force HTTPS for all future requests.
app.use(
helmet({
strictTransportSecurity: {
maxAge: 63072000, // 2 years in seconds
includeSubDomains: true,
preload: true, // Submit to HSTS preload list
},
})
);
After enabling, submit your domain to https://hstspreload.org/ for inclusion in browser preload lists.
- Prevent clickjacking with
X-Frame-Optionsandframe-ancestorsCSP directive.
// X-Frame-Options (legacy, but still needed for older browsers)
// Set by Helmet: SAMEORIGIN by default
// CSP frame-ancestors (modern, preferred)
contentSecurityPolicy: {
directives: {
frameAncestors: ["'self'"], // Or "'none'" to block all framing
},
}
- Prevent MIME type sniffing with
X-Content-Type-Options: nosniff. Without this, browsers may interpret a file as a different MIME type than declared, enabling XSS via uploaded files.
X-Content-Type-Options: nosniff
Set by Helmet by default. Ensure all responses include correct Content-Type headers.
- Control referrer information with
Referrer-Policy. Prevent leaking URLs (which may contain tokens or sensitive data) in theRefererheader.
app.use(
helmet({
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
})
);
Options from most restrictive to least:
no-referrer— never send referrersame-origin— send referrer only for same-origin requestsstrict-origin-when-cross-origin(recommended) — full URL for same-origin, origin only for cross-origin HTTPS, nothing for downgradesorigin— send only the origin, not the full URL
- Set
Permissions-Policyto disable unnecessary browser features.
app.use((req, res, next) => {
res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=(), payment=(self)');
next();
});
This prevents embedded third-party content from accessing device APIs even if your page includes it.
- Set Cross-Origin headers for isolation.
// Prevent your resources from being loaded by other origins
res.setHeader('Cross-Origin-Resource-Policy', 'same-origin');
// Prevent other pages from getting a reference to your window
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
- Remove headers that leak server information.
app.disable('x-powered-by'); // Removes "X-Powered-By: Express"
// Also remove Server header at the reverse proxy level
- Deploy CSP in report-only mode first to identify violations before enforcing.
app.use(
helmet({
contentSecurityPolicy: {
directives: {
// ... your policy
reportUri: '/api/csp-report',
},
reportOnly: true, // Log violations without blocking
},
})
);
// Collect CSP violation reports
app.post('/api/csp-report', express.json({ type: 'application/csp-report' }), (req, res) => {
logger.warn({ event: 'csp.violation', report: req.body }, 'CSP violation');
res.sendStatus(204);
});
Details
Essential headers checklist:
| Header | Purpose | Value |
|---|---|---|
| Content-Security-Policy | XSS prevention, resource control | default-src 'self'; script-src 'self' |
| Strict-Transport-Security | Force HTTPS | max-age=63072000; includeSubDomains; preload |
| X-Content-Type-Options | Prevent MIME sniffing | nosniff |
| X-Frame-Options | Prevent clickjacking | DENY or SAMEORIGIN |
| Referrer-Policy | Control referrer leakage | strict-origin-when-cross-origin |
| Permissions-Policy | Disable unused browser APIs | camera=(), microphone=() |
| Cross-Origin-Opener-Policy | Window isolation | same-origin |
| Cross-Origin-Resource-Policy | Resource isolation | same-origin |
CSP 'strict-dynamic' explained: When 'strict-dynamic' is present in script-src, dynamically created scripts inherit trust from the script that created them. This allows legitimate runtime-generated scripts (bundler chunks, lazy-loaded modules) while still blocking injected scripts. Pair with nonce-based CSP for maximum security.
Nonce-based CSP for inline scripts:
app.use((req, res, next) => {
const nonce = crypto.randomBytes(16).toString('base64');
res.locals.cspNonce = nonce;
res.setHeader('Content-Security-Policy', `script-src 'nonce-${nonce}' 'strict-dynamic'`);
next();
});
<script nonce="<%= cspNonce %>">
/* inline script is allowed */
</script>
API-only applications: For APIs that do not serve HTML, a minimal header set suffices: X-Content-Type-Options: nosniff, Strict-Transport-Security, Cache-Control: no-store (for sensitive data), and Content-Type: application/json.
Testing headers: Use https://securityheaders.com or https://observatory.mozilla.org to scan your application and get a grade.
Common mistakes:
- Setting CSP to
'unsafe-inline' 'unsafe-eval'(defeats the purpose of CSP) - Not testing CSP before enforcing (breaks legitimate functionality)
- Missing HSTS on API subdomains (cookie theft via HTTP)
- Setting
X-Frame-Options: ALLOW-FROM(deprecated, unsupported in modern browsers — useframe-ancestors)
Source
https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.