fastify-helmet
Advanced tools
Comparing version 7.0.1 to 7.1.0
142
index.js
'use strict' | ||
const { randomBytes } = require('crypto') | ||
const fp = require('fastify-plugin') | ||
const helmet = require('helmet') | ||
const warning = require('process-warning')() | ||
warning.create('FastifyWarning.fastify-helmet', 'FST_MODULE_DEP_fastify-helmet'.toUpperCase(), 'fastify-helmet has been deprecated. Use @fastify/helmet@8.0.0 instead.') | ||
warning.emit('FST_MODULE_DEP_fastify-helmet'.toUpperCase()) | ||
async function helmetPlugin (fastify, options) { | ||
// 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 isGlobal = typeof global === 'boolean' ? global : true | ||
// We initialize the `helmet` reply decorator only if it does not already exists | ||
if (!fastify.hasReplyDecorator('helmet')) { | ||
fastify.decorateReply('helmet', null) | ||
} | ||
// We initialize the `cspNonce` reply decorator only if it does not already exists | ||
if (!fastify.hasReplyDecorator('cspNonce')) { | ||
fastify.decorateReply('cspNonce', null) | ||
} | ||
fastify.addHook('onRoute', (routeOptions) => { | ||
if (typeof routeOptions.helmet !== 'undefined') { | ||
if (typeof routeOptions.helmet === 'object') { | ||
routeOptions.config = Object.assign(routeOptions.config || Object.create(null), { helmet: routeOptions.helmet }) | ||
} else if (routeOptions.helmet === false) { | ||
routeOptions.config = Object.assign(routeOptions.config || Object.create(null), { helmet: { skipRoute: true } }) | ||
} else { | ||
throw new Error('Unknown value for route helmet configuration') | ||
} | ||
} | ||
}) | ||
fastify.addHook('onRequest', async (request, reply) => { | ||
const { helmet: routeOptions } = request.context.config | ||
if (typeof routeOptions !== 'undefined') { | ||
const { enableCSPNonces: enableRouteCSPNonces, skipRoute, ...helmetRouteConfiguration } = routeOptions | ||
// If route helmet options are set they overwrite the global helmet configuration | ||
const mergedHelmetConfiguration = Object.assign(Object.create(null), globalConfiguration, helmetRouteConfiguration) | ||
// We decorate the reply with a fallback to the route scoped helmet options | ||
return replyDecorators(request, reply, mergedHelmetConfiguration, enableRouteCSPNonces) | ||
} else { | ||
// We decorate the reply with a fallback to the global helmet options | ||
return replyDecorators(request, reply, globalConfiguration, enableCSPNonces) | ||
} | ||
}) | ||
fastify.addHook('onRequest', (request, reply, next) => { | ||
const { helmet: routeOptions } = request.context.config | ||
if (typeof routeOptions !== 'undefined') { | ||
const { enableCSPNonces: enableRouteCSPNonces, skipRoute, ...helmetRouteConfiguration } = routeOptions | ||
if (skipRoute === true) { | ||
// If helmet route option is set to `false` we skip the route | ||
} else { | ||
// If route helmet options are set they overwrite the global helmet configuration | ||
const mergedHelmetConfiguration = Object.assign(Object.create(null), globalConfiguration, helmetRouteConfiguration) | ||
return buildHelmetOnRoutes(request, reply, mergedHelmetConfiguration, enableRouteCSPNonces) | ||
} | ||
return next() | ||
} 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. | ||
return buildHelmetOnRoutes(request, reply, globalConfiguration, enableCSPNonces) | ||
} else { | ||
// if the plugin is not global we can skip the route | ||
} | ||
return next() | ||
}) | ||
} | ||
async function replyDecorators (request, reply, configuration, enableCSP) { | ||
if (enableCSP) { | ||
reply.cspNonce = { | ||
script: randomBytes(16).toString('hex'), | ||
style: randomBytes(16).toString('hex') | ||
} | ||
} | ||
reply.helmet = function (opts) { | ||
const helmetConfiguration = opts | ||
? Object.assign(Object.create(null), configuration, opts) | ||
: configuration | ||
return helmet(helmetConfiguration)(request.raw, reply.raw, done) | ||
} | ||
} | ||
async function buildHelmetOnRoutes (request, reply, configuration, enableCSP) { | ||
if (enableCSP === true) { | ||
const cspDirectives = configuration.contentSecurityPolicy | ||
? configuration.contentSecurityPolicy.directives | ||
: helmet.contentSecurityPolicy.getDefaultDirectives() | ||
const cspReportOnly = configuration.contentSecurityPolicy | ||
? configuration.contentSecurityPolicy.reportOnly | ||
: undefined | ||
// We get the csp nonce from the reply | ||
const { script: scriptCSPNonce, style: styleCSPNonce } = reply.cspNonce | ||
// We prevent object reference: https://github.com/fastify/fastify-helmet/issues/118 | ||
const directives = { ...cspDirectives } | ||
// We push nonce to csp | ||
// We 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-${scriptCSPNonce}'`) | ||
// 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-${styleCSPNonce}'`) | ||
const contentSecurityPolicy = { directives, reportOnly: cspReportOnly } | ||
const mergedHelmetConfiguration = Object.assign(Object.create(null), configuration, { contentSecurityPolicy }) | ||
helmet(mergedHelmetConfiguration)(request.raw, reply.raw, done) | ||
} else { | ||
helmet(configuration)(request.raw, reply.raw, done) | ||
} | ||
} | ||
// Helmet forward a typeof Error object so we just need to throw it as is. | ||
function done (error) { | ||
if (error) throw error | ||
} | ||
module.exports = fp(helmetPlugin, { | ||
fastify: '3.x', | ||
name: 'fastify-helmet' | ||
}) | ||
module.exports.contentSecurityPolicy = helmet.contentSecurityPolicy | ||
module.exports = require('fastify-helmet-deprecated') |
{ | ||
"name": "fastify-helmet", | ||
"version": "7.0.1", | ||
"description": "Important security headers for Fastify", | ||
"version": "7.1.0", | ||
"main": "index.js", | ||
"types": "index.d.ts", | ||
"scripts": { | ||
"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" | ||
}, | ||
"license": "MIT", | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/fastify/fastify-helmet.git" | ||
"url": "git://github.com/fastify/fastify-helmet.git" | ||
}, | ||
"keywords": [ | ||
"fastify", | ||
"helmet", | ||
"security", | ||
"headers", | ||
"x-frame-options", | ||
"csp", | ||
"hsts", | ||
"clickjack" | ||
], | ||
"author": "Matteo Collina <hello@matteocollina.com>", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/fastify/fastify-helmet/issues" | ||
}, | ||
"homepage": "https://github.com/fastify/fastify-helmet#readme", | ||
"devDependencies": { | ||
"@types/node": "^17.0.8", | ||
"fastify": "^3.25.3", | ||
"pre-commit": "^1.2.2", | ||
"snazzy": "^9.0.0", | ||
"standard": "^16.0.4", | ||
"tap": "^15.1.6", | ||
"tsd": "^0.19.1", | ||
"typescript": "^4.5.4" | ||
}, | ||
"homepage": "https://github.com/fastify/fastify-helmet", | ||
"dependencies": { | ||
"fastify-plugin": "^3.0.0", | ||
"helmet": "^5.0.1" | ||
}, | ||
"tsd": { | ||
"directory": "test/types" | ||
"process-warning": "^1.0.0", | ||
"fastify-helmet-deprecated": "npm:fastify-helmet@7.0.1" | ||
} | ||
} |
238
README.md
# fastify-helmet | ||
![CI](https://github.com/fastify/fastify-helmet/workflows/CI/badge.svg) | ||
[![NPM version](https://img.shields.io/npm/v/fastify-helmet)](https://www.npmjs.com/package/fastify-helmet) | ||
[![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/) | ||
Important security headers for Fastify. It is a tiny wrapper around | ||
[helmet](https://npm.im/helmet). | ||
## Install | ||
``` | ||
npm i fastify-helmet | ||
``` | ||
## Usage | ||
Simply require this plugin, and the basic security headers will be set. | ||
```js | ||
const fastify = require('fastify')() | ||
const helmet = require('fastify-helmet') | ||
fastify.register( | ||
helmet, | ||
// Example disables the `contentSecurityPolicy` middleware but keeps the rest. | ||
{ contentSecurityPolicy: false } | ||
) | ||
fastify.listen(3000, err => { | ||
if (err) throw err | ||
}) | ||
``` | ||
## 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`. | ||
> 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 | ||
}) | ||
``` | ||
## License | ||
MIT | ||
`fastify-helmet@7.1.0` has been deprecated. Please use | ||
`@fastify/helmet@8.0.0` instead. |
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
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
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
Trivial Package
Supply chain riskPackages less than 10 lines of code are easily copied into your own project and may not warrant the additional supply chain risk of an external dependency.
Found 1 instance in 1 package
No contributors or author data
MaintenancePackage does not specify a list of contributors or an author in package.json.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the 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
0
801
3
5
2
1
5
1
3
+ Addedprocess-warning@^1.0.0
+ Addedfastify-helmet@7.0.1(transitive)
+ Addedprocess-warning@1.0.0(transitive)
- Removedfastify-plugin@^3.0.0
- Removedhelmet@^5.0.1