Skip to content

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 {securityHeaders} from '@centralping/ergo';
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)

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 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.

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'

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']
]
}
}

None at request time — this middleware only returns pre-computed headers.

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 configuration
const pipeline = compose(
securityHeaders({
xFrameOptions: 'SAMEORIGIN',
permissionsPolicy: 'camera=(), microphone=()',
xXssProtection: false,
}),
);

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.

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.

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

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

See the auto-generated securityHeaders API docs.