@middy/http-security-headers
Advanced tools
Comparing version 3.0.0-alpha.2 to 3.0.0-alpha.3
@@ -7,14 +7,9 @@ import middy from '@middy/core' | ||
} | ||
expectCT?: { | ||
enforce?: boolean | ||
maxAge?: number | ||
reportUri?: string | ||
} | ||
frameguard?: { | ||
frameOptions?: { | ||
action?: string | ||
} | ||
hidePoweredBy?: { | ||
setTo: string | ||
poweredBy?: { | ||
server: string | ||
} | ||
hsts?: { | ||
strictTransportSecurity?: { | ||
maxAge?: number | ||
@@ -24,12 +19,13 @@ includeSubDomains?: boolean | ||
} | ||
ieNoOpen?: { | ||
downloadOptions?: { | ||
action?: string | ||
} | ||
noSniff?: { | ||
contentTypeOptions?: { | ||
action?: string | ||
} | ||
originAgentCluster?: boolean | ||
referrerPolicy?: { | ||
policy?: string | ||
} | ||
xssFilter?: { | ||
xssProtection?: { | ||
reportUri?: string | ||
@@ -36,0 +32,0 @@ } |
339
index.js
@@ -1,145 +0,280 @@ | ||
import { normalizeHttpResponse } from '@middy/util'; | ||
import { normalizeHttpResponse } from '@middy/util' | ||
// Code and Defaults heavily based off https://helmetjs.github.io/ | ||
const defaults = { | ||
dnsPrefetchControl: { | ||
allow: false | ||
contentSecurityPolicy: { | ||
// Fetch directives | ||
// 'child-src': '', // fallback default-src | ||
// 'connect-src': '', // fallback default-src | ||
'default-src': "'none'", | ||
// 'font-src':'', // fallback default-src | ||
// 'frame-src':'', // fallback child-src > default-src | ||
// 'img-src':'', // fallback default-src | ||
// 'manifest-src':'', // fallback default-src | ||
// 'media-src':'', // fallback default-src | ||
// 'object-src':'', // fallback default-src | ||
// 'prefetch-src':'', // fallback default-src | ||
// 'script-src':'', // fallback default-src | ||
// 'script-src-elem':'', // fallback script-src > default-src | ||
// 'script-src-attr':'', // fallback script-src > default-src | ||
// 'style-src':'', // fallback default-src | ||
// 'style-src-elem':'', // fallback style-src > default-src | ||
// 'style-src-attr':'', // fallback style-src > default-src | ||
// 'worker-src':'', // fallback child-src > script-src > default-src | ||
// Document directives | ||
'base-uri': "'none'", | ||
sandbox: '', | ||
// Navigation directives | ||
'form-action': "'none'", | ||
'frame-ancestors': "'none'", | ||
'navigate-to': "'none'", | ||
// Reporting directives | ||
'report-to': 'csp', | ||
// Other directives | ||
'require-trusted-types-for': "'script'", | ||
'trusted-types': "'none'", | ||
'upgrade-insecure-requests': '' | ||
}, | ||
expectCT: { | ||
enforce: true, | ||
maxAge: 30, | ||
reportUri: '' | ||
contentTypeOptions: { | ||
action: 'nosniff' | ||
}, | ||
frameguard: { | ||
action: 'deny' | ||
crossOriginEmbedderPolicy: { | ||
policy: 'require-corp' | ||
}, | ||
hidePoweredBy: { | ||
setTo: null | ||
crossOriginOpenerPolicy: { | ||
policy: 'same-origin' | ||
}, | ||
hsts: { | ||
maxAge: 180 * 24 * 60 * 60, | ||
includeSubDomains: true, | ||
preload: true | ||
crossOriginResourcePolicy: { | ||
policy: 'same-origin' | ||
}, | ||
ieNoOpen: { | ||
dnsPrefetchControl: { | ||
allow: false | ||
}, | ||
downloadOptions: { | ||
action: 'noopen' | ||
}, | ||
noSniff: { | ||
action: 'nosniff' | ||
frameOptions: { | ||
action: 'deny' | ||
}, | ||
originAgentCluster: {}, | ||
permissionsPolicy: { | ||
// Standard | ||
accelerometer: '', | ||
'ambient-light-sensor': '', | ||
autoplay: '', | ||
battery: '', | ||
camera: '', | ||
'cross-origin-isolated': '', | ||
'display-capture': '', | ||
'document-domain': '', | ||
'encrypted-media': '', | ||
'execution-while-not-rendered': '', | ||
'execution-while-out-of-viewport': '', | ||
fullscreen: '', | ||
geolocation: '', | ||
gyroscope: '', | ||
'keyboard-map': '', | ||
magnetometer: '', | ||
microphone: '', | ||
midi: '', | ||
'navigation-override': '', | ||
payment: '', | ||
'picture-in-picture': '', | ||
'publickey-credentials-get': '', | ||
'screen-wake-lock': '', | ||
'sync-xhr': '', | ||
usb: '', | ||
'web-share': '', | ||
'xr-spatial-tracking': '', | ||
// Proposed | ||
'clipboard-read': '', | ||
'clipboard-write': '', | ||
gamepad: '', | ||
'speaker-selection': '', | ||
// Experimental | ||
'conversion-measurement': '', | ||
'focus-without-user-activation': '', | ||
hid: '', | ||
'idle-detection': '', | ||
'interest-cohort': '', | ||
serial: '', | ||
'sync-script': '', | ||
'trust-token-redemption': '', | ||
'window-placement': '', | ||
'vertical-scroll': '' | ||
}, | ||
permittedCrossDomainPolicies: { | ||
policy: 'none' | ||
policy: 'none' // none, master-only, by-content-type, by-ftp-filename, all | ||
}, | ||
poweredBy: { | ||
server: '' | ||
}, | ||
referrerPolicy: { | ||
policy: 'no-referrer' | ||
}, | ||
xssFilter: { | ||
reportUri: '' | ||
reportTo: { | ||
maxAge: 365 * 24 * 60 * 60, | ||
default: '', | ||
includeSubdomains: true, | ||
csp: '', | ||
staple: '', | ||
xss: '' | ||
}, | ||
strictTransportSecurity: { | ||
maxAge: 180 * 24 * 60 * 60, | ||
includeSubDomains: true, | ||
preload: true | ||
}, | ||
xssProtection: { | ||
reportTo: 'xss' | ||
} | ||
}; | ||
const helmet = {}; | ||
const helmetHtmlOnly = {}; | ||
} | ||
helmet.dnsPrefetchControl = (headers, config) => { | ||
headers['X-DNS-Prefetch-Control'] = config.allow ? 'on' : 'off'; | ||
return headers; | ||
}; | ||
const helmet = {} | ||
const helmetHtmlOnly = {} | ||
helmetHtmlOnly.frameguard = (headers, config) => { | ||
headers['X-Frame-Options'] = config.action.toUpperCase(); | ||
return headers; | ||
}; | ||
helmet.hidePoweredBy = (headers, config) => { | ||
if (config.setTo) { | ||
headers['X-Powered-By'] = config.setTo; | ||
} else { | ||
delete headers.Server; | ||
delete headers['X-Powered-By']; | ||
// *** https://github.com/helmetjs/helmet/tree/main/middlewares *** // | ||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy | ||
helmetHtmlOnly.contentSecurityPolicy = (headers, config) => { | ||
let header = Object.keys(config) | ||
.map(policy => config[policy] ? `${policy} ${config[policy]}` : '') | ||
.filter(str => str) | ||
.join('; ') | ||
if (config.sandbox === '') { | ||
header += '; sandbox' | ||
} | ||
if (config['upgrade-insecure-requests'] === '') { | ||
header += '; upgrade-insecure-requests' | ||
} | ||
headers['Content-Security-Policy'] = header | ||
} | ||
// crossdomain - N/A - for Adobe products | ||
helmetHtmlOnly.crossOriginEmbedderPolicy = (headers, config) => { | ||
headers['Cross-Origin-Embedder-Policy'] = config.policy | ||
} | ||
helmetHtmlOnly.crossOriginOpenerPolicy = (headers, config) => { | ||
headers['Cross-Origin-Opener-Policy'] = config.policy | ||
} | ||
helmetHtmlOnly.crossOriginResourcePolicy = (headers, config) => { | ||
headers['Cross-Origin-Resource-Policy'] = config.policy | ||
} | ||
return headers; | ||
}; | ||
// expectCt - DEPRECATED | ||
// hpkp - DEPRECATED | ||
helmet.hsts = (headers, config) => { | ||
let header = 'max-age=' + Math.round(config.maxAge); | ||
// https://www.permissionspolicy.com/ | ||
helmetHtmlOnly.permissionsPolicy = (headers, config) => { | ||
headers['Permissions-Policy'] = Object.keys(config) | ||
.map(policy => `${policy}=${policy === '*' ? '*' : `(${config[policy]})`}`) | ||
.join(', ') | ||
} | ||
helmet.originAgentCluster = (headers, config) => { | ||
headers['Origin-Agent-Cluster'] = '?1' | ||
} | ||
// https://github.com/helmetjs/referrer-policy | ||
helmet.referrerPolicy = (headers, config) => { | ||
headers['Referrer-Policy'] = config.policy | ||
} | ||
helmetHtmlOnly.reportTo = (headers, config) => { | ||
headers['Report-To'] = Object.keys(config) | ||
.map(group => (config[group] && group !== 'includeSubdomains') ? `{ "group": "default", "max_age": ${config.maxAge}, "endpoints": [ { "url": "${config[group]}" } ]${group === 'default' ? `, "include_subdomains": ${config.includeSubdomains}` : ''} }` : '') | ||
.filter(str => str) | ||
.join(', ') | ||
} | ||
// https://github.com/helmetjs/hsts | ||
helmet.strictTransportSecurity = (headers, config) => { | ||
let header = 'max-age=' + Math.round(config.maxAge) | ||
if (config.includeSubDomains) { | ||
header += '; includeSubDomains'; | ||
header += '; includeSubDomains' | ||
} | ||
if (config.preload) { | ||
header += '; preload'; | ||
header += '; preload' | ||
} | ||
headers['Strict-Transport-Security'] = header | ||
} | ||
headers['Strict-Transport-Security'] = header; | ||
return headers; | ||
}; | ||
// noCache - N/A - separate middleware | ||
helmet.ieNoOpen = (headers, config) => { | ||
headers['X-Download-Options'] = config.action; | ||
return headers; | ||
}; | ||
// X-* // | ||
// https://github.com/helmetjs/dont-sniff-mimetype | ||
helmet.contentTypeOptions = (headers, config) => { | ||
headers['X-Content-Type-Options'] = config.action | ||
} | ||
helmet.noSniff = (headers, config) => { | ||
headers['X-Content-Type-Options'] = config.action; | ||
return headers; | ||
}; | ||
// https://github.com/helmetjs/dns-Prefetch-control | ||
helmet.dnsPrefetchControl = (headers, config) => { | ||
headers['X-DNS-Prefetch-Control'] = config.allow ? 'on' : 'off' | ||
} | ||
helmet.referrerPolicy = (headers, config) => { | ||
headers['Referrer-Policy'] = config.policy; | ||
return headers; | ||
}; | ||
// https://github.com/helmetjs/ienoopen | ||
helmet.downloadOptions = (headers, config) => { | ||
headers['X-Download-Options'] = config.action | ||
} | ||
// https://github.com/helmetjs/frameOptions | ||
helmetHtmlOnly.frameOptions = (headers, config) => { | ||
headers['X-Frame-Options'] = config.action.toUpperCase() | ||
} | ||
// https://github.com/helmetjs/crossdomain | ||
helmet.permittedCrossDomainPolicies = (headers, config) => { | ||
headers['X-Permitted-Cross-Domain-Policies'] = config.policy; | ||
return headers; | ||
}; | ||
headers['X-Permitted-Cross-Domain-Policies'] = config.policy | ||
} | ||
helmetHtmlOnly.xssFilter = (headers, config) => { | ||
let header = '1; mode=block'; | ||
// https://github.com/helmetjs/hide-powered-by | ||
helmet.poweredBy = (headers, config) => { | ||
if (config.server) { | ||
headers['X-Powered-By'] = config.server | ||
} else { | ||
delete headers.Server | ||
delete headers['X-Powered-By'] | ||
} | ||
} | ||
if (config.reportUri) { | ||
header += '; report=' + config.reportUri; | ||
// https://github.com/helmetjs/x-xss-protection | ||
helmetHtmlOnly.xssProtection = (headers, config) => { | ||
let header = '1; mode=block' | ||
if (config.reportTo) { | ||
header += '; report=' + config.reportTo | ||
} | ||
headers['X-XSS-Protection'] = header | ||
} | ||
headers['X-XSS-Protection'] = header; | ||
return headers; | ||
}; | ||
const httpSecurityHeadersMiddleware = (opts = {}) => { | ||
const options = { ...defaults, | ||
...opts | ||
}; | ||
const options = { ...defaults, ...opts } | ||
const httpSecurityHeadersMiddlewareAfter = async request => { | ||
var _request$response$hea; | ||
const httpSecurityHeadersMiddlewareAfter = async (request) => { | ||
normalizeHttpResponse(request) | ||
normalizeHttpResponse(request); | ||
Object.keys(helmet).forEach(key => { | ||
const config = { ...defaults[key], | ||
...options[key] | ||
}; | ||
request.response.headers = helmet[key](request.response.headers, config); | ||
}); | ||
Object.keys(helmet).forEach((key) => { | ||
if (!options[key]) return | ||
const config = { ...defaults[key], ...options[key] } | ||
helmet[key](request.response.headers, config) | ||
}) | ||
if ((_request$response$hea = request.response.headers['Content-Type']) !== null && _request$response$hea !== void 0 && _request$response$hea.includes('text/html')) { | ||
Object.keys(helmetHtmlOnly).forEach(key => { | ||
const config = { ...defaults[key], | ||
...options[key] | ||
}; | ||
request.response.headers = helmetHtmlOnly[key](request.response.headers, config); | ||
}); | ||
if (request.response.headers['Content-Type']?.includes('text/html')) { | ||
Object.keys(helmetHtmlOnly).forEach((key) => { | ||
if (!options[key]) return | ||
const config = { ...defaults[key], ...options[key] } | ||
helmetHtmlOnly[key]( | ||
request.response.headers, | ||
config | ||
) | ||
}) | ||
} | ||
}; | ||
const httpSecurityHeadersMiddlewareOnError = async request => { | ||
if (request.response === undefined) return; | ||
return httpSecurityHeadersMiddlewareAfter(request); | ||
}; | ||
} | ||
const httpSecurityHeadersMiddlewareOnError = async (request) => { | ||
if (request.response === undefined) return | ||
return httpSecurityHeadersMiddlewareAfter(request) | ||
} | ||
return { | ||
after: httpSecurityHeadersMiddlewareAfter, | ||
onError: httpSecurityHeadersMiddlewareOnError | ||
}; | ||
}; | ||
export default httpSecurityHeadersMiddleware; | ||
} | ||
} | ||
export default httpSecurityHeadersMiddleware |
{ | ||
"name": "@middy/http-security-headers", | ||
"version": "3.0.0-alpha.2", | ||
"version": "3.0.0-alpha.3", | ||
"description": "Applies best practice security headers to responses. It's a simplified port of HelmetJS", | ||
@@ -21,3 +21,4 @@ "type": "module", | ||
"test": "npm run test:unit", | ||
"test:unit": "ava" | ||
"test:unit": "ava", | ||
"test:benchmark": "node __benchmarks__/index.js" | ||
}, | ||
@@ -53,9 +54,9 @@ "license": "MIT", | ||
"homepage": "https://github.com/middyjs/middy#readme", | ||
"gitHead": "de30419273ecbff08f367f47c7e320ec981cf145", | ||
"gitHead": "1441158711580313765e6d156046ef0fade0d156", | ||
"dependencies": { | ||
"@middy/util": "^3.0.0-alpha.2" | ||
"@middy/util": "^3.0.0-alpha.3" | ||
}, | ||
"devDependencies": { | ||
"@middy/core": "^3.0.0-alpha.2" | ||
"@middy/core": "^3.0.0-alpha.3" | ||
} | ||
} |
@@ -39,14 +39,22 @@ # Middy http-security-headers middleware | ||
## Options | ||
Setting an option to `false` to cause that rule to be ignored. | ||
- `dnsPrefetchControl` controls browser DNS prefetching | ||
- `expectCt` for handling Certificate Transparency (Future Feature) | ||
- `frameguard` to prevent clickjacking | ||
- `hidePoweredBy` to remove the Server/X-Powered-By header | ||
- `hsts` for HTTP Strict Transport Security | ||
- `ieNoOpen` sets X-Download-Options for IE8+ | ||
- `noSniff` to keep clients from sniffing the MIME type | ||
- `referrerPolicy` to hide the Referer header | ||
- `xssFilter` adds some small XSS protections | ||
### All Responses | ||
- `originAgentCluster`: Default to `{}` to include | ||
- `referrerPolicy`: Default to `{ policy: 'no-referrer' }` | ||
- `strictTransportSecurity`: Default to `{ maxAge: 15552000, includeSubDomains: true, preload: true }` | ||
- X-`dnsPrefetchControl`: Default to `{ allow: false }` | ||
- X-`downloadOptions`: Default to `{ action: 'noopen' }` | ||
- X-`poweredBy`: Default to `{ server: '' }` to remove `Server` and `X-Powered-By` | ||
- X-`contentTypeOptions`: Default to `{ action: 'nosniff' }` | ||
### HTML Responses | ||
- `contentSecurityPolicy`: Default to `{ 'default-src': "'none'", 'base-uri':"'none'", 'sandbox':'', 'form-action':"'none'", 'frame-ancestors':"'none'", 'navigate-to':"'none'", 'report-to':'csp', 'require-trusted-types-for':"'script'", 'trusted-types':"'none'", 'upgrade-insecure-requests':'' }` | ||
- `crossOriginEmbedderPolicy`: Default to `{ policy: 'require-corp' }` | ||
- `crossOriginOpenerPolicy`: Default to `{ policy: 'same-origin' }` | ||
- `crossOriginResourcePolicy`: Default to `{ policy: 'same-origin' }` | ||
- `permissionsPolicy`: Default to `{ *:'', ... }` where all allowed values are set to disable | ||
- `reportTo`: Defaults to `{ maxAge: 31536000, default: '', includeSubdomains: true, csp: '', staple:'', xss: '' }` which won't report by default, needs setting | ||
- X-`frameOptions`: Default to `{ action: 'deny' }` | ||
- X-`xssProtection`: Defaults to `{ reportUri: '' }'` | ||
@@ -53,0 +61,0 @@ |
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
15446
290
93
1
Updated@middy/util@^3.0.0-alpha.3