@nextrush/helmet
Security headers middleware with CSP, HSTS, and modern browser protections.
Why Helmet Exists
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:'],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
},
frameguard: 'DENY',
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 |
frameguard | 'DENY' | 'SAMEORIGIN' | false= 'SAMEORIGIN' | X-Frame-Options 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, frameguard, noSniff, referrerPolicy } from '@nextrush/helmet';
app.use(
contentSecurityPolicy({
directives: { 'default-src': ["'self'"] },
})
);
app.use(hsts({ maxAge: 31536000, includeSubDomains: true, preload: true }));
app.use(frameguard('DENY'));
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.
Using frameguard with an object — frameguard takes a string ('DENY' or 'SAMEORIGIN'), not { action: 'deny' }.
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.
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