Comparing version 4.2.0 to 4.3.0
# Changelog | ||
## 4.3.0 - 2020-12-27 | ||
### Added | ||
- `helmet.contentSecurityPolicy`: setting the `default-src` to `helmet.contentSecurityPolicy.dangerouslyDisableDefaultSrc` disables it | ||
### Changed | ||
- `helmet.frameguard`: slightly improved error messages for non-strings | ||
## 4.2.0 - 2020-11-01 | ||
@@ -4,0 +14,0 @@ |
@@ -21,3 +21,2 @@ "use strict"; | ||
} | ||
// This is overly verbose. It'd be nice to condense this while still being type-safe. | ||
if (Object.values(options).some((option) => option === true)) { | ||
@@ -27,24 +26,12 @@ throw new Error("Helmet no longer supports `true` as a middleware option. Remove the property from your options to fix this error."); | ||
const middlewareFunctions = []; | ||
if (options.contentSecurityPolicy === undefined) { | ||
middlewareFunctions.push(content_security_policy_1.default()); | ||
} | ||
else if (options.contentSecurityPolicy !== false) { | ||
if (options.contentSecurityPolicy !== false) { | ||
middlewareFunctions.push(content_security_policy_1.default(options.contentSecurityPolicy)); | ||
} | ||
if (options.dnsPrefetchControl === undefined) { | ||
middlewareFunctions.push(x_dns_prefetch_control_1.default()); | ||
} | ||
else if (options.dnsPrefetchControl !== false) { | ||
if (options.dnsPrefetchControl !== false) { | ||
middlewareFunctions.push(x_dns_prefetch_control_1.default(options.dnsPrefetchControl)); | ||
} | ||
if (options.expectCt === undefined) { | ||
middlewareFunctions.push(expect_ct_1.default()); | ||
} | ||
else if (options.expectCt !== false) { | ||
if (options.expectCt !== false) { | ||
middlewareFunctions.push(expect_ct_1.default(options.expectCt)); | ||
} | ||
if (options.frameguard === undefined) { | ||
middlewareFunctions.push(x_frame_options_1.default()); | ||
} | ||
else if (options.frameguard !== false) { | ||
if (options.frameguard !== false) { | ||
middlewareFunctions.push(x_frame_options_1.default(options.frameguard)); | ||
@@ -58,6 +45,3 @@ } | ||
} | ||
if (options.hsts === undefined) { | ||
middlewareFunctions.push(strict_transport_security_1.default()); | ||
} | ||
else if (options.hsts !== false) { | ||
if (options.hsts !== false) { | ||
middlewareFunctions.push(strict_transport_security_1.default(options.hsts)); | ||
@@ -77,12 +61,6 @@ } | ||
} | ||
if (options.permittedCrossDomainPolicies === undefined) { | ||
middlewareFunctions.push(x_permitted_cross_domain_policies_1.default()); | ||
} | ||
else if (options.permittedCrossDomainPolicies !== false) { | ||
if (options.permittedCrossDomainPolicies !== false) { | ||
middlewareFunctions.push(x_permitted_cross_domain_policies_1.default(options.permittedCrossDomainPolicies)); | ||
} | ||
if (options.referrerPolicy === undefined) { | ||
middlewareFunctions.push(referrer_policy_1.default()); | ||
} | ||
else if (options.referrerPolicy !== false) { | ||
if (options.referrerPolicy !== false) { | ||
middlewareFunctions.push(referrer_policy_1.default(options.referrerPolicy)); | ||
@@ -89,0 +67,0 @@ } |
@@ -8,3 +8,3 @@ /// <reference types="node" /> | ||
interface ContentSecurityPolicyDirectives { | ||
[directiveName: string]: Iterable<ContentSecurityPolicyDirectiveValue>; | ||
[directiveName: string]: Iterable<ContentSecurityPolicyDirectiveValue> | typeof dangerouslyDisableDefaultSrc; | ||
} | ||
@@ -15,4 +15,5 @@ export interface ContentSecurityPolicyOptions { | ||
} | ||
declare const dangerouslyDisableDefaultSrc: unique symbol; | ||
declare const getDefaultDirectives: () => { | ||
[x: string]: Iterable<ContentSecurityPolicyDirectiveValue>; | ||
[x: string]: Iterable<ContentSecurityPolicyDirectiveValue> | typeof dangerouslyDisableDefaultSrc; | ||
}; | ||
@@ -22,6 +23,7 @@ declare function contentSecurityPolicy(options?: Readonly<ContentSecurityPolicyOptions>): (req: IncomingMessage, res: ServerResponse, next: (err?: Error) => void) => void; | ||
var getDefaultDirectives: () => { | ||
[x: string]: Iterable<ContentSecurityPolicyDirectiveValue>; | ||
[x: string]: Iterable<ContentSecurityPolicyDirectiveValue> | unique symbol; | ||
}; | ||
var dangerouslyDisableDefaultSrc: unique symbol; | ||
} | ||
export default contentSecurityPolicy; | ||
export { getDefaultDirectives }; | ||
export { getDefaultDirectives, dangerouslyDisableDefaultSrc }; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getDefaultDirectives = void 0; | ||
exports.dangerouslyDisableDefaultSrc = exports.getDefaultDirectives = void 0; | ||
const dangerouslyDisableDefaultSrc = Symbol("dangerouslyDisableDefaultSrc"); | ||
exports.dangerouslyDisableDefaultSrc = dangerouslyDisableDefaultSrc; | ||
const DEFAULT_DIRECTIVES = { | ||
@@ -19,17 +21,9 @@ "default-src": ["'self'"], | ||
exports.getDefaultDirectives = getDefaultDirectives; | ||
const isRawPolicyDirectiveNameInvalid = (rawDirectiveName) => rawDirectiveName.length === 0 || /[^a-zA-Z0-9-]/.test(rawDirectiveName); | ||
const dashify = (str) => str.replace(/[A-Z]/g, (capitalLetter) => "-" + capitalLetter.toLowerCase()); | ||
const isDirectiveValueInvalid = (directiveValue) => /;|,/.test(directiveValue); | ||
const has = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key); | ||
function getHeaderName({ reportOnly, }) { | ||
if (reportOnly) { | ||
return "Content-Security-Policy-Report-Only"; | ||
} | ||
else { | ||
return "Content-Security-Policy"; | ||
} | ||
} | ||
function normalizeDirectives(options) { | ||
const result = {}; | ||
const { directives: rawDirectives = getDefaultDirectives() } = options; | ||
const result = []; | ||
const directiveNamesSeen = new Set(); | ||
for (const rawDirectiveName in rawDirectives) { | ||
@@ -39,9 +33,11 @@ if (!has(rawDirectives, rawDirectiveName)) { | ||
} | ||
if (isRawPolicyDirectiveNameInvalid(rawDirectiveName)) { | ||
if (rawDirectiveName.length === 0 || | ||
/[^a-zA-Z0-9-]/.test(rawDirectiveName)) { | ||
throw new Error(`Content-Security-Policy received an invalid directive name ${JSON.stringify(rawDirectiveName)}`); | ||
} | ||
const directiveName = dashify(rawDirectiveName); | ||
if (has(result, directiveName)) { | ||
if (directiveNamesSeen.has(directiveName)) { | ||
throw new Error(`Content-Security-Policy received a duplicate directive ${JSON.stringify(directiveName)}`); | ||
} | ||
directiveNamesSeen.add(directiveName); | ||
const rawDirectiveValue = rawDirectives[rawDirectiveName]; | ||
@@ -52,2 +48,13 @@ let directiveValue; | ||
} | ||
else if (!rawDirectiveValue) { | ||
throw new Error(`Content-Security-Policy received an invalid directive value for ${JSON.stringify(directiveName)}`); | ||
} | ||
else if (rawDirectiveValue === dangerouslyDisableDefaultSrc) { | ||
if (directiveName === "default-src") { | ||
continue; | ||
} | ||
else { | ||
throw new Error(`Content-Security-Policy: tried to disable ${JSON.stringify(directiveName)} as if it were default-src; simply omit the key`); | ||
} | ||
} | ||
else { | ||
@@ -61,5 +68,8 @@ directiveValue = rawDirectiveValue; | ||
} | ||
result[directiveName] = directiveValue; | ||
result.push({ directiveName, directiveValue }); | ||
} | ||
if (!("default-src" in result)) { | ||
if (!result.length) { | ||
throw new Error("Content-Security-Policy has no directives. Either set some or disable the header"); | ||
} | ||
if (!directiveNamesSeen.has("default-src")) { | ||
throw new Error("Content-Security-Policy needs a default-src but none was provided"); | ||
@@ -69,9 +79,5 @@ } | ||
} | ||
function getHeaderValue(req, res, directives) { | ||
function getHeaderValue(req, res, normalizedDirectives) { | ||
const result = []; | ||
for (const directiveName in directives) { | ||
if (!has(directives, directiveName)) { | ||
continue; | ||
} | ||
const rawDirectiveValue = directives[directiveName]; | ||
for (const { directiveName, directiveValue: rawDirectiveValue, } of normalizedDirectives) { | ||
let directiveValue = ""; | ||
@@ -110,6 +116,8 @@ for (const element of rawDirectiveValue) { | ||
}); | ||
const headerName = getHeaderName(options); | ||
const directives = normalizeDirectives(options); | ||
const headerName = options.reportOnly | ||
? "Content-Security-Policy-Report-Only" | ||
: "Content-Security-Policy"; | ||
const normalizedDirectives = normalizeDirectives(options); | ||
return function contentSecurityPolicyMiddleware(req, res, next) { | ||
const result = getHeaderValue(req, res, directives); | ||
const result = getHeaderValue(req, res, normalizedDirectives); | ||
if (result instanceof Error) { | ||
@@ -125,3 +133,4 @@ next(result); | ||
contentSecurityPolicy.getDefaultDirectives = getDefaultDirectives; | ||
contentSecurityPolicy.dangerouslyDisableDefaultSrc = dangerouslyDisableDefaultSrc; | ||
module.exports = contentSecurityPolicy; | ||
exports.default = contentSecurityPolicy; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
function parseMaxAge(value) { | ||
if (value === undefined) { | ||
return 0; | ||
} | ||
else if (typeof value === "number" && | ||
value >= 0 && | ||
Number.isFinite(value)) { | ||
function parseMaxAge(value = 0) { | ||
if (value >= 0 && Number.isFinite(value)) { | ||
return Math.floor(value); | ||
@@ -17,4 +12,3 @@ } | ||
function getHeaderValueFromOptions(options) { | ||
const directives = []; | ||
directives.push(`max-age=${parseMaxAge(options.maxAge)}`); | ||
const directives = [`max-age=${parseMaxAge(options.maxAge)}`]; | ||
if (options.enforce) { | ||
@@ -21,0 +15,0 @@ directives.push("enforce"); |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const DEFAULT_MAX_AGE = 180 * 24 * 60 * 60; | ||
function parseMaxAge(value) { | ||
if (value === undefined) { | ||
return DEFAULT_MAX_AGE; | ||
} | ||
else if (typeof value === "number" && | ||
value >= 0 && | ||
Number.isFinite(value)) { | ||
function parseMaxAge(value = DEFAULT_MAX_AGE) { | ||
if (value >= 0 && Number.isFinite(value)) { | ||
return Math.floor(value); | ||
@@ -12,0 +7,0 @@ } |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
function getHeaderValueFromOptions({ action = "SAMEORIGIN", }) { | ||
action = String(action).toUpperCase(); | ||
if (action === "SAME-ORIGIN") { | ||
return "SAMEORIGIN"; | ||
const normalizedAction = typeof action === "string" ? action.toUpperCase() : action; | ||
switch (normalizedAction) { | ||
case "SAME-ORIGIN": | ||
return "SAMEORIGIN"; | ||
case "DENY": | ||
case "SAMEORIGIN": | ||
return normalizedAction; | ||
case "ALLOW-FROM": | ||
throw new Error("X-Frame-Options no longer supports `ALLOW-FROM` due to poor browser support. See <https://github.com/helmetjs/helmet/wiki/How-to-use-X%E2%80%93Frame%E2%80%93Options's-%60ALLOW%E2%80%93FROM%60-directive> for more info."); | ||
default: | ||
throw new Error(`X-Frame-Options received an invalid action ${JSON.stringify(action)}`); | ||
} | ||
else if (action === "DENY" || action === "SAMEORIGIN") { | ||
return action; | ||
} | ||
else if (action === "ALLOW-FROM") { | ||
throw new Error("X-Frame-Options no longer supports `ALLOW-FROM` due to poor browser support. See <https://github.com/helmetjs/helmet/wiki/How-to-use-X%E2%80%93Frame%E2%80%93Options's-%60ALLOW%E2%80%93FROM%60-directive> for more info."); | ||
} | ||
else { | ||
throw new Error(`X-Frame-Options received an invalid action ${JSON.stringify(action)}`); | ||
} | ||
} | ||
@@ -18,0 +17,0 @@ function xFrameOptions(options = {}) { |
@@ -9,3 +9,3 @@ { | ||
"description": "help secure Express/Connect apps with various HTTP headers", | ||
"version": "4.2.0", | ||
"version": "4.3.0", | ||
"keywords": [ | ||
@@ -60,16 +60,15 @@ "express", | ||
], | ||
"dependencies": {}, | ||
"devDependencies": { | ||
"@types/connect": "^3.4.33", | ||
"@types/jest": "^26.0.15", | ||
"@types/connect": "^3.4.34", | ||
"@types/jest": "^26.0.19", | ||
"@types/supertest": "^2.0.10", | ||
"@typescript-eslint/eslint-plugin": "^4.6.0", | ||
"@typescript-eslint/parser": "^4.6.0", | ||
"@typescript-eslint/eslint-plugin": "^4.11.0", | ||
"@typescript-eslint/parser": "^4.11.0", | ||
"connect": "^3.7.0", | ||
"eslint": "^7.12.1", | ||
"jest": "^26.6.1", | ||
"prettier": "^2.1.2", | ||
"supertest": "^6.0.0", | ||
"ts-jest": "^26.4.3", | ||
"typescript": "^4.0.5" | ||
"eslint": "^7.16.0", | ||
"jest": "^26.6.3", | ||
"prettier": "^2.2.1", | ||
"supertest": "^6.0.1", | ||
"ts-jest": "^26.4.4", | ||
"typescript": "^4.1.3" | ||
}, | ||
@@ -76,0 +75,0 @@ "scripts": { |
@@ -5,3 +5,2 @@ # Helmet | ||
[![npm dependency status](https://david-dm.org/helmetjs/helmet.svg)](https://david-dm.org/helmetjs/helmet) | ||
[![Build Status](https://travis-ci.org/helmetjs/helmet.svg?branch=master)](https://travis-ci.org/helmetjs/helmet) | ||
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fhelmetjs%2Fhelmet.svg?type=shield)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fhelmetjs%2Fhelmet?ref=badge_shield) | ||
@@ -123,3 +122,3 @@ | ||
`options.directives` is an object. Each key is a directive name in camel case (such as `defaultSrc`) or kebab case (such as `default-src`). Each value is an iterable (usually an array) of strings or functions for that directive. If a function appears in the iterable, it will be called with the request and response. | ||
`options.directives` is an object. Each key is a directive name in camel case (such as `defaultSrc`) or kebab case (such as `default-src`). Each value is an iterable (usually an array) of strings or functions for that directive. If a function appears in the iterable, it will be called with the request and response. The `default-src` can be explicitly disabled by setting its value to `helmet.contentSecurityPolicy.dangerouslyDisableDefaultSrc`. | ||
@@ -203,2 +202,12 @@ `options.reportOnly` is a boolean, defaulting to `false`. If `true`, [the `Content-Security-Policy-Report-Only` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only) will be set instead. | ||
); | ||
// Sets "Content-Security-Policy: script-src 'self'" | ||
app.use( | ||
helmet.contentSecurityPolicy({ | ||
directives: { | ||
"default-src": helmet.contentSecurityPolicy.dangerouslyDisableDefaultSrc, | ||
"script-src": ["'self'"], | ||
}, | ||
}) | ||
); | ||
``` | ||
@@ -205,0 +214,0 @@ |
# Security issue reporting & disclosure process | ||
If you feel you have found a security issue or concern with Helmet please reach out to the maintainers. | ||
If you feel you have found a security issue or concern with Helmet, please reach out to the maintainers. | ||
@@ -5,0 +5,0 @@ Email Evan Hahn at <me@evanhahn.com> or Adam Baldwin at <adam@npmjs.com>. |
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
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
70501
482
567