fastify-helmet
Advanced tools
Comparing version 5.2.0 to 5.3.0
import { FastifyPluginCallback } from "fastify"; | ||
import helmet = require("helmet"); | ||
type FastifyHelmetOptions = Parameters<typeof helmet>[0]; | ||
declare module 'fastify' { | ||
interface FastifyReply { | ||
cspNonce: { | ||
script: string | ||
style: string | ||
} | ||
} | ||
} | ||
type FastifyHelmetOptions = Parameters<typeof helmet>[0] & { enableCSPNonces?: boolean }; | ||
export const fastifyHelmet: FastifyPluginCallback<NonNullable<FastifyHelmetOptions>>; | ||
export default fastifyHelmet; |
33
index.js
@@ -5,4 +5,9 @@ 'use strict' | ||
const helmet = require('helmet') | ||
const crypto = require('crypto') | ||
module.exports = fp(function (app, options, next) { | ||
const enableCSPNonces = options.enableCSPNonces | ||
// clear options as helmet will throw when any options is "true" | ||
options.enableCSPNonces = undefined | ||
const middleware = helmet(options) | ||
@@ -14,2 +19,30 @@ | ||
if (enableCSPNonces) { | ||
// outside onRequest hooks so that it can be reused in every route | ||
const cspDirectives = options.contentSecurityPolicy ? options.contentSecurityPolicy.directives : helmet.contentSecurityPolicy.getDefaultDirectives() | ||
const cspReportOnly = options.contentSecurityPolicy ? options.contentSecurityPolicy.reportOnly : undefined | ||
app.decorateReply('cspNonce', null) | ||
app.addHook('onRequest', function (req, reply, next) { | ||
// create csp nonce | ||
reply.cspNonce = { | ||
script: crypto.randomBytes(16).toString('hex'), | ||
style: crypto.randomBytes(16).toString('hex') | ||
} | ||
// push nonce to csp | ||
// allow both script-src or scriptSrc syntax | ||
const scriptKey = Array.isArray(cspDirectives['script-src']) ? 'script-src' : 'scriptSrc' | ||
cspDirectives[scriptKey] = Array.isArray(cspDirectives.scriptSrc) ? cspDirectives.scriptSrc : [] | ||
cspDirectives[scriptKey].push('nonce-' + reply.cspNonce.script) | ||
// allow both style-src or styleSrc syntax | ||
const styleKey = Array.isArray(cspDirectives['style-src']) ? 'style-src' : 'styleSrc' | ||
cspDirectives[styleKey] = Array.isArray(cspDirectives.styleSrc) ? cspDirectives.styleSrc : [] | ||
cspDirectives[styleKey].push('nonce-' + reply.cspNonce.style) | ||
const cspMiddleware = helmet.contentSecurityPolicy({ directives: cspDirectives, reportOnly: cspReportOnly }) | ||
cspMiddleware(req.raw, reply.raw, next) | ||
}) | ||
} | ||
next() | ||
@@ -16,0 +49,0 @@ }, { |
@@ -0,3 +1,4 @@ | ||
import fastify from "fastify"; | ||
import { expectType } from "tsd"; | ||
import fastifyHelmet from "."; | ||
import fastify from "fastify"; | ||
@@ -57,1 +58,19 @@ const app = fastify(); | ||
}); | ||
app.register(fastifyHelmet, { enableCSPNonces: true }); | ||
app.register(fastifyHelmet, { | ||
enableCSPNonces: true, | ||
contentSecurityPolicy: { | ||
directives: { | ||
'directive-1': ['foo', 'bar'] | ||
}, | ||
reportOnly: true | ||
}, | ||
}); | ||
app.get('/', function(request, reply) { | ||
expectType<{ | ||
script: string | ||
style: string | ||
}>(reply.cspNonce) | ||
}) |
{ | ||
"name": "fastify-helmet", | ||
"version": "5.2.0", | ||
"version": "5.3.0", | ||
"description": "Important security headers for Fastify", | ||
"main": "index.js", | ||
"scripts": { | ||
"typescript": "tsd", | ||
"lint": "standard", | ||
"lint:fix": "standard --fix", | ||
"test": "standard | snazzy && tap test.js && npm run typescript" | ||
"test": "standard | snazzy && tap test.js && npm run typescript", | ||
"test:ci": "standard | snazzy && tap test.js --coverage-report=lcovonly && npm run typescript", | ||
"typescript": "tsd" | ||
}, | ||
@@ -12,0 +13,0 @@ "repository": { |
# fastify-helmet | ||
[![npm version](https://img.shields.io/npm/v/fastify-helmet)](https://www.npmjs.com/package/fastify-helmet) | ||
![CI workflow](https://github.com/fastify/fastify-helmet/workflows/CI%20workflow/badge.svg) | ||
[![Known Vulnerabilities](https://snyk.io/test/github/fastify/fastify-helmet/badge.svg)](https://snyk.io/test/github/fastify/fastify-helmet) | ||
[![Coverage Status](https://coveralls.io/repos/github/fastify/fastify-helmet/badge.svg?branch=master)](https://coveralls.io/github/fastify/fastify-helmet?branch=master) | ||
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/) | ||
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/) ![CI | ||
workflow](https://github.com/fastify/fastify-helmet/workflows/CI%20workflow/badge.svg) | ||
Important security headers for Fastify. It is a tiny wrapper around | ||
[helmet](http://npm.im/helmet). | ||
[helmet](https://npm.im/helmet). | ||
@@ -33,2 +35,77 @@ ## Install | ||
### Content-Security-Policy Nonce | ||
`fastify-helmet` provide a simple way for `csp nonces generation`. You can enable | ||
this behavior by passing `{ enableCSPNonces: true }` into the options. Then, you can | ||
retrieve the `nonces` through `reply.cspNonce`. | ||
Note: This feature is implemented inside this module. It is not a valid option or | ||
supported by helmet. If you need to use helmet feature only for csp nonce you | ||
can follow the example [here](#example---generate-by-helmet). | ||
#### Example - Generate by options | ||
```js | ||
fastify.register( | ||
helmet, | ||
// enable csp nonces generation with default content-security-policy option | ||
{ enableCSPNonces: true } | ||
) | ||
fastify.register( | ||
helmet, | ||
// customize content security policy with nonce generation | ||
{ | ||
enableCSPNonces: true, | ||
contentSecurityPolicy: { | ||
directives: { | ||
... | ||
} | ||
} | ||
} | ||
) | ||
fastify.get('/', function(request, reply) { | ||
// retrieve script nonce | ||
reply.cspNonce.script | ||
// retrieve style nonce | ||
reply.cspNonce.style | ||
}) | ||
``` | ||
#### Example - Generate by helmet | ||
```js | ||
fastify.register( | ||
helmet, | ||
{ | ||
contentSecurityPolicy: { | ||
directives: { | ||
defaultSrc: ["'self'"], | ||
scriptSrc: [ | ||
function (req, res) { | ||
// "res" here is actually "reply.raw" in fastify | ||
res.scriptNonce = crypto.randomBytes(16).toString('hex') | ||
} | ||
], | ||
styleSrc: [ | ||
function (req, res) { | ||
// "res" here is actually "reply.raw" in fastify | ||
res.styleNonce = crypto.randomBytes(16).toString('hex') | ||
} | ||
] | ||
} | ||
} | ||
} | ||
) | ||
fastify.get('/', function(request, reply) { | ||
// you can access the generated nonce by "reply.raw" | ||
reply.raw.scriptNonce | ||
reply.raw.styleNonce | ||
}) | ||
``` | ||
## How it works | ||
@@ -35,0 +112,0 @@ |
64
test.js
@@ -137,1 +137,65 @@ 'use strict' | ||
}) | ||
test('auto generate nonce pre request', async (t) => { | ||
t.plan(7) | ||
const fastify = Fastify() | ||
fastify.register(helmet, { | ||
enableCSPNonces: true | ||
}) | ||
fastify.get('/', (request, reply) => { | ||
t.ok(reply.cspNonce) | ||
reply.send(reply.cspNonce) | ||
}) | ||
let cspCache, res | ||
try { | ||
res = await fastify.inject({ method: 'GET', url: '/' }) | ||
cspCache = res.json() | ||
t.ok(cspCache.script) | ||
t.ok(cspCache.style) | ||
res = await fastify.inject({ method: 'GET', url: '/' }) | ||
const newCsp = res.json() | ||
t.notEqual(cspCache, newCsp) | ||
t.ok(cspCache.script) | ||
t.ok(cspCache.style) | ||
} catch (err) { | ||
t.error(err) | ||
} | ||
}) | ||
test('allow merging options for enableCSPNonces', async (t) => { | ||
t.plan(4) | ||
const fastify = Fastify() | ||
fastify.register(helmet, { | ||
enableCSPNonces: true, | ||
contentSecurityPolicy: { | ||
directives: { | ||
defaultSrc: ["'self'"], | ||
scriptSrc: ["'self'"], | ||
styleSrc: ["'self'"] | ||
} | ||
} | ||
}) | ||
fastify.get('/', (request, reply) => { | ||
t.ok(reply.cspNonce) | ||
reply.send(reply.cspNonce) | ||
}) | ||
try { | ||
const res = await fastify.inject({ method: 'GET', url: '/' }) | ||
const cspCache = res.json() | ||
t.ok(cspCache.script) | ||
t.ok(cspCache.style) | ||
t.includes(res.headers, { | ||
'content-security-policy': `default-src 'self';script-src 'self' nonce-${cspCache.script};style-src 'self' nonce-${cspCache.style}` | ||
}) | ||
} catch (err) { | ||
t.error(err) | ||
} | ||
}) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
18422
339
118
1