UNPKG

8.95 kBJavaScriptView Raw
1import { normalizeHttpResponse } from '@middy/util';
2// Code and Defaults heavily based off https://helmetjs.github.io/
3const defaults = {
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};
131const helmet = {};
132const helmetHtmlOnly = {};
133// *** https://github.com/helmetjs/helmet/tree/main/middlewares *** //
134// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
135helmetHtmlOnly.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};
145// crossdomain - N/A - for Adobe products
146helmetHtmlOnly.crossOriginEmbedderPolicy = (headers, config)=>{
147 headers['Cross-Origin-Embedder-Policy'] = config.policy;
148};
149helmetHtmlOnly.crossOriginOpenerPolicy = (headers, config)=>{
150 headers['Cross-Origin-Opener-Policy'] = config.policy;
151};
152helmetHtmlOnly.crossOriginResourcePolicy = (headers, config)=>{
153 headers['Cross-Origin-Resource-Policy'] = config.policy;
154};
155// DEPRECATED: expectCt
156// DEPRECATED: hpkp
157// https://www.permissionspolicy.com/
158helmetHtmlOnly.permissionsPolicy = (headers, config)=>{
159 headers['Permissions-Policy'] = Object.keys(config).map((policy)=>`${policy}=${config[policy] === '*' ? '*' : '(' + config[policy] + ')'}`).join(', ');
160};
161helmet.originAgentCluster = (headers, config)=>{
162 headers['Origin-Agent-Cluster'] = '?1';
163};
164// https://github.com/helmetjs/referrer-policy
165helmet.referrerPolicy = (headers, config)=>{
166 headers['Referrer-Policy'] = config.policy;
167};
168helmetHtmlOnly.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};
174// https://github.com/helmetjs/hsts
175helmet.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};
185// noCache - N/A - separate middleware
186// X-* //
187// https://github.com/helmetjs/dont-sniff-mimetype
188helmet.contentTypeOptions = (headers, config)=>{
189 headers['X-Content-Type-Options'] = config.action;
190};
191// https://github.com/helmetjs/dns-Prefetch-control
192helmet.dnsPrefetchControl = (headers, config)=>{
193 headers['X-DNS-Prefetch-Control'] = config.allow ? 'on' : 'off';
194};
195// https://github.com/helmetjs/ienoopen
196helmet.downloadOptions = (headers, config)=>{
197 headers['X-Download-Options'] = config.action;
198};
199// https://github.com/helmetjs/frameOptions
200helmetHtmlOnly.frameOptions = (headers, config)=>{
201 headers['X-Frame-Options'] = config.action.toUpperCase();
202};
203// https://github.com/helmetjs/crossdomain
204helmet.permittedCrossDomainPolicies = (headers, config)=>{
205 headers['X-Permitted-Cross-Domain-Policies'] = config.policy;
206};
207// https://github.com/helmetjs/hide-powered-by
208helmet.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};
216// https://github.com/helmetjs/x-xss-protection
217helmetHtmlOnly.xssProtection = (headers, config)=>{
218 let header = '1; mode=block';
219 if (config.reportTo) {
220 header += '; report=' + config.reportTo;
221 }
222 headers['X-XSS-Protection'] = header;
223};
224const 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};
259export default httpSecurityHeadersMiddleware;
260