@middy/http-security-headers 5.0.0-alpha.1 → 5.0.0-alpha.2

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 (2) hide show
  1. package/index.js +240 -268
  2. package/package.json +3 -3
package/index.js CHANGED
@@ -1,288 +1,260 @@
1
- import { normalizeHttpResponse } from '@middy/util'
2
-
1
+ import { normalizeHttpResponse } from '@middy/util';
3
2
  // Code and Defaults heavily based off https://helmetjs.github.io/
4
-
5
3
  const defaults = {
6
- contentSecurityPolicy: {
7
- // Fetch directives
8
- // 'child-src': '', // fallback default-src
9
- // 'connect-src': '', // fallback default-src
10
- 'default-src': "'none'",
11
- // 'font-src':'', // fallback default-src
12
- // 'frame-src':'', // fallback child-src > default-src
13
- // 'img-src':'', // fallback default-src
14
- // 'manifest-src':'', // fallback default-src
15
- // 'media-src':'', // fallback default-src
16
- // 'object-src':'', // fallback default-src
17
- // 'prefetch-src':'', // fallback default-src
18
- // 'script-src':'', // fallback default-src
19
- // 'script-src-elem':'', // fallback script-src > default-src
20
- // 'script-src-attr':'', // fallback script-src > default-src
21
- // 'style-src':'', // fallback default-src
22
- // 'style-src-elem':'', // fallback style-src > default-src
23
- // 'style-src-attr':'', // fallback style-src > default-src
24
- // 'worker-src':'', // fallback child-src > script-src > default-src
25
- // Document directives
26
- 'base-uri': "'none'",
27
- sandbox: '',
28
- // Navigation directives
29
- 'form-action': "'none'",
30
- 'frame-ancestors': "'none'",
31
- 'navigate-to': "'none'",
32
- // Reporting directives
33
- 'report-to': 'csp',
34
- // Other directives
35
- 'require-trusted-types-for': "'script'",
36
- 'trusted-types': "'none'",
37
- 'upgrade-insecure-requests': ''
38
- },
39
- contentTypeOptions: {
40
- action: 'nosniff'
41
- },
42
- crossOriginEmbedderPolicy: {
43
- policy: 'require-corp'
44
- },
45
- crossOriginOpenerPolicy: {
46
- policy: 'same-origin'
47
- },
48
- crossOriginResourcePolicy: {
49
- policy: 'same-origin'
50
- },
51
- dnsPrefetchControl: {
52
- allow: false
53
- },
54
- downloadOptions: {
55
- action: 'noopen'
56
- },
57
- frameOptions: {
58
- action: 'deny'
59
- },
60
- originAgentCluster: {},
61
- permissionsPolicy: {
62
- // Standard
63
- accelerometer: '',
64
- 'ambient-light-sensor': '',
65
- autoplay: '',
66
- battery: '',
67
- camera: '',
68
- 'cross-origin-isolated': '',
69
- 'display-capture': '',
70
- 'document-domain': '',
71
- 'encrypted-media': '',
72
- 'execution-while-not-rendered': '',
73
- 'execution-while-out-of-viewport': '',
74
- fullscreen: '',
75
- geolocation: '',
76
- gyroscope: '',
77
- 'keyboard-map': '',
78
- magnetometer: '',
79
- microphone: '',
80
- midi: '',
81
- 'navigation-override': '',
82
- payment: '',
83
- 'picture-in-picture': '',
84
- 'publickey-credentials-get': '',
85
- 'screen-wake-lock': '',
86
- 'sync-xhr': '',
87
- usb: '',
88
- 'web-share': '',
89
- 'xr-spatial-tracking': '',
90
- // Proposed
91
- 'clipboard-read': '',
92
- 'clipboard-write': '',
93
- gamepad: '',
94
- 'speaker-selection': '',
95
- // Experimental
96
- 'conversion-measurement': '',
97
- 'focus-without-user-activation': '',
98
- hid: '',
99
- 'idle-detection': '',
100
- 'interest-cohort': '',
101
- serial: '',
102
- 'sync-script': '',
103
- 'trust-token-redemption': '',
104
- 'window-placement': '',
105
- 'vertical-scroll': ''
106
- },
107
- permittedCrossDomainPolicies: {
108
- policy: 'none' // none, master-only, by-content-type, by-ftp-filename, all
109
- },
110
- poweredBy: {
111
- server: ''
112
- },
113
- referrerPolicy: {
114
- policy: 'no-referrer'
115
- },
116
- reportTo: {
117
- maxAge: 365 * 24 * 60 * 60,
118
- default: '',
119
- includeSubdomains: true,
120
- csp: '',
121
- staple: '',
122
- xss: ''
123
- },
124
- strictTransportSecurity: {
125
- maxAge: 180 * 24 * 60 * 60,
126
- includeSubDomains: true,
127
- preload: true
128
- },
129
- xssProtection: {
130
- reportTo: 'xss'
131
- }
132
- }
133
-
134
- const helmet = {}
135
- const helmetHtmlOnly = {}
136
-
4
+ contentSecurityPolicy: {
5
+ // Fetch directives
6
+ // 'child-src': '', // fallback default-src
7
+ // 'connect-src': '', // fallback default-src
8
+ 'default-src': "'none'",
9
+ // 'font-src':'', // fallback default-src
10
+ // 'frame-src':'', // fallback child-src > default-src
11
+ // 'img-src':'', // fallback default-src
12
+ // 'manifest-src':'', // fallback default-src
13
+ // 'media-src':'', // fallback default-src
14
+ // 'object-src':'', // fallback default-src
15
+ // 'prefetch-src':'', // fallback default-src
16
+ // 'script-src':'', // fallback default-src
17
+ // 'script-src-elem':'', // fallback script-src > default-src
18
+ // 'script-src-attr':'', // fallback script-src > default-src
19
+ // 'style-src':'', // fallback default-src
20
+ // 'style-src-elem':'', // fallback style-src > default-src
21
+ // 'style-src-attr':'', // fallback style-src > default-src
22
+ // 'worker-src':'', // fallback child-src > script-src > default-src
23
+ // Document directives
24
+ 'base-uri': "'none'",
25
+ sandbox: '',
26
+ // Navigation directives
27
+ 'form-action': "'none'",
28
+ 'frame-ancestors': "'none'",
29
+ 'navigate-to': "'none'",
30
+ // Reporting directives
31
+ 'report-to': 'csp',
32
+ // Other directives
33
+ 'require-trusted-types-for': "'script'",
34
+ 'trusted-types': "'none'",
35
+ 'upgrade-insecure-requests': ''
36
+ },
37
+ contentTypeOptions: {
38
+ action: 'nosniff'
39
+ },
40
+ crossOriginEmbedderPolicy: {
41
+ policy: 'require-corp'
42
+ },
43
+ crossOriginOpenerPolicy: {
44
+ policy: 'same-origin'
45
+ },
46
+ crossOriginResourcePolicy: {
47
+ policy: 'same-origin'
48
+ },
49
+ dnsPrefetchControl: {
50
+ allow: false
51
+ },
52
+ downloadOptions: {
53
+ action: 'noopen'
54
+ },
55
+ frameOptions: {
56
+ action: 'deny'
57
+ },
58
+ originAgentCluster: {},
59
+ permissionsPolicy: {
60
+ // Standard
61
+ accelerometer: '',
62
+ 'ambient-light-sensor': '',
63
+ autoplay: '',
64
+ battery: '',
65
+ camera: '',
66
+ 'cross-origin-isolated': '',
67
+ 'display-capture': '',
68
+ 'document-domain': '',
69
+ 'encrypted-media': '',
70
+ 'execution-while-not-rendered': '',
71
+ 'execution-while-out-of-viewport': '',
72
+ fullscreen: '',
73
+ geolocation: '',
74
+ gyroscope: '',
75
+ 'keyboard-map': '',
76
+ magnetometer: '',
77
+ microphone: '',
78
+ midi: '',
79
+ 'navigation-override': '',
80
+ payment: '',
81
+ 'picture-in-picture': '',
82
+ 'publickey-credentials-get': '',
83
+ 'screen-wake-lock': '',
84
+ 'sync-xhr': '',
85
+ usb: '',
86
+ 'web-share': '',
87
+ 'xr-spatial-tracking': '',
88
+ // Proposed
89
+ 'clipboard-read': '',
90
+ 'clipboard-write': '',
91
+ gamepad: '',
92
+ 'speaker-selection': '',
93
+ // Experimental
94
+ 'conversion-measurement': '',
95
+ 'focus-without-user-activation': '',
96
+ hid: '',
97
+ 'idle-detection': '',
98
+ 'interest-cohort': '',
99
+ serial: '',
100
+ 'sync-script': '',
101
+ 'trust-token-redemption': '',
102
+ 'window-placement': '',
103
+ 'vertical-scroll': ''
104
+ },
105
+ permittedCrossDomainPolicies: {
106
+ policy: 'none' // none, master-only, by-content-type, by-ftp-filename, all
107
+ },
108
+ poweredBy: {
109
+ server: ''
110
+ },
111
+ referrerPolicy: {
112
+ policy: 'no-referrer'
113
+ },
114
+ reportTo: {
115
+ maxAge: 365 * 24 * 60 * 60,
116
+ default: '',
117
+ includeSubdomains: true,
118
+ csp: '',
119
+ staple: '',
120
+ xss: ''
121
+ },
122
+ strictTransportSecurity: {
123
+ maxAge: 180 * 24 * 60 * 60,
124
+ includeSubDomains: true,
125
+ preload: true
126
+ },
127
+ xssProtection: {
128
+ reportTo: 'xss'
129
+ }
130
+ };
131
+ const helmet = {};
132
+ const helmetHtmlOnly = {};
137
133
  // *** https://github.com/helmetjs/helmet/tree/main/middlewares *** //
138
134
  // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
139
- helmetHtmlOnly.contentSecurityPolicy = (headers, config) => {
140
- let header = Object.keys(config)
141
- .map((policy) => (config[policy] ? `${policy} ${config[policy]}` : ''))
142
- .filter((str) => str)
143
- .join('; ')
144
- if (config.sandbox === '') {
145
- header += '; sandbox'
146
- }
147
- if (config['upgrade-insecure-requests'] === '') {
148
- header += '; upgrade-insecure-requests'
149
- }
150
- headers['Content-Security-Policy'] = header
151
- }
135
+ helmetHtmlOnly.contentSecurityPolicy = (headers, config)=>{
136
+ let header = Object.keys(config).map((policy)=>config[policy] ? `${policy} ${config[policy]}` : '').filter((str)=>str).join('; ');
137
+ if (config.sandbox === '') {
138
+ header += '; sandbox';
139
+ }
140
+ if (config['upgrade-insecure-requests'] === '') {
141
+ header += '; upgrade-insecure-requests';
142
+ }
143
+ headers['Content-Security-Policy'] = header;
144
+ };
152
145
  // crossdomain - N/A - for Adobe products
153
- helmetHtmlOnly.crossOriginEmbedderPolicy = (headers, config) => {
154
- headers['Cross-Origin-Embedder-Policy'] = config.policy
155
- }
156
- helmetHtmlOnly.crossOriginOpenerPolicy = (headers, config) => {
157
- headers['Cross-Origin-Opener-Policy'] = config.policy
158
- }
159
- helmetHtmlOnly.crossOriginResourcePolicy = (headers, config) => {
160
- headers['Cross-Origin-Resource-Policy'] = config.policy
161
- }
162
-
146
+ helmetHtmlOnly.crossOriginEmbedderPolicy = (headers, config)=>{
147
+ headers['Cross-Origin-Embedder-Policy'] = config.policy;
148
+ };
149
+ helmetHtmlOnly.crossOriginOpenerPolicy = (headers, config)=>{
150
+ headers['Cross-Origin-Opener-Policy'] = config.policy;
151
+ };
152
+ helmetHtmlOnly.crossOriginResourcePolicy = (headers, config)=>{
153
+ headers['Cross-Origin-Resource-Policy'] = config.policy;
154
+ };
163
155
  // DEPRECATED: expectCt
164
156
  // DEPRECATED: hpkp
165
-
166
157
  // https://www.permissionspolicy.com/
167
- helmetHtmlOnly.permissionsPolicy = (headers, config) => {
168
- headers['Permissions-Policy'] = Object.keys(config)
169
- .map(
170
- (policy) =>
171
- `${policy}=${config[policy] === '*' ? '*' : '(' + config[policy] + ')'}`
172
- )
173
- .join(', ')
174
- }
175
-
176
- helmet.originAgentCluster = (headers, config) => {
177
- headers['Origin-Agent-Cluster'] = '?1'
178
- }
179
-
158
+ helmetHtmlOnly.permissionsPolicy = (headers, config)=>{
159
+ headers['Permissions-Policy'] = Object.keys(config).map((policy)=>`${policy}=${config[policy] === '*' ? '*' : '(' + config[policy] + ')'}`).join(', ');
160
+ };
161
+ helmet.originAgentCluster = (headers, config)=>{
162
+ headers['Origin-Agent-Cluster'] = '?1';
163
+ };
180
164
  // https://github.com/helmetjs/referrer-policy
181
- helmet.referrerPolicy = (headers, config) => {
182
- headers['Referrer-Policy'] = config.policy
183
- }
184
-
185
- helmetHtmlOnly.reportTo = (headers, config) => {
186
- headers['Report-To'] = Object.keys(config)
187
- .map((group) => {
188
- const includeSubdomains =
189
- group === 'default'
190
- ? `, "include_subdomains": ${config.includeSubdomains}`
191
- : ''
192
- return config[group] && group !== 'includeSubdomains'
193
- ? `{ "group": "default", "max_age": ${config.maxAge}, "endpoints": [ { "url": "${config[group]}" } ]${includeSubdomains} }`
194
- : ''
195
- })
196
- .filter((str) => str)
197
- .join(', ')
198
- }
199
-
165
+ helmet.referrerPolicy = (headers, config)=>{
166
+ headers['Referrer-Policy'] = config.policy;
167
+ };
168
+ helmetHtmlOnly.reportTo = (headers, config)=>{
169
+ headers['Report-To'] = Object.keys(config).map((group)=>{
170
+ const includeSubdomains = group === 'default' ? `, "include_subdomains": ${config.includeSubdomains}` : '';
171
+ return config[group] && group !== 'includeSubdomains' ? `{ "group": "default", "max_age": ${config.maxAge}, "endpoints": [ { "url": "${config[group]}" } ]${includeSubdomains} }` : '';
172
+ }).filter((str)=>str).join(', ');
173
+ };
200
174
  // https://github.com/helmetjs/hsts
201
- helmet.strictTransportSecurity = (headers, config) => {
202
- let header = 'max-age=' + Math.round(config.maxAge)
203
- if (config.includeSubDomains) {
204
- header += '; includeSubDomains'
205
- }
206
- if (config.preload) {
207
- header += '; preload'
208
- }
209
- headers['Strict-Transport-Security'] = header
210
- }
211
-
175
+ helmet.strictTransportSecurity = (headers, config)=>{
176
+ let header = 'max-age=' + Math.round(config.maxAge);
177
+ if (config.includeSubDomains) {
178
+ header += '; includeSubDomains';
179
+ }
180
+ if (config.preload) {
181
+ header += '; preload';
182
+ }
183
+ headers['Strict-Transport-Security'] = header;
184
+ };
212
185
  // noCache - N/A - separate middleware
213
-
214
186
  // X-* //
215
187
  // https://github.com/helmetjs/dont-sniff-mimetype
216
- helmet.contentTypeOptions = (headers, config) => {
217
- headers['X-Content-Type-Options'] = config.action
218
- }
219
-
188
+ helmet.contentTypeOptions = (headers, config)=>{
189
+ headers['X-Content-Type-Options'] = config.action;
190
+ };
220
191
  // https://github.com/helmetjs/dns-Prefetch-control
221
- helmet.dnsPrefetchControl = (headers, config) => {
222
- headers['X-DNS-Prefetch-Control'] = config.allow ? 'on' : 'off'
223
- }
224
-
192
+ helmet.dnsPrefetchControl = (headers, config)=>{
193
+ headers['X-DNS-Prefetch-Control'] = config.allow ? 'on' : 'off';
194
+ };
225
195
  // https://github.com/helmetjs/ienoopen
226
- helmet.downloadOptions = (headers, config) => {
227
- headers['X-Download-Options'] = config.action
228
- }
229
-
196
+ helmet.downloadOptions = (headers, config)=>{
197
+ headers['X-Download-Options'] = config.action;
198
+ };
230
199
  // https://github.com/helmetjs/frameOptions
231
- helmetHtmlOnly.frameOptions = (headers, config) => {
232
- headers['X-Frame-Options'] = config.action.toUpperCase()
233
- }
234
-
200
+ helmetHtmlOnly.frameOptions = (headers, config)=>{
201
+ headers['X-Frame-Options'] = config.action.toUpperCase();
202
+ };
235
203
  // https://github.com/helmetjs/crossdomain
236
- helmet.permittedCrossDomainPolicies = (headers, config) => {
237
- headers['X-Permitted-Cross-Domain-Policies'] = config.policy
238
- }
239
-
204
+ helmet.permittedCrossDomainPolicies = (headers, config)=>{
205
+ headers['X-Permitted-Cross-Domain-Policies'] = config.policy;
206
+ };
240
207
  // https://github.com/helmetjs/hide-powered-by
241
- helmet.poweredBy = (headers, config) => {
242
- if (config.server) {
243
- headers['X-Powered-By'] = config.server
244
- } else {
245
- delete headers.Server
246
- delete headers['X-Powered-By']
247
- }
248
- }
249
-
208
+ helmet.poweredBy = (headers, config)=>{
209
+ if (config.server) {
210
+ headers['X-Powered-By'] = config.server;
211
+ } else {
212
+ delete headers.Server;
213
+ delete headers['X-Powered-By'];
214
+ }
215
+ };
250
216
  // https://github.com/helmetjs/x-xss-protection
251
- helmetHtmlOnly.xssProtection = (headers, config) => {
252
- let header = '1; mode=block'
253
- if (config.reportTo) {
254
- header += '; report=' + config.reportTo
255
- }
256
- headers['X-XSS-Protection'] = header
257
- }
258
-
259
- const httpSecurityHeadersMiddleware = (opts = {}) => {
260
- const options = { ...defaults, ...opts }
261
-
262
- const httpSecurityHeadersMiddlewareAfter = async (request) => {
263
- normalizeHttpResponse(request)
264
-
265
- Object.keys(helmet).forEach((key) => {
266
- if (!options[key]) return
267
- const config = { ...defaults[key], ...options[key] }
268
- helmet[key](request.response.headers, config)
269
- })
270
-
271
- if (request.response.headers['Content-Type']?.includes('text/html')) {
272
- Object.keys(helmetHtmlOnly).forEach((key) => {
273
- if (!options[key]) return
274
- const config = { ...defaults[key], ...options[key] }
275
- helmetHtmlOnly[key](request.response.headers, config)
276
- })
217
+ helmetHtmlOnly.xssProtection = (headers, config)=>{
218
+ let header = '1; mode=block';
219
+ if (config.reportTo) {
220
+ header += '; report=' + config.reportTo;
277
221
  }
278
- }
279
- const httpSecurityHeadersMiddlewareOnError = async (request) => {
280
- if (request.response === undefined) return
281
- await httpSecurityHeadersMiddlewareAfter(request)
282
- }
283
- return {
284
- after: httpSecurityHeadersMiddlewareAfter,
285
- onError: httpSecurityHeadersMiddlewareOnError
286
- }
287
- }
288
- export default httpSecurityHeadersMiddleware
222
+ headers['X-XSS-Protection'] = header;
223
+ };
224
+ const httpSecurityHeadersMiddleware = (opts = {})=>{
225
+ const options = {
226
+ ...defaults,
227
+ ...opts
228
+ };
229
+ const httpSecurityHeadersMiddlewareAfter = async (request)=>{
230
+ normalizeHttpResponse(request);
231
+ Object.keys(helmet).forEach((key)=>{
232
+ if (!options[key]) return;
233
+ const config = {
234
+ ...defaults[key],
235
+ ...options[key]
236
+ };
237
+ helmet[key](request.response.headers, config);
238
+ });
239
+ if (request.response.headers['Content-Type']?.includes('text/html')) {
240
+ Object.keys(helmetHtmlOnly).forEach((key)=>{
241
+ if (!options[key]) return;
242
+ const config = {
243
+ ...defaults[key],
244
+ ...options[key]
245
+ };
246
+ helmetHtmlOnly[key](request.response.headers, config);
247
+ });
248
+ }
249
+ };
250
+ const httpSecurityHeadersMiddlewareOnError = async (request)=>{
251
+ if (request.response === undefined) return;
252
+ await httpSecurityHeadersMiddlewareAfter(request);
253
+ };
254
+ return {
255
+ after: httpSecurityHeadersMiddlewareAfter,
256
+ onError: httpSecurityHeadersMiddlewareOnError
257
+ };
258
+ };
259
+ export default httpSecurityHeadersMiddleware;
260
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@middy/http-security-headers",
3
- "version": "5.0.0-alpha.1",
3
+ "version": "5.0.0-alpha.2",
4
4
  "description": "Applies best practice security headers to responses. It's a simplified port of HelmetJS",
5
5
  "type": "module",
6
6
  "engines": {
@@ -64,9 +64,9 @@
64
64
  },
65
65
  "gitHead": "ebce8d5df8783077fa49ba62ee9be20e8486a7f1",
66
66
  "dependencies": {
67
- "@middy/util": "5.0.0-alpha.1"
67
+ "@middy/util": "5.0.0-alpha.2"
68
68
  },
69
69
  "devDependencies": {
70
- "@middy/core": "5.0.0-alpha.1"
70
+ "@middy/core": "5.0.0-alpha.2"
71
71
  }
72
72
  }