@nextrush/helmetHelmet
Security headers middleware with CSP, HSTS, and modern browser protections.
Why Helmet Exists
Source & internals
When your server responds to a browser request, HTTP headers control how the browser handles the content. Security-critical headers tell the browser:
- Which scripts are allowed to execute
- Whether your page can be embedded in an iframe
- Whether to enforce HTTPS
- What information to send in the
Refererheader
Without these headers, browsers default to permissive behavior — allowing cross-site scripting, clickjacking, MIME sniffing, and protocol downgrade attacks.
Helmet sets 13 security response headers with OWASP-recommended values in a single middleware call. You get protection against the most common browser-based attack vectors without configuring each header individually.
What Helmet Protects Against
| Attack | Header | What Happens Without It |
|---|---|---|
| XSS (cross-site scripting) | Content-Security-Policy | Attackers inject scripts into your page |
| Clickjacking | X-Frame-Options | Your site is embedded in a hidden iframe to trick users |
| MIME sniffing | X-Content-Type-Options | Browsers execute disguised files as scripts |
| Protocol downgrade | Strict-Transport-Security | Attackers force HTTP to intercept traffic |
| Info leakage | Referrer-Policy | Internal URLs leak to third parties |
| Fingerprinting | Removes X-Powered-By | Attackers identify your tech stack |
| Cross-origin data leaks | Cross-Origin-Opener-Policy | Malicious popups access your page's data |
What Helmet Does NOT Do
Helmet handles browser-side response headers only. These concerns need separate middleware:
| Concern | Package |
|---|---|
| Input validation | Zod, ArkType, or similar |
| Authentication | JWTs, OAuth, or session auth |
| CSRF protection | @nextrush/csrf |
| Rate limiting | @nextrush/rate-limit |
| CORS | @nextrush/cors |
Default Behavior
Calling helmet() with no arguments sets 13 security headers covering XSS, clickjacking, MIME sniffing, and protocol downgrades.
Defaults cover common browser risks
A single app.use(helmet()) call protects against the most common browser-based attack vectors.
Customize individual headers only when your application requires it.
Full default headers reference
| Header | Default Value | Protection |
|---|---|---|
Content-Security-Policy | OWASP defaults | XSS, injection attacks |
Cross-Origin-Embedder-Policy | require-corp | Embedding control |
Cross-Origin-Opener-Policy | same-origin | Cross-origin isolation |
Cross-Origin-Resource-Policy | same-origin | Resource access control |
Strict-Transport-Security | max-age=15552000; includeSubDomains | Force HTTPS |
X-Content-Type-Options | nosniff | MIME sniffing |
X-Frame-Options | SAMEORIGIN | Clickjacking |
X-XSS-Protection | 0 | Disable legacy XSS filter (OWASP recommended) |
X-DNS-Prefetch-Control | off | DNS prefetch control |
X-Download-Options | noopen | IE download execution |
X-Permitted-Cross-Domain-Policies | none | Adobe cross-domain |
Referrer-Policy | no-referrer | Information leakage |
Origin-Agent-Cluster | ?1 | Process isolation |
Headers not set by default: Permissions-Policy, Clear-Site-Data, Reporting-Endpoints.
Installation
$ pnpm add @nextrush/helmet
Minimal Usage
import { createApp } from '@nextrush/core';
import { helmet } from '@nextrush/helmet';
const app = createApp();
app.use(helmet());Configuration Options
Customize individual headers by passing options. Disable any header by setting it to false.
app.use(
helmet({
contentSecurityPolicy: {
directives: {
'default-src': ["'self'"],
'script-src': ["'self'", 'https://cdn.example.com'],
'style-src': ["'self'", "'unsafe-inline'"],
'img-src': ["'self'", 'data:', 'https:'],
'frame-ancestors': ["'self'"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
},
referrerPolicy: 'strict-origin-when-cross-origin',
})
);HelmetOptions
| Property | Type | Description |
|---|---|---|
contentSecurityPolicy | ContentSecurityPolicyOptions | false= { useDefaults: true } | CSP header configuration |
crossOriginEmbedderPolicy | 'require-corp' | 'credentialless' | 'unsafe-none' | false= 'require-corp' | Cross-Origin-Embedder-Policy header |
crossOriginOpenerPolicy | 'same-origin' | 'same-origin-allow-popups' | 'unsafe-none' | false= 'same-origin' | Cross-Origin-Opener-Policy header |
crossOriginResourcePolicy | 'same-origin' | 'same-site' | 'cross-origin' | false= 'same-origin' | Cross-Origin-Resource-Policy header |
dnsPrefetchControl | 'on' | 'off' | false= 'off' | X-DNS-Prefetch-Control header |
hsts | StrictTransportSecurityOptions | false= { maxAge: 15552000, includeSubDomains: true } | Strict-Transport-Security header |
noSniff | boolean= true | X-Content-Type-Options nosniff header |
originAgentCluster | boolean= true | Origin-Agent-Cluster header |
permissionsPolicy? | PermissionsPolicyDirectives | false= undefined (not set) | Permissions-Policy header |
referrerPolicy | ReferrerPolicyValue | ReferrerPolicyValue[] | false= 'no-referrer' | Referrer-Policy header |
xssFilter | boolean= false | X-XSS-Protection header. false sets '0' (recommended), true sets '1; mode=block' |
ieNoOpen | boolean= true | X-Download-Options noopen header (IE) |
permittedCrossDomainPolicies | 'none' | 'master-only' | 'by-content-type' | 'all' | false= 'none' | X-Permitted-Cross-Domain-Policies header |
clearSiteData? | ClearSiteDataValue[] | false | Clear-Site-Data header (useful for logout endpoints) |
reportingEndpoints? | Record<string, string> | false | Reporting-Endpoints header |
StrictTransportSecurityOptions
| Property | Type | Description |
|---|---|---|
maxAge | number= 15552000 (180 days) | Max age in seconds |
includeSubDomains | boolean= true | Apply to all subdomains |
preload | boolean= false | Allow HSTS preload list submission |
ContentSecurityPolicyOptions
| Property | Type | Description |
|---|---|---|
directives? | ContentSecurityPolicyDirectives | CSP directives using hyphenated keys |
reportOnly | boolean= false | Use Content-Security-Policy-Report-Only header |
useDefaults | boolean= true | Merge with OWASP default directives |
Presets
Built-in configurations for common scenarios:
import { strictHelmet, apiHelmet, devHelmet, staticHelmet, logoutHelmet } from '@nextrush/helmet';
// Maximum security for production apps handling sensitive data
app.use(strictHelmet());
// Optimized for REST/GraphQL APIs (disables CSP, COEP)
app.use(apiHelmet());
// Relaxed headers for local development (disables HSTS, CSP)
app.use(devHelmet());
// Optimized for static file serving
app.use(staticHelmet());
// Clears browser cache, cookies, and storage on logout
app.post('/logout', logoutHelmet());Each preset accepts an overrides parameter (except devHelmet):
app.use(
strictHelmet({
hsts: { maxAge: 63072000, includeSubDomains: true, preload: true },
})
);Content Security Policy
CSP directives use hyphenated keys matching the HTTP header specification.
Basic CSP
app.use(
helmet({
contentSecurityPolicy: {
directives: {
'default-src': ["'self'"],
'script-src': ["'self'", 'https://cdn.example.com'],
'style-src': ["'self'", "'unsafe-inline'"],
'img-src': ["'self'", 'data:', 'https:'],
'connect-src': ["'self'", 'https://api.example.com'],
'object-src': ["'none'"],
},
},
})
);CSP Builder
Fluent API for building CSP headers:
import { createCspBuilder } from '@nextrush/helmet';
const builder = createCspBuilder()
.defaultSrc("'self'")
.scriptSrc("'self'", 'https://cdn.example.com')
.styleSrc("'self'", "'unsafe-inline'")
.imgSrc("'self'", 'data:', 'https:')
.upgradeInsecureRequests();
app.use(
helmet({
contentSecurityPolicy: builder.toOptions(),
})
);CSP with Nonces
For inline scripts without 'unsafe-inline':
import { buildCspWithNonce, buildCspHeader, generateNonce } from '@nextrush/helmet';
app.use(async (ctx) => {
const nonce = generateNonce();
ctx.state.cspNonce = nonce;
const { cspOptions } = buildCspWithNonce(
{
directives: {
'default-src': ["'self'"],
'script-src': ["'self'"],
},
generateNonce: true,
},
nonce
);
const cspHeader = buildCspHeader(cspOptions.directives ?? {});
ctx.set('Content-Security-Policy', cspHeader);
await ctx.next();
});Permissions Policy
Control browser feature access. Pass PermissionsPolicyDirectives directly — use an empty array to disable a feature, and ['self'] for self-only access.
app.use(
helmet({
permissionsPolicy: {
camera: [],
microphone: [],
geolocation: ['self'],
fullscreen: ['self'],
payment: ['self', 'https://pay.example.com'],
},
})
);Use the builder for complex policies:
import { createPermissionsPolicyBuilder } from '@nextrush/helmet';
const policy = createPermissionsPolicyBuilder()
.disable('camera', 'microphone', 'geolocation')
.allow('fullscreen', 'self')
.allow('payment', 'self', 'https://pay.example.com')
.getDirectives();
app.use(helmet({ permissionsPolicy: policy }));Use restrictivePermissionsPolicy() for a preset that disables most features:
import { restrictivePermissionsPolicy } from '@nextrush/helmet';
app.use(
helmet({
permissionsPolicy: restrictivePermissionsPolicy(),
})
);Individual Header Middleware
Apply specific headers without the full Helmet stack:
import { contentSecurityPolicy, hsts, noSniff, referrerPolicy } from '@nextrush/helmet';
app.use(
contentSecurityPolicy({
directives: { 'default-src': ["'self'"], 'frame-ancestors': ["'self'"] },
})
);
app.use(hsts({ maxAge: 31536000, includeSubDomains: true, preload: true }));
app.use(noSniff());
app.use(referrerPolicy('strict-origin-when-cross-origin'));Disabling Headers
Set any header option to false to skip it:
app.use(
helmet({
contentSecurityPolicy: false,
hsts: false,
})
);Common Mistakes
These are the most frequent configuration errors when using Helmet.
CSP directives use hyphenated keys like 'default-src', not camelCase like defaultSrc.
Using camelCase produces invalid headers silently.
Clickjacking protection moved to CSP — frameguard() / X-Frame-Options were removed in favor of the Content-Security-Policy frame-ancestors directive, which every modern browser honors and which subsumes what X-Frame-Options did. Set contentSecurityPolicy: { directives: { 'frame-ancestors': ["'self'"] } } (or ["'none'"] to block all framing) instead of the old frameguard option.
Enabling hsts.preload prematurely — HSTS preload is permanent. Only enable after submitting to the HSTS Preload List. Preload requires maxAge >= 31536000 and includeSubDomains: true.
Expecting Permissions-Policy by default — Helmet does not set Permissions-Policy unless you provide permissionsPolicy in the options.
Advanced: CSP builders and nonces
Beyond the contentSecurityPolicy() preset above, the package exports the
builder and nonce primitives directly — use these when a single static CSP
config isn't enough (e.g. a per-request nonce for inline <script> tags).
CSP builder
| Property | Type | Description |
|---|---|---|
createCspBuilder | () => CspBuilder | Fluent builder for constructing a CSP directive set programmatically instead of a static options object. |
buildCspHeader | (directives: ContentSecurityPolicyDirectives) => string | Serializes a directives object into the literal CSP header value. |
buildCspWithNonce | (directives: ContentSecurityPolicyDirectives, nonce: string) => string | Same as buildCspHeader, injecting 'nonce-<value>' into script-src/style-src. |
analyzeCsp | (header: string) => { warnings: string[] } | Parses an existing CSP header string and flags common misconfigurations. |
analyzeCspSecurity | (directives: ContentSecurityPolicyDirectives) => HstsValidationResult | Structural security analysis of a directive set (e.g. flags unsafe-inline without a nonce). |
Nonces (per-request inline script/style allowlisting)
| Property | Type | Description |
|---|---|---|
generateNonce | () => string | Generates a cryptographically random nonce value (base64). |
generateCspNonce | () => string | Alias of generateNonce, named for CSP call sites. |
createNonceProvider | () => NonceProvider | Per-request nonce factory — call once per request, then pass the same value to buildCspWithNonce and your template. |
createNoncedScript | (nonce: string, src: string) => string | Renders a `<script nonce="...">` tag string for a given nonce and source. |
createNoncedStyle | (nonce: string, css: string) => string | Renders a `<style nonce="...">` tag string. |
extractNonce | (cspHeaderValue: string) => string | undefined | Reads the nonce back out of an already-built CSP header value. |
validateNonce | (nonce: string) => boolean | Structural validation — rejects a nonce that doesn't look like a real generated value. |
isValidNonce | (nonce: string) => boolean | Alias of validateNonce. |
Permissions-Policy builder
| Property | Type | Description |
|---|---|---|
createPermissionsPolicyBuilder | () => PermissionsPolicyBuilder | Fluent builder for Permissions-Policy directives, mirroring createCspBuilder. |
buildPermissionsPolicyHeader | (directives: PermissionsPolicyDirectives) => string | Serializes a directives object into the literal Permissions-Policy header value. |
restrictivePermissionsPolicy | PermissionsPolicyDirectives | A preset directive set that denies every feature by default (camera, microphone, geolocation, etc.) — start here and allowlist what you actually use. |
Validation
| Property | Type | Description |
|---|---|---|
isValidCspDirective | (name: string) => name is CspDirectiveName | Type guard for a recognized CSP directive name. |
isBooleanCspDirective | (name: string) => boolean | Whether a directive is boolean-valued (e.g. upgrade-insecure-requests) rather than a source list. |
isCspValueSafe | (value: string) => boolean | Rejects a source-list value containing an unsafe token (`'unsafe-inline'`, `'unsafe-eval'`, or a bare `*`) — used to warn, not silently strip. |
isUnsafeCspValue | (value: string) => boolean | Negation-style check against the same unsafe-token list. |
isValidHash | (value: string) => boolean | Validates a CSP hash-source value (`'sha256-...'`). |
sanitizeCspValue | (value: string) => string | Strips characters that would break CSP header serialization or enable header injection. |
sanitizeHeaderValue | (value: string) => string | General header-injection guard applied to any user-influenced header value this package sets. |
validateHstsOptions | (options: StrictTransportSecurityOptions) => HstsValidationResult | Validates an HSTS config before use — e.g. warns if preload is set without includeSubDomains. |
securityWarning | (message: string) => void | The internal dev-time console warning helper for a risky configuration. |
Constants (DEFAULT_CSP_DIRECTIVES, STRICT_CSP_DIRECTIVES,
BOOLEAN_CSP_DIRECTIVES, VALID_CSP_DIRECTIVES, UNSAFE_CSP_VALUES,
DEFAULT_HSTS_MAX_AGE, RECOMMENDED_HSTS_MAX_AGE,
MIN_HSTS_PRELOAD_MAX_AGE, HEADERS) are the literal directive lists and
threshold values the presets themselves use — exported so a custom
configuration can start from the same baseline rather than hardcoding it.
Troubleshooting
Cross-origin resources fail to load — The default Cross-Origin-Embedder-Policy: require-corp blocks resources without CORP headers. Set crossOriginEmbedderPolicy: false or 'credentialless' if loading third-party resources.
CORS requests blocked after adding Helmet — Cross-Origin-Resource-Policy: same-origin blocks cross-origin resource sharing. Set crossOriginResourcePolicy: 'cross-origin' for public resources.
Inline scripts blocked by CSP — Default CSP does not allow 'unsafe-inline' for scripts. Use nonces or add the source to 'script-src'.
Related
- Middleware Overview — All middleware packages
- @nextrush/cors — CORS handling
- @nextrush/rate-limit — Rate limiting