Secure headers
A strict Content-Security-Policy with nonces, HSTS, and the rest.
Needs inkan 0.8
@inkanjs/secure-headers needs inkan 0.8.0 or newer, below 1.0.
npm i @inkanjs/secure-headersimport { } from "@inkanjs/secure-headers";
.(())
.("/", () => `<script nonce="${.cspNonce}">start()</script>`);What helmet does for Express. Every header can be changed or left out, and a header the handler set itself is never overwritten.
What every answer gets
| header | default | option |
|---|---|---|
| content-security-policy | see below | csp |
| strict-transport-security | max-age=31536000; includeSubDomains, over HTTPS only | hsts |
| x-content-type-options | nosniff | noSniff |
| x-frame-options | DENY | frameOptions |
| referrer-policy | no-referrer | referrerPolicy |
| cross-origin-opener-policy | same-origin | crossOriginOpenerPolicy |
| cross-origin-resource-policy | same-origin | crossOriginResourcePolicy |
| x-dns-prefetch-control | off | dnsPrefetchControl |
| permissions-policy | camera, geolocation, microphone, payment, usb, interest-cohort off | permissionsPolicy |
| origin-agent-cluster | ?1 | originAgentCluster |
| x-permitted-cross-domain-policies | none | permittedCrossDomainPolicies |
A string sends another value, false leaves the header out. extra adds headers of your own
the same way.
The default policy, exported as DEFAULT_CSP:
default-src 'self'; base-uri 'self'; font-src 'self' https: data:; form-action 'self';
frame-ancestors 'none'; img-src 'self' data:; object-src 'none'; script-src 'self';
script-src-attr 'none'; style-src 'self'No 'unsafe-inline' anywhere: inline scripts and styles need a nonce.
secureHeaders({
: {
: { "img-src": ["'self'", "https://cdn.example.com"], "script-src-attr": false, "upgrade-insecure-requests": true },
// useDefaults: false start from nothing
// reportOnly: true content-security-policy-report-only
},
: { : 63072000, : true },
: "cross-origin", // a public API that pages on other sites load
});Behaviour
- The nonce.
ctx.cspNonceis 128 random bits, fresh for every request, made only when read. An answer whose handler (or template) read it gets'nonce-…'inscript-srcandstyle-src; every other answer gets the plain policy. - HSTS goes out only over HTTPS: the socket is a TLS socket, or
https(ctx)says so. Behind a proxy that ends TLS, passhttps: (ctx) => ctx.headers["x-forwarded-proto"] === "https"(only when the proxy sets that header itself) orhsts: { force: true }. - Your headers win. A header the handler or an earlier hook set stays as it is, so one route can send its own policy.
- inkan's own pages keep working.
/docs,/openapi.jsonand the inspector are answered before any hook runs, so they never get these headers. - Problems (4xx, 5xx) get the headers too, and so do streams.
Options
| option | default | |
|---|---|---|
csp | {} | false for none; directives over the defaults (false drops one, true for one without sources), useDefaults, reportOnly |
hsts | {} | false for none; maxAge, includeSubDomains (true), preload (false), force (false) |
https | the socket is a TLS socket | (ctx) => boolean: whether the request came over HTTPS |
skip | none | (ctx) => boolean: answers to leave alone |
extra | none | more headers, by name |
| each header above | a string, or false |
OpenAPI
Unchanged. It is a shared plugin: registered on the app it serves every route, inside a plugin only that plugin's routes.