@contrast/assess 1.1.0 → 1.2.0

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 (72) hide show
  1. package/lib/dataflow/propagation/index.js +6 -7
  2. package/lib/dataflow/propagation/install/contrast-methods/index.js +1 -0
  3. package/lib/dataflow/propagation/install/contrast-methods/string.js +56 -0
  4. package/lib/dataflow/propagation/install/string/index.js +1 -0
  5. package/lib/dataflow/propagation/install/string/split.js +108 -0
  6. package/lib/dataflow/sources/index.js +0 -1
  7. package/lib/dataflow/sources/install/http.js +9 -6
  8. package/lib/index.js +7 -2
  9. package/lib/response-scanning/handlers/index.js +274 -0
  10. package/lib/response-scanning/handlers/utils.js +394 -0
  11. package/lib/response-scanning/index.js +36 -0
  12. package/lib/response-scanning/install/http.js +119 -0
  13. package/package.json +3 -3
  14. package/coverage/lcov-report/base.css +0 -224
  15. package/coverage/lcov-report/block-navigation.js +0 -87
  16. package/coverage/lcov-report/favicon.png +0 -0
  17. package/coverage/lcov-report/index.html +0 -266
  18. package/coverage/lcov-report/lib/dataflow/common.js.html +0 -154
  19. package/coverage/lcov-report/lib/dataflow/event-factory.js.html +0 -598
  20. package/coverage/lcov-report/lib/dataflow/index.html +0 -191
  21. package/coverage/lcov-report/lib/dataflow/index.js.html +0 -190
  22. package/coverage/lcov-report/lib/dataflow/propagation/common.js.html +0 -145
  23. package/coverage/lcov-report/lib/dataflow/propagation/index.html +0 -131
  24. package/coverage/lcov-report/lib/dataflow/propagation/index.js.html +0 -190
  25. package/coverage/lcov-report/lib/dataflow/propagation/install/contrast-methods/index.html +0 -131
  26. package/coverage/lcov-report/lib/dataflow/propagation/install/contrast-methods/index.js.html +0 -184
  27. package/coverage/lcov-report/lib/dataflow/propagation/install/contrast-methods/plus.js.html +0 -397
  28. package/coverage/lcov-report/lib/dataflow/propagation/install/string/concat.js.html +0 -478
  29. package/coverage/lcov-report/lib/dataflow/propagation/install/string/index.html +0 -176
  30. package/coverage/lcov-report/lib/dataflow/propagation/install/string/index.js.html +0 -202
  31. package/coverage/lcov-report/lib/dataflow/propagation/install/string/replace.js.html +0 -496
  32. package/coverage/lcov-report/lib/dataflow/propagation/install/string/substring.js.html +0 -415
  33. package/coverage/lcov-report/lib/dataflow/propagation/install/string/trim.js.html +0 -403
  34. package/coverage/lcov-report/lib/dataflow/signatures.js.html +0 -5923
  35. package/coverage/lcov-report/lib/dataflow/sinks/common.js.html +0 -145
  36. package/coverage/lcov-report/lib/dataflow/sinks/index.html +0 -131
  37. package/coverage/lcov-report/lib/dataflow/sinks/index.js.html +0 -211
  38. package/coverage/lcov-report/lib/dataflow/sinks/install/fastify/index.html +0 -146
  39. package/coverage/lcov-report/lib/dataflow/sinks/install/fastify/index.js.html +0 -172
  40. package/coverage/lcov-report/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js.html +0 -319
  41. package/coverage/lcov-report/lib/dataflow/sinks/install/fastify/xss.js.html +0 -721
  42. package/coverage/lcov-report/lib/dataflow/sinks/install/http.js.html +0 -340
  43. package/coverage/lcov-report/lib/dataflow/sinks/install/index.html +0 -116
  44. package/coverage/lcov-report/lib/dataflow/sources/common.js.html +0 -145
  45. package/coverage/lcov-report/lib/dataflow/sources/index.html +0 -131
  46. package/coverage/lcov-report/lib/dataflow/sources/index.js.html +0 -226
  47. package/coverage/lcov-report/lib/dataflow/sources/install/fastify.js.html +0 -379
  48. package/coverage/lcov-report/lib/dataflow/sources/install/http.js.html +0 -502
  49. package/coverage/lcov-report/lib/dataflow/sources/install/index.html +0 -146
  50. package/coverage/lcov-report/lib/dataflow/sources/install/qs.js.html +0 -322
  51. package/coverage/lcov-report/lib/dataflow/tag-utils.js.html +0 -418
  52. package/coverage/lcov-report/lib/dataflow/tracker.js.html +0 -466
  53. package/coverage/lcov-report/lib/dataflow/utils/index.html +0 -116
  54. package/coverage/lcov-report/lib/dataflow/utils/is-vulnerable.js.html +0 -424
  55. package/coverage/lcov-report/lib/index.html +0 -116
  56. package/coverage/lcov-report/lib/index.js.html +0 -193
  57. package/coverage/lcov-report/prettify.css +0 -1
  58. package/coverage/lcov-report/prettify.js +0 -2
  59. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  60. package/coverage/lcov-report/sorter.js +0 -196
  61. package/coverage/lcov.info +0 -4856
  62. package/coverage/tmp/coverage-9548-1675168551025-0.json +0 -1
  63. package/coverage/tmp/coverage-9551-1675168550963-0.json +0 -1
  64. package/coverage/tmp/coverage-9552-1675168550969-0.json +0 -1
  65. package/coverage/tmp/coverage-9553-1675168550970-0.json +0 -1
  66. package/coverage/tmp/coverage-9554-1675168550962-0.json +0 -1
  67. package/coverage/tmp/coverage-9555-1675168550965-0.json +0 -1
  68. package/coverage/tmp/coverage-9556-1675168550964-0.json +0 -1
  69. package/coverage/tmp/coverage-9557-1675168550969-0.json +0 -1
  70. package/coverage/tmp/coverage-9558-1675168550964-0.json +0 -1
  71. package/coverage/tmp/coverage-9559-1675168550971-0.json +0 -1
  72. package/lib/dataflow/sources/install/qs.js +0 -88
@@ -0,0 +1,394 @@
1
+ /*
2
+ * Copyright: 2022 Contrast Security, Inc
3
+ * Contact: support@contrastsecurity.com
4
+ * License: Commercial
5
+
6
+ * NOTICE: This Software and the patented inventions embodied within may only be
7
+ * used as part of Contrast Security’s commercial offerings. Even though it is
8
+ * made available through public repositories, use of this Software is subject to
9
+ * the applicable End User Licensing Agreement found at
10
+ * https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
11
+ * between Contrast Security and the End User. The Software may not be reverse
12
+ * engineered, modified, repackaged, sold, redistributed or otherwise used in a
13
+ * way not consistent with the End User License Agreement.
14
+ */
15
+
16
+ 'use strict';
17
+
18
+ const { join, substring, toLowerCase, split, trim } = require('@contrast/common');
19
+
20
+ //
21
+ // General HTML utils
22
+ //
23
+ const htmlEscapes = {
24
+ '&': '&',
25
+ '<': '&lt;',
26
+ '>': '&gt;',
27
+ '"': '&quot;',
28
+ "'": '&#39;'
29
+ };
30
+ const reUnescapedHtml = /[&<>"']/g;
31
+ const reHasUnescapedHtml = RegExp(reUnescapedHtml.source);
32
+
33
+ function escapeHtml(string) {
34
+ return (string && reHasUnescapedHtml.test(string))
35
+ ? string.replace(reUnescapedHtml, (chr) => htmlEscapes[chr])
36
+ : (string || '');
37
+ }
38
+
39
+ function getHtmlTagRange(htmlTag, content, offset) {
40
+ const htmlTagStart = content.indexOf(`<${htmlTag}`, offset);
41
+ if (htmlTagStart < 0) return [-1, 0];
42
+
43
+ const htmlTagEnd = content.indexOf('>', htmlTagStart);
44
+ return [htmlTagStart, htmlTagEnd - htmlTagStart + 1];
45
+ }
46
+
47
+ function getElements(htmlTag, content = '') {
48
+ const elements = [];
49
+ let offset = 0;
50
+ let htmlTagRangeStart = -1;
51
+ let htmlTagRangeLength = 0;
52
+
53
+ do {
54
+ [htmlTagRangeStart, htmlTagRangeLength] = getHtmlTagRange(htmlTag, content, offset);
55
+ if (htmlTagRangeStart >= 0) {
56
+ offset = htmlTagRangeStart + htmlTagRangeLength;
57
+ elements.push(substring(content, htmlTagRangeStart, offset));
58
+ }
59
+ } while (htmlTagRangeStart >= 0);
60
+
61
+ return elements;
62
+ }
63
+
64
+ function isHtmlContent(responseHeaders) {
65
+ if (!responseHeaders) {
66
+ // this should never happen, but better safe than sorry;
67
+ // worst case, we parse image data or something
68
+ return true;
69
+ }
70
+
71
+ // we may want to do a case-insensitive search through object keys here
72
+ const contentType = toLowerCase(responseHeaders['Content-Type'] || responseHeaders['content-type'] || '');
73
+
74
+ return (
75
+ !contentType ||
76
+ contentType.includes('text') ||
77
+ contentType.includes('html')
78
+ );
79
+ }
80
+
81
+ function isParseableResponse(responseHeaders) {
82
+ if (!responseHeaders) {
83
+ // this should never happen, but better safe than sorry;
84
+ // worst case, we parse image data or something
85
+ return true;
86
+ }
87
+
88
+ // we may want to do a case-insensitive search through object keys here
89
+ let contentType =
90
+ responseHeaders['Content-Type'] || responseHeaders['content-type'];
91
+ if (contentType) contentType = toLowerCase(contentType);
92
+
93
+ return (
94
+ !contentType ||
95
+ contentType.includes('text') ||
96
+ contentType.includes('html') ||
97
+ contentType.includes('xml') ||
98
+ contentType.includes('json') ||
99
+ contentType.includes('soap') ||
100
+ contentType.includes('pdf')
101
+ );
102
+ }
103
+
104
+ function getAttribute(attribute, htmlTag = '') {
105
+ let attrStart = htmlTag.indexOf(`${attribute}=`);
106
+ if (attrStart === -1) return undefined;
107
+
108
+ attrStart += attribute.length + 1;
109
+ const quoteChar = htmlTag[attrStart];
110
+ if (quoteChar === ' ') return undefined;
111
+
112
+ let attrEnd = -1;
113
+ if (quoteChar === '"' || quoteChar === "'") {
114
+ attrStart += 1;
115
+ attrEnd = htmlTag.indexOf(quoteChar, attrStart);
116
+ } else {
117
+ // handle unquoted <form method=post >
118
+ attrEnd = htmlTag.indexOf(' ', attrStart);
119
+
120
+ // handle unquoted and last attribute <form method=post>
121
+ if (attrEnd === -1) {
122
+ attrEnd = htmlTag[htmlTag.length - 2] === '/' ? htmlTag.length - 2 : htmlTag.length - 1;
123
+ }
124
+ }
125
+
126
+ return substring(htmlTag, attrStart, attrEnd);
127
+ }
128
+
129
+ /**
130
+ * Checks if http-equiv is one of the following
131
+ * below
132
+ *
133
+ * @param {String} httpequiv value of http-equiv meta attr
134
+ */
135
+ function applicableHttpEquiv(httpequiv) {
136
+ return (
137
+ httpequiv == 'cache-control' ||
138
+ httpequiv == 'pragma' ||
139
+ httpequiv == 'expires'
140
+ );
141
+ }
142
+ /**
143
+ * Checks every meta html tag from body for http-equiv attributes.
144
+ * If attr exists it will check the content attr if http-equiv is cache-control
145
+ * Otherwise it will add value of attr if cache-control, pragma or expires
146
+ */
147
+ function checkMetaTags({ body, cacheControlHeader, instructions }) {
148
+ const metaTags = getElements('meta', body);
149
+ metaTags.forEach((tag) => {
150
+ const httpequiv = getAttribute('http-equiv', tag);
151
+ if (httpequiv) {
152
+ const content = getAttribute('content', tag);
153
+ if (httpequiv == 'cache-control') {
154
+ const [
155
+ containsMetaNoCache,
156
+ containsMetaNoStore
157
+ ] = checkCacheControlValue(content);
158
+ if (containsMetaNoCache && containsMetaNoStore) {
159
+ return;
160
+ }
161
+
162
+ const [containsNoCache, containsNoStore] = checkCacheControlValue(
163
+ cacheControlHeader
164
+ );
165
+
166
+ // got no-store from headers and no-cache from meta
167
+ // or no-cache from headers and no-store from meta
168
+ if (
169
+ (containsNoStore && containsMetaNoCache) ||
170
+ (containsNoCache && containsMetaNoStore)
171
+ ) {
172
+ return;
173
+ }
174
+ }
175
+
176
+ // if we make it this far push the values into instructions
177
+ if (applicableHttpEquiv(httpequiv)) {
178
+ instructions.push({
179
+ type: 'META tag',
180
+ name: httpequiv,
181
+ value: tag
182
+ });
183
+ }
184
+ }
185
+ });
186
+ }
187
+
188
+
189
+ //
190
+ // Utils regarding Content Security Policy headers rules
191
+ //
192
+
193
+ /**
194
+ * Terminology for Content Security Policies
195
+ * See https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
196
+ * csp - Content Security Policy
197
+ * policy - the entire csp header value
198
+ * directive - a specific key of the policy(e.g default-src, media-src)
199
+ * sources - the value(s) of the directive(default-src foobar.com;)
200
+ */
201
+
202
+ /**
203
+ * Checks the value of cache-control
204
+ * either header or meta content attr value
205
+ * for if it contains no-cache or no-store
206
+ *
207
+ * @param {String} value value of cache-control
208
+ * @return {Array} [no-cache present, no-store present]
209
+ */
210
+ function checkCacheControlValue(value = '') {
211
+ if (Array.isArray(value)) {
212
+ let noCache = false;
213
+ let noStore = false;
214
+
215
+ value.forEach((directive) => {
216
+ if (toLowerCase(directive).includes('no-cache')) {
217
+ noCache = true;
218
+ }
219
+ if (toLowerCase(directive).includes('no-store')) {
220
+ noStore = true;
221
+ }
222
+ });
223
+ return [noCache, noStore];
224
+ } else {
225
+ value = toLowerCase(value);
226
+ return [value.includes('no-cache'), value.includes('no-store')];
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Special evaluator for any -src directives
232
+ * If it is empty and default is secure then it is secure
233
+ *
234
+ * @param {Array} sources sources for a given -src directive
235
+ * @param {Array} defaultSources sources for the default-src directive
236
+ * @return {Boolean}
237
+ */
238
+ function isSrcSecure(sources, defaultSources) {
239
+ return sources.length === 0
240
+ ? isSourceSecure(defaultSources)
241
+ : isSourceSecure(sources);
242
+ }
243
+
244
+ /**
245
+ * Checks if a given source(s) for a directive is defined and is secure(does not contain a '*')
246
+ *
247
+ * @param {Array} sources sources for a given csp directive
248
+ * @return {Boolean} whether a directive is secure
249
+ */
250
+ function isSourceSecure(sources) {
251
+ return (
252
+ sources.length > 0 &&
253
+ sources.every((source) => !!source && !/\*/.test(source))
254
+ );
255
+ }
256
+
257
+ /**
258
+ * Evaluator for reflected-xss directive. Checks if the value is not
259
+ * equal to 1
260
+ * Note: If empty it is secure
261
+ * @param {Array} sources sources for a given csp directive
262
+ * @return {Boolean} whether a directive is secure
263
+ */
264
+ function xssCheck(sources) {
265
+ return sources.every((source) => parseInt(source) === 1);
266
+ }
267
+
268
+ /**
269
+ * Evaluator for referrer directive. Checks if value is not *
270
+ * or contains unsafe-url
271
+ * @param {Array} sources sources for a given csp directive
272
+ * @return {Boolean} whether a directive is secure
273
+ */
274
+ function referrerCheck(sources) {
275
+ return sources.every((source) => !/unsafe-url|\*/.test(source));
276
+ }
277
+
278
+ const KNOWN_DIRECTIVES = [
279
+ { name: 'default-src', camelCasedName: 'defaultSrc', evaluator: isSourceSecure },
280
+ { name: 'base-uri', camelCasedName: 'baseUri', evaluator: isSourceSecure },
281
+ { name: 'child-src', camelCasedName: 'childSrc' },
282
+ { name: 'connect-src', camelCasedName: 'connectSrc' },
283
+ { name: 'frame-src', camelCasedName: 'frameSrc' },
284
+ { name: 'media-src', camelCasedName: 'mediaSrc' },
285
+ { name: 'object-src', camelCasedName: 'objectSrc' },
286
+ { name: 'script-src', camelCasedName: 'scriptSrc' },
287
+ { name: 'style-src', camelCasedName: 'styleSrc' },
288
+ { name: 'form-action', camelCasedName: 'formAction', evaluator: isSourceSecure },
289
+ { name: 'frame-ancestors', camelCasedName: 'frameAncestors', evaluator: isSourceSecure },
290
+ { name: 'plugin-types', camelCasedName: 'pluginTypes', evaluator: isSourceSecure },
291
+ { name: 'reflected-xss', camelCasedName: 'reflectedXss', evaluator: xssCheck },
292
+ { name: 'referrer', evaluator: referrerCheck }
293
+ ];
294
+
295
+ /**
296
+ * Convenience method for formatting a given directive's
297
+ * secure and value properties. It also camel cases the key
298
+ * to match the spec for the rule's finding
299
+ *
300
+ * @param {Object} params
301
+ * @param {Object} params.data obj to store the full props object
302
+ * @param {String} params.key the csp header directive
303
+ * @param {Array} params.value array of sources for the directive
304
+ * @param {Boolean} params.isSecure flag indicating if directive is secure
305
+ */
306
+ function formatSource({
307
+ data,
308
+ key,
309
+ sources = [],
310
+ defaultSources = [],
311
+ evaluator = isSrcSecure,
312
+ }) {
313
+ const isSecure = evaluator(sources, defaultSources);
314
+
315
+ if (!isSecure && !data.insecure) {
316
+ data.insecure = true;
317
+ }
318
+
319
+ data[`${key}Secure`] = isSecure;
320
+ data[`${key}Value`] = join(sources, ' ');
321
+ }
322
+
323
+ /**
324
+ * This will take a Content Security Policy string and parse it
325
+ */
326
+ function policyParser(policy) {
327
+ const result = {};
328
+ for (const directive of split(policy, ';')) {
329
+ const [directiveKey, ...directiveValue] = split(trim(directive), /\s+/g);
330
+ if (
331
+ directiveKey &&
332
+ !Object.prototype.hasOwnProperty.call(result, directiveKey)
333
+ ) {
334
+ result[directiveKey] = directiveValue;
335
+ }
336
+ }
337
+ return result;
338
+ }
339
+
340
+ /**
341
+ * This will parse a CSP header value into an object
342
+ * It will then iterate over all known keys and build
343
+ * which are secure and which aren't with the values
344
+ * for each. If a directive is undefined, it assumed
345
+ * insecure and will provide '' as the value
346
+ *
347
+ * See: https://contrast.atlassian.net/wiki/spaces/ENG/pages/805503460/Content-Security-Policy+Header+Misconfigured
348
+ */
349
+ function checkCspSources(policy) {
350
+ const csp = policyParser(policy);
351
+ const data = {};
352
+
353
+ KNOWN_DIRECTIVES.forEach(({ name, evaluator, camelCasedName }) => {
354
+ const sources = csp[name];
355
+ formatSource({
356
+ data,
357
+ defaultSources: csp['default-src'],
358
+ key: camelCasedName || name,
359
+ sources,
360
+ evaluator,
361
+ });
362
+ });
363
+
364
+ return data;
365
+ }
366
+
367
+ /**
368
+ * Extracts the Content Security Policy from headers
369
+ * in one of the CSP_HEADERS constants
370
+ *
371
+ * @param {Object} responseHeaders
372
+ * @return {*} the csp header value or false(if not present)
373
+ */
374
+ function getCspHeaders(responseHeaders) {
375
+ const CSP_HEADERS = [
376
+ 'content-security-policy',
377
+ 'x-content-security-policy',
378
+ 'x-webkit-csp'
379
+ ];
380
+ const headerName = CSP_HEADERS.filter((header) => responseHeaders[header]);
381
+ return headerName.length ? responseHeaders[headerName[0]] : false;
382
+ }
383
+
384
+ module.exports = {
385
+ escapeHtml,
386
+ isHtmlContent,
387
+ getElements,
388
+ getAttribute,
389
+ isParseableResponse,
390
+ checkCacheControlValue,
391
+ checkMetaTags,
392
+ getCspHeaders,
393
+ checkCspSources
394
+ };
@@ -0,0 +1,36 @@
1
+ /*
2
+ * Copyright: 2022 Contrast Security, Inc
3
+ * Contact: support@contrastsecurity.com
4
+ * License: Commercial
5
+
6
+ * NOTICE: This Software and the patented inventions embodied within may only be
7
+ * used as part of Contrast Security’s commercial offerings. Even though it is
8
+ * made available through public repositories, use of this Software is subject to
9
+ * the applicable End User Licensing Agreement found at
10
+ * https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
11
+ * between Contrast Security and the End User. The Software may not be reverse
12
+ * engineered, modified, repackaged, sold, redistributed or otherwise used in a
13
+ * way not consistent with the End User License Agreement.
14
+ */
15
+
16
+ 'use strict';
17
+
18
+ const { callChildComponentMethodsSync, Event } = require('@contrast/common');
19
+
20
+ module.exports = function(core) {
21
+ const { messages } = core;
22
+ const responseScanning = core.assess.responseScanning = {
23
+ reportFindings(_sourceContext, vulnerabilityMetadata) {
24
+ messages.emit(Event.ASSESS_RESPONSE_SCANNING_FINDING, vulnerabilityMetadata);
25
+ },
26
+ };
27
+
28
+ require('./handlers')(core);
29
+ require('./install/http')(core);
30
+
31
+ responseScanning.install = function() {
32
+ callChildComponentMethodsSync(responseScanning, 'install');
33
+ };
34
+
35
+ return responseScanning;
36
+ };
@@ -0,0 +1,119 @@
1
+ /*
2
+ * Copyright: 2022 Contrast Security, Inc
3
+ * Contact: support@contrastsecurity.com
4
+ * License: Commercial
5
+
6
+ * NOTICE: This Software and the patented inventions embodied within may only be
7
+ * used as part of Contrast Security’s commercial offerings. Even though it is
8
+ * made available through public repositories, use of this Software is subject to
9
+ * the applicable End User Licensing Agreement found at
10
+ * https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
11
+ * between Contrast Security and the End User. The Software may not be reverse
12
+ * engineered, modified, repackaged, sold, redistributed or otherwise used in a
13
+ * way not consistent with the End User License Agreement.
14
+ */
15
+
16
+ 'use strict';
17
+
18
+ const { split, substring, toLowerCase, trim } = require('@contrast/common');
19
+
20
+ module.exports = function(core) {
21
+ const {
22
+ depHooks,
23
+ patcher,
24
+ scopes: { sources },
25
+ assess: {
26
+ responseScanning: {
27
+ handleAutoCompleteMissing,
28
+ handleCacheControlsMissing,
29
+ handleClickJackingControlsMissing,
30
+ handleParameterPollution,
31
+ handleCspHeader,
32
+ handleHstsHeaderMissing,
33
+ handlePoweredByHeader,
34
+ handleXContentTypeHeaderMissing,
35
+ handleXxsProtectionHeaderDisabled,
36
+ }
37
+ }
38
+ } = core;
39
+ const http = core.assess.responseScanning.httpInstrumentation = {};
40
+
41
+ function parseHeaders(rawHeaders = '') {
42
+ const headersArray = split(rawHeaders, '\r\n').filter(Boolean);
43
+ return headersArray.reduce((acc, header) => {
44
+ const idx = header.indexOf(':');
45
+
46
+ if (idx > -1) {
47
+ const name = toLowerCase(substring(header, 0, idx));
48
+ const value = trim(substring(header, idx + 1));
49
+ const currentValue = acc[name];
50
+ acc[name] = currentValue
51
+ ? Array.isArray(currentValue)
52
+ ? currentValue.push(value) && currentValue
53
+ : [currentValue, value]
54
+ : value;
55
+ }
56
+
57
+ return acc;
58
+ }, {});
59
+ }
60
+
61
+ const patchType = 'response-scanning';
62
+
63
+ http.install = function() {
64
+ depHooks.resolve({ name: 'http' }, (http) => {
65
+ {
66
+ const name = 'http.ServerResponse.prototype.write';
67
+ patcher.patch(http.ServerResponse.prototype, 'write', {
68
+ name,
69
+ patchType,
70
+ post(data) {
71
+ const sourceContext = sources.getStore()?.assess;
72
+ if (!sourceContext) return;
73
+
74
+ const evaluationContext = {
75
+ responseBody: toLowerCase(data.args[0] || ''),
76
+ responseHeaders: parseHeaders(data.result._header),
77
+ };
78
+
79
+ // Check only the rules concerning the response body
80
+ handleAutoCompleteMissing(sourceContext, evaluationContext);
81
+ handleCacheControlsMissing(sourceContext, evaluationContext);
82
+ handleParameterPollution(sourceContext, evaluationContext);
83
+ handleXxsProtectionHeaderDisabled(sourceContext, evaluationContext);
84
+ }
85
+ });
86
+ }
87
+ {
88
+ const name = 'http.ServerResponse.prototype.end';
89
+ patcher.patch(http.ServerResponse.prototype, 'end', {
90
+ name,
91
+ patchType,
92
+ post(data) {
93
+ const sourceContext = sources.getStore()?.assess;
94
+ if (!sourceContext) return;
95
+
96
+ const evaluationContext = {
97
+ responseBody: toLowerCase(data.args[0] || ''),
98
+ responseHeaders: parseHeaders(data.result._header),
99
+ };
100
+
101
+ // Check all the response scanning rules
102
+ handleAutoCompleteMissing(sourceContext, evaluationContext);
103
+ handleCacheControlsMissing(sourceContext, evaluationContext);
104
+ handleClickJackingControlsMissing(sourceContext, evaluationContext);
105
+ handleParameterPollution(sourceContext, evaluationContext);
106
+ handleCspHeader(sourceContext, evaluationContext);
107
+ handleHstsHeaderMissing(sourceContext, evaluationContext);
108
+ handlePoweredByHeader(sourceContext, evaluationContext);
109
+ handleXContentTypeHeaderMissing(sourceContext, evaluationContext);
110
+ handleXxsProtectionHeaderDisabled(sourceContext, evaluationContext);
111
+ }
112
+ });
113
+ }
114
+ });
115
+ };
116
+
117
+ return http;
118
+ };
119
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/assess",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -15,7 +15,7 @@
15
15
  "dependencies": {
16
16
  "@contrast/distringuish": "^4.1.0",
17
17
  "@contrast/scopes": "1.3.0",
18
- "@contrast/common": "1.4.0",
18
+ "@contrast/common": "1.5.0",
19
19
  "parseurl": "^1.3.3"
20
20
  }
21
- }
21
+ }