Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

fastify-helmet

Package Overview
Dependencies
Maintainers
17
Versions
26
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

fastify-helmet - npm Package Compare versions

Comparing version 6.0.0 to 7.0.0

.taprc

28

index.d.ts

@@ -1,19 +0,33 @@

import { FastifyPluginCallback } from "fastify";
import helmet from "helmet";
import { FastifyPluginCallback, RawServerBase, RawServerDefault } from 'fastify';
import helmet, { contentSecurityPolicy, HelmetOptions } from 'helmet';
declare module 'fastify' {
export interface RouteShorthandOptions<
RawServer extends RawServerBase = RawServerDefault
> extends FastifyHelmetRouteOptions {}
interface FastifyReply {
cspNonce: {
script: string
style: string
}
script: string;
style: string;
},
helmet: (opts?: HelmetOptions) => typeof helmet
}
export interface RouteOptions extends FastifyHelmetRouteOptions {}
}
export type FastifyHelmetOptions = NonNullable<Parameters<typeof helmet>[0] & { enableCSPNonces?: boolean }>;
export interface FastifyHelmetRouteOptions {
helmet?: Omit<FastifyHelmetOptions, 'global'> | false;
}
export interface FastifyHelmetOptions extends NonNullable<HelmetOptions> {
enableCSPNonces?: boolean,
global?: boolean;
}
export const fastifyHelmet: FastifyPluginCallback<FastifyHelmetOptions> & {
contentSecurityPolicy: typeof helmet.contentSecurityPolicy;
contentSecurityPolicy: typeof contentSecurityPolicy;
};
export default fastifyHelmet;
'use strict'
const { randomBytes } = require('crypto')
const fp = require('fastify-plugin')
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
function helmetPlugin (fastify, options, next) {
// helmet will throw when any option is explicitly set to "true"
// using ECMAScript destructuring is a clean workaround as we do not need to alter options
const { enableCSPNonces, global, ...globalConfiguration } = options
const middleware = helmet(options)
const isGlobal = typeof global === 'boolean' ? global : true
app.addHook('onRequest', function (req, reply, next) {
middleware(req.raw, reply.raw, next)
})
// We initialize the `helmet` reply decorator
fastify.decorateReply('helmet', null)
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
// We will add the onRequest helmet middleware functions through the onRoute hook if needed
fastify.addHook('onRoute', (routeOptions) => {
if (typeof routeOptions.helmet !== 'undefined') {
if (typeof routeOptions.helmet === 'object') {
const { enableCSPNonces: enableRouteCSPNonces, ...helmetRouteConfiguration } = routeOptions.helmet
app.decorateReply('cspNonce', null)
app.addHook('onRequest', function (req, reply, next) {
// prevent object reference #118
const directives = { ...cspDirectives }
// If route helmet options are set they overwrite the global helmet configuration
const mergedHelmetConfiguration = Object.assign({}, globalConfiguration, helmetRouteConfiguration)
// create csp nonce
reply.cspNonce = {
script: crypto.randomBytes(16).toString('hex'),
style: crypto.randomBytes(16).toString('hex')
buildRouteHooks(mergedHelmetConfiguration, routeOptions)
if (enableRouteCSPNonces) {
routeOptions.onRequest.push(buildCSPNonce(fastify, mergedHelmetConfiguration))
}
} else if (routeOptions.helmet === false) {
// don't apply any helmet settings but decorate the reply with a fallback to the
// global helmet options
buildRouteHooks(globalConfiguration, routeOptions, true)
} else {
throw new Error('Unknown value for route helmet configuration')
}
} else if (isGlobal) {
// if the plugin is set globally (meaning that all the routes will be decorated)
// As the endpoint, does not have a custom helmet configuration, use the global one.
buildRouteHooks(globalConfiguration, routeOptions)
// push nonce to csp
// allow both script-src or scriptSrc syntax
const scriptKey = Array.isArray(directives['script-src']) ? 'script-src' : 'scriptSrc'
directives[scriptKey] = Array.isArray(directives[scriptKey]) ? [...directives[scriptKey]] : []
directives[scriptKey].push(`'nonce-${reply.cspNonce.script}'`)
// allow both style-src or styleSrc syntax
const styleKey = Array.isArray(directives['style-src']) ? 'style-src' : 'styleSrc'
directives[styleKey] = Array.isArray(directives[styleKey]) ? [...directives[styleKey]] : []
directives[styleKey].push(`'nonce-${reply.cspNonce.style}'`)
if (enableCSPNonces) {
routeOptions.onRequest.push(buildCSPNonce(fastify, globalConfiguration))
}
} else {
// if no options are specified and the plugin is not global, then we still want to decorate
// the reply in this case
buildRouteHooks(globalConfiguration, routeOptions, true)
}
})
const cspMiddleware = helmet.contentSecurityPolicy({ directives, reportOnly: cspReportOnly })
cspMiddleware(req.raw, reply.raw, next)
})
next()
}
function buildCSPNonce (fastify, configuration) {
const cspDirectives = configuration.contentSecurityPolicy
? configuration.contentSecurityPolicy.directives
: helmet.contentSecurityPolicy.getDefaultDirectives()
const cspReportOnly = configuration.contentSecurityPolicy
? configuration.contentSecurityPolicy.reportOnly
: undefined
return function (request, reply, next) {
if (!fastify.hasReplyDecorator('cspNonce')) {
fastify.decorateReply('cspNonce', null)
}
// prevent object reference: https://github.com/fastify/fastify-helmet/issues/118
const directives = { ...cspDirectives }
// create csp nonce
reply.cspNonce = {
script: randomBytes(16).toString('hex'),
style: randomBytes(16).toString('hex')
}
// push nonce to csp
// allow both script-src or scriptSrc syntax
const scriptKey = Array.isArray(directives['script-src']) ? 'script-src' : 'scriptSrc'
directives[scriptKey] = Array.isArray(directives[scriptKey]) ? [...directives[scriptKey]] : []
directives[scriptKey].push(`'nonce-${reply.cspNonce.script}'`)
// allow both style-src or styleSrc syntax
const styleKey = Array.isArray(directives['style-src']) ? 'style-src' : 'styleSrc'
directives[styleKey] = Array.isArray(directives[styleKey]) ? [...directives[styleKey]] : []
directives[styleKey].push(`'nonce-${reply.cspNonce.style}'`)
const cspMiddleware = helmet.contentSecurityPolicy({ directives, reportOnly: cspReportOnly })
cspMiddleware(request.raw, reply.raw, next)
}
}
next()
}, {
function buildRouteHooks (configuration, routeOptions, decorateOnly) {
if (Array.isArray(routeOptions.onRequest)) {
routeOptions.onRequest.push(addHelmetReplyDecorator)
} else if (typeof routeOptions.onRequest === 'function') {
routeOptions.onRequest = [routeOptions.onRequest, addHelmetReplyDecorator]
} else {
routeOptions.onRequest = [addHelmetReplyDecorator]
}
const middleware = helmet(configuration)
function addHelmetReplyDecorator (request, reply, next) {
// We decorate `reply.helmet` with all helmet middleware functions
// NB: we allow users to pass a custom helmet options object with a fallback
// to global helmet configuration.
reply.helmet = (opts) => opts
? helmet(opts)(request.raw, reply.raw)
: helmet(configuration)(request.raw, reply.raw)
next()
}
if (decorateOnly) {
return
}
// At this point `routeOptions.onRequest` is an array
// we just have to push our `onRequest` function
routeOptions.onRequest.push(onRequest)
function onRequest (request, reply, next) {
middleware(request.raw, reply.raw, next)
}
}
module.exports = fp(helmetPlugin, {
fastify: '3.x',

@@ -52,0 +130,0 @@ name: 'fastify-helmet'

{
"name": "fastify-helmet",
"version": "6.0.0",
"version": "7.0.0",
"description": "Important security headers for Fastify",
"main": "index.js",
"types": "index.d.ts",
"scripts": {
"lint": "standard",
"lint:fix": "standard --fix",
"test": "standard | snazzy && tap test.js && npm run typescript",
"test:ci": "standard | snazzy && tap test.js --coverage-report=lcovonly && npm run typescript",
"coverage": "npm run unit -- --coverage-report=lcovonly",
"lint": "standard | snazzy",
"lint:fix": "standard --fix | snazzy",
"test": "npm run lint && npm run unit && npm run typescript",
"test:ci": "npm run lint && npm run coverage && npm run typescript",
"unit": "tap -J \"test/*.test.js\"",
"unit:report": "npm run unit -- --coverage-report=html",
"unit:verbose": "npm run unit -- -Rspec",
"typescript": "tsd"

@@ -34,10 +39,10 @@ },

"devDependencies": {
"@types/node": "^17.0.0",
"fastify": "^3.0.0",
"@types/node": "^17.0.8",
"fastify": "^3.25.3",
"pre-commit": "^1.2.2",
"snazzy": "^9.0.0",
"standard": "^16.0.2",
"tap": "^15.0.0",
"tsd": "^0.19.0",
"typescript": "^4.0.2"
"standard": "^16.0.4",
"tap": "^15.1.6",
"tsd": "^0.19.1",
"typescript": "^4.5.4"
},

@@ -47,3 +52,6 @@ "dependencies": {

"helmet": "^5.0.1"
},
"tsd": {
"directory": "test/types"
}
}

@@ -36,11 +36,137 @@ # fastify-helmet

## How it works
`fastify-helmet` is a tiny wrapper around helmet that adds an `'onRequest'` hook
and a `reply.helmet` decorator.
It accepts the same options as helmet, and you can see more in [the helmet documentation](https://helmetjs.github.io/docs/).
### Apply Helmet to all your application routes
By passing `{ global: true }` into the options, `fastify-helmet` allows you to register Helmet for all your application
routes by default. If you want a more granular control on how to apply Helmet to your application you can choose to
disable it on a global scope by passing `{ global: false }` to the options. By default, this option is set to `true`.
#### Example - enable `fastify-helmet` globally
```js
fastify.register(helmet)
// or
fastify.register(helmet, { global: true })
```
#### Example - disable `fastify-helmet` globally
```js
// register the package with the `{ global: false }` option
fastify.register(helmet, { global: false })
fastify.get('/route-with-disabled-helmet', async (request, reply) => {
return { message: 'helmet is not enabled here' }
})
fastify.get('/route-with-enabled-helmet', {
// We enable and configure helmet for this route only
helmet: {
dnsPrefetchControl: {
allow: true
},
expectCt: {
maxAge: 1,
enforce: true,
reportUri: 'foo'
},
frameguard: {
action: 'foo'
},
referrerPolicy: false
}
}, async (request, reply) => {
return { message: 'helmet is enabled here' }
})
// helmet is disabled on this route but we have access to `reply.helmet` decorator
// that allows us to apply helmet conditionally
fastify.get('/here-we-use-helmet-reply-decorator', async (request, reply) => {
if (condition) {
// we apply the default options
await reply.helmet()
} else {
// we apply customized options
await reply.helmet({ frameguard: false })
}
return {
message: 'we use the helmet reply decorator to conditionally apply helmet middlewares'
}
})
```
### `helmet` route option
`fastify-helmet` allows you to enable, disable, and customize helmet for each one of your application hooks by using the
`helmet` shorthand route option when you register your application routes.
If you want to disable helmet for a specific endpoint you must pass `{ helmet: false }` to your route options.
If you want to enable or customize helmet for a specific endpoint you must pass a helmet configuration object to your
route options. E.g.: `{ helmet: { frameguard: false } }`.
#### Example - `fastify-helmet` configuration using the `helmet` shorthand route option
```js
// register the package with the `{ global: true }` option
fastify.register(helmet, { global: true })
fastify.get('/route-with-disabled-helmet', { helmet: false }, async (request, reply) => {
return { message: 'helmet is not enabled here' }
})
fastify.get('/route-with-enabled-helmet', async (request, reply) => {
return { message: 'helmet is enabled by default here' }
})
fastify.get('/route-with-custom-helmet-configuration', {
// We change the helmet configuration for this route only
helmet: {
enableCSPNonces: true,
contentSecurityPolicy: {
directives: {
'directive-1': ['foo', 'bar']
},
reportOnly: true
},
dnsPrefetchControl: {
allow: true
},
expectCt: {
maxAge: 1,
enforce: true,
reportUri: 'foo'
},
frameguard: {
action: 'foo'
},
hsts: {
maxAge: 1,
includeSubDomains: true,
preload: true
},
permittedCrossDomainPolicies: {
permittedPolicies: 'foo'
},
referrerPolicy: false
}
}, async (request, reply) => {
return { message: 'helmet is enabled with a custom configuration on this route' }
})
```
### 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`.
`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).
> 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).

@@ -111,10 +237,4 @@ #### Example - Generate by options

## How it works
`fastify-helmet` is just a tiny wrapper around helmet that adds an `'onRequest'` hook.
It accepts the same options of Helmet, and you can see more in [the helmet documentation](https://helmetjs.github.io/docs/).
## License
MIT

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc