UNPKG

6.93 kBJavaScriptView Raw
1import { normalizeHttpResponse } from '@middy/util';
2const 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};
106const helmet = {};
107const helmetHtmlOnly = {};
108helmetHtmlOnly.contentSecurityPolicy = (headers, config)=>{
109 let header = Object.keys(config).map((policy)=>config[policy] ? `${policy} ${config[policy]}` : '').filter((str)=>str).join('; ');
110 if (config.sandbox === '') {
111 header += '; sandbox';
112 }
113 if (config['upgrade-insecure-requests'] === '') {
114 header += '; upgrade-insecure-requests';
115 }
116 headers['Content-Security-Policy'] = header;
117};
118helmetHtmlOnly.crossOriginEmbedderPolicy = (headers, config)=>{
119 headers['Cross-Origin-Embedder-Policy'] = config.policy;
120};
121helmetHtmlOnly.crossOriginOpenerPolicy = (headers, config)=>{
122 headers['Cross-Origin-Opener-Policy'] = config.policy;
123};
124helmetHtmlOnly.crossOriginResourcePolicy = (headers, config)=>{
125 headers['Cross-Origin-Resource-Policy'] = config.policy;
126};
127helmetHtmlOnly.permissionsPolicy = (headers, config)=>{
128 headers['Permissions-Policy'] = Object.keys(config).map((policy)=>`${policy}=${config[policy] === '*' ? '*' : '(' + config[policy] + ')'}`).join(', ');
129};
130helmet.originAgentCluster = (headers, config)=>{
131 headers['Origin-Agent-Cluster'] = '?1';
132};
133helmet.referrerPolicy = (headers, config)=>{
134 headers['Referrer-Policy'] = config.policy;
135};
136helmetHtmlOnly.reportTo = (headers, config)=>{
137 headers['Report-To'] = Object.keys(config).map((group)=>{
138 const includeSubdomains = group === 'default' ? `, "include_subdomains": ${config.includeSubdomains}` : '';
139 return config[group] && group !== 'includeSubdomains' ? `{ "group": "default", "max_age": ${config.maxAge}, "endpoints": [ { "url": "${config[group]}" } ]${includeSubdomains} }` : '';
140 }).filter((str)=>str).join(', ');
141};
142helmet.strictTransportSecurity = (headers, config)=>{
143 let header = 'max-age=' + Math.round(config.maxAge);
144 if (config.includeSubDomains) {
145 header += '; includeSubDomains';
146 }
147 if (config.preload) {
148 header += '; preload';
149 }
150 headers['Strict-Transport-Security'] = header;
151};
152helmet.contentTypeOptions = (headers, config)=>{
153 headers['X-Content-Type-Options'] = config.action;
154};
155helmet.dnsPrefetchControl = (headers, config)=>{
156 headers['X-DNS-Prefetch-Control'] = config.allow ? 'on' : 'off';
157};
158helmet.downloadOptions = (headers, config)=>{
159 headers['X-Download-Options'] = config.action;
160};
161helmetHtmlOnly.frameOptions = (headers, config)=>{
162 headers['X-Frame-Options'] = config.action.toUpperCase();
163};
164helmet.permittedCrossDomainPolicies = (headers, config)=>{
165 headers['X-Permitted-Cross-Domain-Policies'] = config.policy;
166};
167helmet.poweredBy = (headers, config)=>{
168 if (config.server) {
169 headers['X-Powered-By'] = config.server;
170 } else {
171 delete headers.Server;
172 delete headers['X-Powered-By'];
173 }
174};
175helmetHtmlOnly.xssProtection = (headers, config)=>{
176 let header = '1; mode=block';
177 if (config.reportTo) {
178 header += '; report=' + config.reportTo;
179 }
180 headers['X-XSS-Protection'] = header;
181};
182const httpSecurityHeadersMiddleware = (opts = {})=>{
183 const options = {
184 ...defaults,
185 ...opts
186 };
187 const httpSecurityHeadersMiddlewareAfter = async (request)=>{
188 normalizeHttpResponse(request);
189 Object.keys(helmet).forEach((key)=>{
190 if (!options[key]) return;
191 const config = {
192 ...defaults[key],
193 ...options[key]
194 };
195 helmet[key](request.response.headers, config);
196 });
197 if (request.response.headers['Content-Type']?.includes('text/html')) {
198 Object.keys(helmetHtmlOnly).forEach((key)=>{
199 if (!options[key]) return;
200 const config = {
201 ...defaults[key],
202 ...options[key]
203 };
204 helmetHtmlOnly[key](request.response.headers, config);
205 });
206 }
207 };
208 const httpSecurityHeadersMiddlewareOnError = async (request)=>{
209 if (request.response === undefined) return;
210 httpSecurityHeadersMiddlewareAfter(request);
211 };
212 return {
213 after: httpSecurityHeadersMiddlewareAfter,
214 onError: httpSecurityHeadersMiddlewareOnError
215 };
216};
217export default httpSecurityHeadersMiddleware;
218