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";
OptionTypeDefaultDescription
contentSecurityPolicystring | false"default-src 'none'"Content-Security-Policy
strictTransportSecuritystring | object | falsefalseHSTS — off by default (see note)
xContentTypeOptionsstring | boolean'nosniff'X-Content-Type-Options (true'nosniff', false disables)
xFrameOptionsstring | false'DENY'X-Frame-Options
referrerPolicystring | false'no-referrer'Referrer-Policy
xXssProtectionstring | false'0'X-XSS-Protection (0 disables legacy browser filter)
permissionsPolicystringPermissions-Policy (omitted by default)

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.

When using a string: 'max-age=31536000; includeSubDomains; preload'

When using an object: { maxAge: 31536000, includeSubDomains: true, preload: true }

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.

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.