Safety

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