@pixpilot/next-security-headers
v0.3.2
Published
Reusable security headers and Content-Security-Policy builder for Next.js.
Readme
@pixpilot/next-security-headers
Security response headers for Next.js, with a Content-Security-Policy you extend instead of rewriting.
pnpm add @pixpilot/next-security-headersUsage
// next.config.ts
import type { NextConfig } from 'next';
import { createSecurityHeaders, originSources } from '@pixpilot/next-security-headers';
const nextConfig: NextConfig = {
async headers() {
return [
{
source: '/:path*',
headers: createSecurityHeaders({
contentSecurityPolicy: {
directives: {
'connect-src': originSources(process.env.SUPABASE_URL, { websocket: true }),
'img-src': ['https:'],
},
reporting: { endpoint: '/api/csp-report' },
},
}),
},
];
},
};
export default nextConfig;With no options, createSecurityHeaders() sends:
| Header | Value |
| --------------------------- | -------------------------------------------------------------- |
| Content-Security-Policy | DEFAULT_CSP_DIRECTIVES |
| X-Frame-Options | DENY |
| X-Content-Type-Options | nosniff |
| Referrer-Policy | strict-origin-when-cross-origin |
| Permissions-Policy | camera=(), microphone=(), geolocation=(), browsing-topics=() |
| Strict-Transport-Security | max-age=63072000; includeSubDomains (production only) |
Every header can be changed through its option, or left out with false.
Cross-Origin-Opener-Policy is off unless you set crossOriginOpenerPolicy,
because same-origin breaks sign-in and payment popups.
Development mode
isDev defaults to NODE_ENV === 'development'. In development:
Strict-Transport-Securityis not sent, solocalhostis never pinned to HTTPS.upgrade-insecure-requestsis dropped, sincenext devserves over http.'unsafe-eval'is added toscript-src, which React needs for debugging.
Content-Security-Policy
The default policy follows the Next.js CSP guide for apps without nonces:
default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';
img-src 'self' data: blob:; font-src 'self'; connect-src 'self'; object-src 'none';
base-uri 'self'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requestsscript-src keeps 'unsafe-inline' because React Server Components stream their
payload through inline scripts. Removing it needs a per-request nonce.
directives is merged onto the defaults:
| Value | Effect |
| ------------------- | ---------------------------------------------- |
| ['https://x.com'] | Adds the sources to the directive |
| ["'none'"] | Replaces the directive's sources with 'none' |
| true | Emits a directive that takes no value |
| false | Removes the directive |
Set extendDefaults: false to build the policy from directives alone, and
reportOnly: true to send Content-Security-Policy-Report-Only while rolling a
policy out.
reporting adds report-uri and report-to to the policy and sends the
Reporting-Endpoints header that defines the group.
The builder throws on values that would break or widen the policy: spaces,
commas or semicolons in a value, and unquoted keywords such as self in a
source list.
Lower-level helpers
| Export | Use |
| ------------------------------- | --------------------------------------------------------- |
| createContentSecurityPolicy | The CSP header value on its own |
| mergeCspDirectives | Merge directive maps with the rules above |
| buildContentSecurityPolicy | Serialise a directive map as it is |
| originSources | [origin], or [origin, wss://host], from a service URL |
| cspReportingDirectives | The report-uri / report-to directives |
| reportingEndpointsHeaderValue | The Reporting-Endpoints header value |
| buildPermissionsPolicy | Serialise a Permissions-Policy map |
| buildStrictTransportSecurity | Build an HSTS value |
