@middy/http-security-headers 3.0.0-alpha.0 → 3.0.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +19 -11
  2. package/index.d.ts +8 -12
  3. package/index.js +243 -0
  4. package/package.json +7 -5
package/README.md CHANGED
@@ -36,18 +36,26 @@ To install this middleware you can use NPM:
36
36
  npm install --save @middy/http-security-headers
37
37
  ```
38
38
 
39
-
40
39
  ## Options
41
-
42
- - `dnsPrefetchControl` controls browser DNS prefetching
43
- - `expectCt` for handling Certificate Transparency (Future Feature)
44
- - `frameguard` to prevent clickjacking
45
- - `hidePoweredBy` to remove the Server/X-Powered-By header
46
- - `hsts` for HTTP Strict Transport Security
47
- - `ieNoOpen` sets X-Download-Options for IE8+
48
- - `noSniff` to keep clients from sniffing the MIME type
49
- - `referrerPolicy` to hide the Referer header
50
- - `xssFilter` adds some small XSS protections
40
+ Setting an option to `false` to cause that rule to be ignored.
41
+
42
+ ### All Responses
43
+ - `originAgentCluster`: Default to `{}` to include
44
+ - `referrerPolicy`: Default to `{ policy: 'no-referrer' }`
45
+ - `strictTransportSecurity`: Default to `{ maxAge: 15552000, includeSubDomains: true, preload: true }`
46
+ - X-`dnsPrefetchControl`: Default to `{ allow: false }`
47
+ - X-`downloadOptions`: Default to `{ action: 'noopen' }`
48
+ - X-`poweredBy`: Default to `{ server: '' }` to remove `Server` and `X-Powered-By`
49
+ - X-`contentTypeOptions`: Default to `{ action: 'nosniff' }`
50
+ ### HTML Responses
51
+ - `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':'' }`
52
+ - `crossOriginEmbedderPolicy`: Default to `{ policy: 'require-corp' }`
53
+ - `crossOriginOpenerPolicy`: Default to `{ policy: 'same-origin' }`
54
+ - `crossOriginResourcePolicy`: Default to `{ policy: 'same-origin' }`
55
+ - `permissionsPolicy`: Default to `{ *:'', ... }` where all allowed values are set to disable
56
+ - `reportTo`: Defaults to `{ maxAge: 31536000, default: '', includeSubdomains: true, csp: '', staple:'', xss: '' }` which won't report by default, needs setting
57
+ - X-`frameOptions`: Default to `{ action: 'deny' }`
58
+ - X-`xssProtection`: Defaults to `{ reportUri: '' }'`
51
59
 
52
60
 
53
61
  ## Sample usage
package/index.d.ts CHANGED
@@ -4,32 +4,28 @@ interface Options {
4
4
  dnsPrefetchControl?: {
5
5
  allow?: boolean
6
6
  }
7
- expectCT?: {
8
- enforce?: boolean
9
- maxAge?: number
10
- reportUri?: string
11
- }
12
- frameguard?: {
7
+ frameOptions?: {
13
8
  action?: string
14
9
  }
15
- hidePoweredBy?: {
16
- setTo: string
10
+ poweredBy?: {
11
+ server: string
17
12
  }
18
- hsts?: {
13
+ strictTransportSecurity?: {
19
14
  maxAge?: number
20
15
  includeSubDomains?: boolean
21
16
  preload?: boolean
22
17
  }
23
- ieNoOpen?: {
18
+ downloadOptions?: {
24
19
  action?: string
25
20
  }
26
- noSniff?: {
21
+ contentTypeOptions?: {
27
22
  action?: string
28
23
  }
24
+ originAgentCluster?: boolean
29
25
  referrerPolicy?: {
30
26
  policy?: string
31
27
  }
32
- xssFilter?: {
28
+ xssProtection?: {
33
29
  reportUri?: string
34
30
  }
35
31
  }
package/index.js ADDED
@@ -0,0 +1,243 @@
1
+ import { normalizeHttpResponse } from '@middy/util';
2
+ const defaults = {
3
+ contentSecurityPolicy: {
4
+ 'default-src': "'none'",
5
+ 'base-uri': "'none'",
6
+ sandbox: '',
7
+ 'form-action': "'none'",
8
+ 'frame-ancestors': "'none'",
9
+ 'navigate-to': "'none'",
10
+ 'report-to': 'csp',
11
+ 'require-trusted-types-for': "'script'",
12
+ 'trusted-types': "'none'",
13
+ 'upgrade-insecure-requests': ''
14
+ },
15
+ contentTypeOptions: {
16
+ action: 'nosniff'
17
+ },
18
+ crossOriginEmbedderPolicy: {
19
+ policy: 'require-corp'
20
+ },
21
+ crossOriginOpenerPolicy: {
22
+ policy: 'same-origin'
23
+ },
24
+ crossOriginResourcePolicy: {
25
+ policy: 'same-origin'
26
+ },
27
+ dnsPrefetchControl: {
28
+ allow: false
29
+ },
30
+ downloadOptions: {
31
+ action: 'noopen'
32
+ },
33
+ frameOptions: {
34
+ action: 'deny'
35
+ },
36
+ originAgentCluster: {},
37
+ permissionsPolicy: {
38
+ accelerometer: '',
39
+ 'ambient-light-sensor': '',
40
+ autoplay: '',
41
+ battery: '',
42
+ camera: '',
43
+ 'cross-origin-isolated': '',
44
+ 'display-capture': '',
45
+ 'document-domain': '',
46
+ 'encrypted-media': '',
47
+ 'execution-while-not-rendered': '',
48
+ 'execution-while-out-of-viewport': '',
49
+ fullscreen: '',
50
+ geolocation: '',
51
+ gyroscope: '',
52
+ 'keyboard-map': '',
53
+ magnetometer: '',
54
+ microphone: '',
55
+ midi: '',
56
+ 'navigation-override': '',
57
+ payment: '',
58
+ 'picture-in-picture': '',
59
+ 'publickey-credentials-get': '',
60
+ 'screen-wake-lock': '',
61
+ 'sync-xhr': '',
62
+ usb: '',
63
+ 'web-share': '',
64
+ 'xr-spatial-tracking': '',
65
+ 'clipboard-read': '',
66
+ 'clipboard-write': '',
67
+ gamepad: '',
68
+ 'speaker-selection': '',
69
+ 'conversion-measurement': '',
70
+ 'focus-without-user-activation': '',
71
+ hid: '',
72
+ 'idle-detection': '',
73
+ 'interest-cohort': '',
74
+ serial: '',
75
+ 'sync-script': '',
76
+ 'trust-token-redemption': '',
77
+ 'window-placement': '',
78
+ 'vertical-scroll': ''
79
+ },
80
+ permittedCrossDomainPolicies: {
81
+ policy: 'none'
82
+ },
83
+ poweredBy: {
84
+ server: ''
85
+ },
86
+ referrerPolicy: {
87
+ policy: 'no-referrer'
88
+ },
89
+ reportTo: {
90
+ maxAge: 365 * 24 * 60 * 60,
91
+ default: '',
92
+ includeSubdomains: true,
93
+ csp: '',
94
+ staple: '',
95
+ xss: ''
96
+ },
97
+ strictTransportSecurity: {
98
+ maxAge: 180 * 24 * 60 * 60,
99
+ includeSubDomains: true,
100
+ preload: true
101
+ },
102
+ xssProtection: {
103
+ reportTo: 'xss'
104
+ }
105
+ };
106
+ const helmet = {};
107
+ const helmetHtmlOnly = {};
108
+
109
+ helmetHtmlOnly.contentSecurityPolicy = (headers, config) => {
110
+ let header = Object.keys(config).map(policy => config[policy] ? `${policy} ${config[policy]}` : '').filter(str => str).join('; ');
111
+
112
+ if (config.sandbox === '') {
113
+ header += '; sandbox';
114
+ }
115
+
116
+ if (config['upgrade-insecure-requests'] === '') {
117
+ header += '; upgrade-insecure-requests';
118
+ }
119
+
120
+ headers['Content-Security-Policy'] = header;
121
+ };
122
+
123
+ helmetHtmlOnly.crossOriginEmbedderPolicy = (headers, config) => {
124
+ headers['Cross-Origin-Embedder-Policy'] = config.policy;
125
+ };
126
+
127
+ helmetHtmlOnly.crossOriginOpenerPolicy = (headers, config) => {
128
+ headers['Cross-Origin-Opener-Policy'] = config.policy;
129
+ };
130
+
131
+ helmetHtmlOnly.crossOriginResourcePolicy = (headers, config) => {
132
+ headers['Cross-Origin-Resource-Policy'] = config.policy;
133
+ };
134
+
135
+ helmetHtmlOnly.permissionsPolicy = (headers, config) => {
136
+ headers['Permissions-Policy'] = Object.keys(config).map(policy => `${policy}=${policy === '*' ? '*' : `(${config[policy]})`}`).join(', ');
137
+ };
138
+
139
+ helmet.originAgentCluster = (headers, config) => {
140
+ headers['Origin-Agent-Cluster'] = '?1';
141
+ };
142
+
143
+ helmet.referrerPolicy = (headers, config) => {
144
+ headers['Referrer-Policy'] = config.policy;
145
+ };
146
+
147
+ helmetHtmlOnly.reportTo = (headers, config) => {
148
+ 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(', ');
149
+ };
150
+
151
+ helmet.strictTransportSecurity = (headers, config) => {
152
+ let header = 'max-age=' + Math.round(config.maxAge);
153
+
154
+ if (config.includeSubDomains) {
155
+ header += '; includeSubDomains';
156
+ }
157
+
158
+ if (config.preload) {
159
+ header += '; preload';
160
+ }
161
+
162
+ headers['Strict-Transport-Security'] = header;
163
+ };
164
+
165
+ helmet.contentTypeOptions = (headers, config) => {
166
+ headers['X-Content-Type-Options'] = config.action;
167
+ };
168
+
169
+ helmet.dnsPrefetchControl = (headers, config) => {
170
+ headers['X-DNS-Prefetch-Control'] = config.allow ? 'on' : 'off';
171
+ };
172
+
173
+ helmet.downloadOptions = (headers, config) => {
174
+ headers['X-Download-Options'] = config.action;
175
+ };
176
+
177
+ helmetHtmlOnly.frameOptions = (headers, config) => {
178
+ headers['X-Frame-Options'] = config.action.toUpperCase();
179
+ };
180
+
181
+ helmet.permittedCrossDomainPolicies = (headers, config) => {
182
+ headers['X-Permitted-Cross-Domain-Policies'] = config.policy;
183
+ };
184
+
185
+ helmet.poweredBy = (headers, config) => {
186
+ if (config.server) {
187
+ headers['X-Powered-By'] = config.server;
188
+ } else {
189
+ delete headers.Server;
190
+ delete headers['X-Powered-By'];
191
+ }
192
+ };
193
+
194
+ helmetHtmlOnly.xssProtection = (headers, config) => {
195
+ let header = '1; mode=block';
196
+
197
+ if (config.reportTo) {
198
+ header += '; report=' + config.reportTo;
199
+ }
200
+
201
+ headers['X-XSS-Protection'] = header;
202
+ };
203
+
204
+ const httpSecurityHeadersMiddleware = (opts = {}) => {
205
+ const options = { ...defaults,
206
+ ...opts
207
+ };
208
+
209
+ const httpSecurityHeadersMiddlewareAfter = async request => {
210
+ var _request$response$hea;
211
+
212
+ normalizeHttpResponse(request);
213
+ Object.keys(helmet).forEach(key => {
214
+ if (!options[key]) return;
215
+ const config = { ...defaults[key],
216
+ ...options[key]
217
+ };
218
+ helmet[key](request.response.headers, config);
219
+ });
220
+
221
+ if ((_request$response$hea = request.response.headers['Content-Type']) !== null && _request$response$hea !== void 0 && _request$response$hea.includes('text/html')) {
222
+ Object.keys(helmetHtmlOnly).forEach(key => {
223
+ if (!options[key]) return;
224
+ const config = { ...defaults[key],
225
+ ...options[key]
226
+ };
227
+ helmetHtmlOnly[key](request.response.headers, config);
228
+ });
229
+ }
230
+ };
231
+
232
+ const httpSecurityHeadersMiddlewareOnError = async request => {
233
+ if (request.response === undefined) return;
234
+ return httpSecurityHeadersMiddlewareAfter(request);
235
+ };
236
+
237
+ return {
238
+ after: httpSecurityHeadersMiddlewareAfter,
239
+ onError: httpSecurityHeadersMiddlewareOnError
240
+ };
241
+ };
242
+
243
+ export default httpSecurityHeadersMiddleware;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@middy/http-security-headers",
3
- "version": "3.0.0-alpha.0",
3
+ "version": "3.0.0-alpha.4",
4
4
  "description": "Applies best practice security headers to responses. It's a simplified port of HelmetJS",
5
5
  "type": "module",
6
6
  "engines": {
@@ -13,11 +13,13 @@
13
13
  "exports": "./index.js",
14
14
  "types": "index.d.ts",
15
15
  "files": [
16
+ "index.js",
16
17
  "index.d.ts"
17
18
  ],
18
19
  "scripts": {
19
20
  "test": "npm run test:unit",
20
- "test:unit": "ava"
21
+ "test:unit": "ava",
22
+ "test:benchmark": "node __benchmarks__/index.js"
21
23
  },
22
24
  "license": "MIT",
23
25
  "keywords": [
@@ -48,11 +50,11 @@
48
50
  "url": "https://github.com/middyjs/middy/issues"
49
51
  },
50
52
  "homepage": "https://github.com/middyjs/middy#readme",
51
- "gitHead": "c533f62841c8a39d061d7b94f30ba178f002c8db",
53
+ "gitHead": "d4bea7f4e21f6a9bbb1f6f6908361169598b9e53",
52
54
  "dependencies": {
53
- "@middy/util": "^3.0.0-alpha.0"
55
+ "@middy/util": "^3.0.0-alpha.4"
54
56
  },
55
57
  "devDependencies": {
56
- "@middy/core": "^3.0.0-alpha.0"
58
+ "@middy/core": "^3.0.0-alpha.4"
57
59
  }
58
60
  }