Security Headers
HTTP response headers (CSP, HSTS, X-Frame-Options) that enforce browser-level protections.
Overview
HTTP security headers instruct browsers to enforce security constraints: preventing clickjacking, restricting script sources, forcing HTTPS, controlling cross-origin sharing, and blocking MIME type sniffing. They are a low-effort, high-value defence layer. The helmet.js library (Node.js) and SecureHeaders gem (Ruby) set sensible defaults. Security headers are evaluated by securityheaders.com and included in OWASP guidance.
Origin
X-Frame-Options was introduced by Microsoft Internet Explorer in 2009 to prevent clickjacking. Content Security Policy was proposed by Mozilla in 2010, standardised as CSP Level 1 in 2012. Strict-Transport-Security (HSTS) was proposed in 2009 and standardised as RFC 6797 in 2012. Permissions-Policy (formerly Feature-Policy) was introduced in 2019 to control browser feature access.
Examples
Security header configuration with helmet in Express
import helmet from 'helmet';
import { Express } from 'express';
export function applySecurityHeaders(app: Express): void {
// HSTS: force HTTPS for 1 year including subdomains
app.use(helmet.hsts({
maxAge: 31536000,
includeSubDomains: true,
preload: true,
}));
// Prevent MIME type sniffing
app.use(helmet.noSniff());
// Prevent clickjacking (CSP frameAncestors is preferred, this is for older browsers)
app.use(helmet.frameguard({ action: 'deny' }));
// Referrer policy: do not leak full URL to third parties
app.use(helmet.referrerPolicy({ policy: 'strict-origin-when-cross-origin' }));
// Permissions policy: disable camera, mic, geolocation unless explicitly needed
app.use(helmet.permittedCrossDomainPolicies());
app.use((_, res, next) => {
res.setHeader(
'Permissions-Policy',
'camera=(), microphone=(), geolocation=(), payment=()'
);
next();
});
// Remove X-Powered-By to avoid advertising Express
app.disable('x-powered-by');
}HSTS preload (submitting the domain to the browser's preload list at hstspreload.org) prevents the initial HTTP connection that HSTS cannot protect on first visit. preload: true in the header signals intent to preload; the domain must also be manually submitted.
CORS configuration with explicit origin allowlist in TypeScript
import cors from 'cors';
import { Express } from 'express';
const ALLOWED_ORIGINS = new Set([
'https://app.example.com',
'https://admin.example.com',
...(process.env.NODE_ENV === 'development' ? ['http://localhost:3000'] : []),
]);
export function configureCors(app: Express): void {
app.use(cors({
origin: (origin, callback) => {
// Allow requests with no origin (curl, server-to-server)
if (!origin) return callback(null, true);
if (ALLOWED_ORIGINS.has(origin)) return callback(null, true);
callback(new Error('CORS policy: origin not allowed: ' + origin));
},
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],
credentials: true, // Allow cookies
maxAge: 86400, // Preflight cache: 24 hours
}));
}Using a Set for O(1) lookup and building it from an explicit list prevents the common mistake of using regex or endsWith to check origins, which can be bypassed with subdomain injection (evil.example.com would match .example.com). credentials: true requires a specific origin, not wildcard *.
Use Cases
- 01All internet-facing web applications and APIs should set at minimum HSTS, X-Content-Type-Options, and X-Frame-Options or CSP frameAncestors
- 02API-only backends need CORS headers to control which browser origins can make cross-site requests
- 03Banking and payment applications should submit to HSTS preload lists to prevent HTTPS downgrade attacks on first-visit
- 04Applications loading third-party scripts (analytics, chat widgets) should scope CSP to allow only specific domains
When Not to Use
- //Do not set Access-Control-Allow-Origin: * on endpoints that accept credentials (cookies or Authorization headers); browsers block this combination
- //Do not set HSTS on a domain you are not certain will serve HTTPS indefinitely; removing HSTS requires waiting for the maxAge to expire for all browsers that have seen the header
- //Do not copy a CSP from a template without testing; an overly restrictive CSP will break legitimate functionality (analytics scripts, fonts, embedded videos)
Technical Notes
- CSP violation reports can be collected via the report-uri or report-to directive; the Report-To header (Reporting API v1) batches and sends reports asynchronously, reducing impact on page load
- Cross-Origin-Resource-Policy (CORP) header controls which origins can embed a resource, while CORS controls which origins can read the response. CORP is needed for resources loaded with no-cors requests (script, img)
- Cross-Origin-Opener-Policy (COOP) and Cross-Origin-Embedder-Policy (COEP) together enable SharedArrayBuffer by isolating the page's browsing context group; required for high-resolution timers and Atomics in modern Chrome
- HTTP Observatory (Mozilla) and securityheaders.com grade your site's security headers and provide specific remediation advice; integrating a scan into CI via the API detects header regressions on each deployment
More in Safety