security-headers
Injects pre-computed security response headers. Each header is
individually configurable or disableable (false). All values are built
at factory time for zero per-request overhead.
Pipeline stage: Cross-cutting
Import
Section titled “Import”import {securityHeaders} from '@centralping/ergo';Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
contentSecurityPolicy |
string | false |
"default-src 'none'" |
Content-Security-Policy (non-empty, no CTL) |
strictTransportSecurity |
string | object | false |
false |
HSTS — off by default (see note) |
xContentTypeOptions |
'nosniff' | true | false |
'nosniff' |
X-Content-Type-Options (true → 'nosniff') |
xFrameOptions |
'DENY' | 'SAMEORIGIN' | false |
'DENY' |
X-Frame-Options (ALLOW-FROM rejected) |
referrerPolicy |
W3C token | false |
'no-referrer' |
Referrer-Policy (exact token; empty string rejected) |
xXssProtection |
'0' | '1' | '1; mode=block' | false |
'0' |
X-XSS-Protection |
permissionsPolicy |
string | false |
— | Permissions-Policy (omitted by default) |
Default Headers
Section titled “Default Headers”When called with no options, the pipeline middleware emits these headers on every response:
| Header Name | Value | Option Key |
|---|---|---|
| Content-Security-Policy | default-src 'none' |
contentSecurityPolicy |
| X-Content-Type-Options | nosniff |
xContentTypeOptions |
| X-Frame-Options | DENY |
xFrameOptions |
| Referrer-Policy | no-referrer |
referrerPolicy |
| X-XSS-Protection | 0 |
xXssProtection |
Strict-Transport-Security and Permissions-Policy are not emitted
by default. HSTS requires HTTPS verification (see note below);
Permissions-Policy has no universal default.
X-XSS-Protection: 0 explicitly disables the legacy browser XSS filter.
Modern CSP makes the filter unnecessary, and leaving it enabled can
introduce vulnerabilities in older browsers.
HSTS Note
Section titled “HSTS Note”HSTS defaults to false because this middleware returns pre-computed
header tuples with no access to the request object, so it cannot verify
HTTPS. Per RFC 6797 §7.2, HSTS MUST only be sent over secure transport.
ergo-router’s transport-level middleware performs the HTTPS check and
enables HSTS appropriately.
Structured HSTS
Section titled “Structured HSTS”Import the named constant when constructing object-form HSTS:
import {securityHeaders} from '@centralping/ergo';import {DEFAULT_HSTS_MAX_AGE_SECONDS} from '@centralping/ergo/lib/security-headers';
securityHeaders({ strictTransportSecurity: { maxAge: DEFAULT_HSTS_MAX_AGE_SECONDS, // 31_536_000 (one year) includeSubDomains: true, preload: true }});String form: 'max-age=31536000; includeSubDomains; preload'
Return Value
Section titled “Return Value”Always returns response headers:
{ response: { headers: [ ['Content-Security-Policy', "default-src 'none'"], ['X-Content-Type-Options', 'nosniff'], ['X-Frame-Options', 'DENY'], ['Referrer-Policy', 'no-referrer'], ['X-XSS-Protection', '0'] ] }}Error Responses
Section titled “Error Responses”None at request time — this middleware only returns pre-computed headers.
Construction-time errors
Section titled “Construction-time errors”securityHeaders() / buildSecurityHeaderTuples() throw TypeError when an
option value is invalid. Invalid configs fail at startup rather than silently
emitting headers browsers ignore:
| Condition | Error |
|---|---|
Enabled contentSecurityPolicy / permissionsPolicy / string-form HSTS is empty, non-string, or contains CTLs (false / omitted are valid) |
TypeError: buildSecurityHeaderTuples(): "<option>" option must be a non-empty string without control characters |
xContentTypeOptions not true, 'nosniff', or false |
TypeError: buildSecurityHeaderTuples(): "xContentTypeOptions" option must be true, "nosniff", or false |
xFrameOptions not 'DENY', 'SAMEORIGIN', or false |
TypeError: buildSecurityHeaderTuples(): "xFrameOptions" option must be "DENY", "SAMEORIGIN", or false |
referrerPolicy not a W3C token or false |
TypeError: buildSecurityHeaderTuples(): "referrerPolicy" option must be a W3C referrer policy token or false |
xXssProtection not '0', '1', '1; mode=block', or false |
TypeError: buildSecurityHeaderTuples(): "xXssProtection" option must be "0", "1", "1; mode=block", or false |
strictTransportSecurity not a string, object, or false (arrays/null rejected) |
TypeError: buildSecurityHeaderTuples(): "strictTransportSecurity" option must be a string, plain object, or false |
HSTS object maxAge missing, non-integer, or negative |
TypeError: buildSecurityHeaderTuples(): "strictTransportSecurity.maxAge" option must be a non-negative integer |
HSTS includeSubDomains / preload provided but not boolean |
TypeError: buildSecurityHeaderTuples(): "strictTransportSecurity.<field>" option must be a boolean |
import {compose, securityHeaders} from '@centralping/ergo';
const pipeline = compose( securityHeaders(),);
// Custom configurationconst pipeline = compose( securityHeaders({ xFrameOptions: 'SAMEORIGIN', permissionsPolicy: 'camera=(), microphone=()', xXssProtection: false, }),);const router = createRouter({ defaults: { securityHeaders: { contentSecurityPolicy: "default-src 'self'", xFrameOptions: 'SAMEORIGIN', }, },});CSP Implications
Section titled “CSP Implications”The default contentSecurityPolicy is "default-src 'none'" — the most
restrictive Content Security Policy possible. This is appropriate for
JSON API endpoints that never serve HTML content, but it will break
any response that contains inline styles, scripts, images, or other
embedded resources.
When default-src 'none' Matters
Section titled “When default-src 'none' Matters”If your endpoint returns JSON (the common case for ergo APIs), the restrictive CSP is invisible — browsers do not evaluate CSP for JSON responses. The default exists as defense-in-depth: if an attacker somehow causes your API to return HTML, the CSP prevents the browser from executing any embedded content.
Relaxing CSP for HTML-Serving Routes
Section titled “Relaxing CSP for HTML-Serving Routes”If you have routes that serve HTML content (e.g., server-rendered pages,
health check dashboards, or documentation endpoints), override
contentSecurityPolicy on those routes:
import {compose, securityHeaders} from '@centralping/ergo';
const pipeline = compose( securityHeaders({ contentSecurityPolicy: "default-src 'self'; style-src 'self' 'unsafe-inline'", }),);// Per-route overriderouter.get('/dashboard', { securityHeaders: { contentSecurityPolicy: "default-src 'self'; style-src 'self' 'unsafe-inline'", }, execute: (req, res, acc) => ({ response: {body: '<html>...</html>'}, }),});Transport-Level CSP
Section titled “Transport-Level CSP”ergo-router’s transport layer applies security headers to every
response — including 404, 405, and 429 short-circuits that bypass the
route pipeline. By default, transport-level CSP is off (undefined),
so only matched routes receive a CSP header via the route-level
middleware.
To set a CSP policy at the transport level:
const router = createRouter({ transport: { security: { csp: "default-src 'self'", }, },});To disable CSP entirely for a route:
securityHeaders({contentSecurityPolicy: false})RFC References
Section titled “RFC References”API Reference
Section titled “API Reference”See the auto-generated securityHeaders API docs.