@contrast/assess 1.30.0 → 1.32.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 (156) hide show
  1. package/lib/crypto-analysis/install/crypto.test.js +146 -0
  2. package/lib/crypto-analysis/install/math.test.js +65 -0
  3. package/lib/dataflow/index.test.js +36 -0
  4. package/lib/dataflow/propagation/index.test.js +103 -0
  5. package/lib/dataflow/propagation/install/JSON/index.test.js +50 -0
  6. package/lib/dataflow/propagation/install/JSON/parse-fn.test.js +232 -0
  7. package/lib/dataflow/propagation/install/JSON/parse.test.js +968 -0
  8. package/lib/dataflow/propagation/install/JSON/stringify.test.js +265 -0
  9. package/lib/dataflow/propagation/install/array-prototype-join.test.js +106 -0
  10. package/lib/dataflow/propagation/install/buffer.test.js +109 -0
  11. package/lib/dataflow/propagation/install/contrast-methods/add.test.js +94 -0
  12. package/lib/dataflow/propagation/install/contrast-methods/index.test.js +49 -0
  13. package/lib/dataflow/propagation/install/contrast-methods/number.test.js +50 -0
  14. package/lib/dataflow/propagation/install/contrast-methods/string.test.js +148 -0
  15. package/lib/dataflow/propagation/install/contrast-methods/tag.test.js +145 -0
  16. package/lib/dataflow/propagation/install/decode-uri-component.test.js +78 -0
  17. package/lib/dataflow/propagation/install/ejs/escape-xml.test.js +69 -0
  18. package/lib/dataflow/propagation/install/ejs/template.test.js +62 -0
  19. package/lib/dataflow/propagation/install/encode-uri.test.js +83 -0
  20. package/lib/dataflow/propagation/install/escape-html.test.js +71 -0
  21. package/lib/dataflow/propagation/install/escape.test.js +73 -0
  22. package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.test.js +71 -0
  23. package/lib/dataflow/propagation/install/isnumeric-0.test.js +58 -0
  24. package/lib/dataflow/propagation/install/joi/any.test.js +270 -0
  25. package/lib/dataflow/propagation/install/joi/array.test.js +912 -0
  26. package/lib/dataflow/propagation/install/joi/boolean.test.js +103 -0
  27. package/lib/dataflow/propagation/install/joi/expression.test.js +76 -0
  28. package/lib/dataflow/propagation/install/joi/index.test.js +39 -0
  29. package/lib/dataflow/propagation/install/joi/number.test.js +103 -0
  30. package/lib/dataflow/propagation/install/joi/object.test.js +119 -0
  31. package/lib/dataflow/propagation/install/joi/ref.test.js +607 -0
  32. package/lib/dataflow/propagation/install/joi/string-schema.test.js +513 -0
  33. package/lib/dataflow/propagation/install/mongoose/index.test.js +42 -0
  34. package/lib/dataflow/propagation/install/mongoose/schema-map.test.js +348 -0
  35. package/lib/dataflow/propagation/install/mongoose/schema-mixed.test.js +512 -0
  36. package/lib/dataflow/propagation/install/mongoose/schema-string.test.js +160 -0
  37. package/lib/dataflow/propagation/install/mustache-escape.test.js +62 -0
  38. package/lib/dataflow/propagation/install/mysql-connection-escape.test.js +74 -0
  39. package/lib/dataflow/propagation/install/parse-int.test.js +48 -0
  40. package/lib/dataflow/propagation/install/path/basename.test.js +143 -0
  41. package/lib/dataflow/propagation/install/path/dirname.test.js +167 -0
  42. package/lib/dataflow/propagation/install/path/extname.test.js +141 -0
  43. package/lib/dataflow/propagation/install/path/format.test.js +250 -0
  44. package/lib/dataflow/propagation/install/path/index.test.js +45 -0
  45. package/lib/dataflow/propagation/install/path/join-and-resolve.test.js +485 -0
  46. package/lib/dataflow/propagation/install/path/normalize.test.js +176 -0
  47. package/lib/dataflow/propagation/install/path/parse.test.js +238 -0
  48. package/lib/dataflow/propagation/install/path/relative.test.js +239 -0
  49. package/lib/dataflow/propagation/install/path/toNamespacedPath.test.js +158 -0
  50. package/lib/dataflow/propagation/install/pug/index.test.js +55 -0
  51. package/lib/dataflow/propagation/install/pug-runtime-escape.test.js +69 -0
  52. package/lib/dataflow/propagation/install/querystring/escape.test.js +63 -0
  53. package/lib/dataflow/propagation/install/querystring/index.test.js +40 -0
  54. package/lib/dataflow/propagation/install/querystring/parse.test.js +272 -0
  55. package/lib/dataflow/propagation/install/querystring/stringify.test.js +301 -0
  56. package/lib/dataflow/propagation/install/reg-exp-prototype-exec.test.js +281 -0
  57. package/lib/dataflow/propagation/install/send.test.js +63 -0
  58. package/lib/dataflow/propagation/install/sequelize/query-generator.test.js +73 -0
  59. package/lib/dataflow/propagation/install/sequelize/sql-string.test.js +130 -0
  60. package/lib/dataflow/propagation/install/sql-template-strings.test.js +100 -0
  61. package/lib/dataflow/propagation/install/string/concat.test.js +132 -0
  62. package/lib/dataflow/propagation/install/string/format-methods.test.js +61 -0
  63. package/lib/dataflow/propagation/install/string/html-methods.test.js +164 -0
  64. package/lib/dataflow/propagation/install/string/index.test.js +103 -0
  65. package/lib/dataflow/propagation/install/string/match-all.test.js +399 -0
  66. package/lib/dataflow/propagation/install/string/match.test.js +361 -0
  67. package/lib/dataflow/propagation/install/string/replace.test.js +588 -0
  68. package/lib/dataflow/propagation/install/string/slice.test.js +265 -0
  69. package/lib/dataflow/propagation/install/string/split.test.js +500 -0
  70. package/lib/dataflow/propagation/install/string/substring.test.js +238 -0
  71. package/lib/dataflow/propagation/install/string/trim.test.js +122 -0
  72. package/lib/dataflow/propagation/install/unescape.test.js +78 -0
  73. package/lib/dataflow/propagation/install/url/domain-parsers.test.js +63 -0
  74. package/lib/dataflow/propagation/install/url/parse.test.js +391 -0
  75. package/lib/dataflow/propagation/install/url/searchParams.test.js +538 -0
  76. package/lib/dataflow/propagation/install/url/url.test.js +466 -0
  77. package/lib/dataflow/propagation/install/util-format.test.js +336 -0
  78. package/lib/dataflow/propagation/install/validator/hooks.test.js +211 -0
  79. package/lib/dataflow/sinks/index.test.js +78 -0
  80. package/lib/dataflow/sinks/install/child-process.test.js +338 -0
  81. package/lib/dataflow/sinks/install/eval.test.js +95 -0
  82. package/lib/dataflow/sinks/install/express/index.test.js +33 -0
  83. package/lib/dataflow/sinks/install/express/reflected-xss.js +55 -57
  84. package/lib/dataflow/sinks/install/express/reflected-xss.test.js +109 -0
  85. package/lib/dataflow/sinks/install/express/unvalidated-redirect.test.js +144 -0
  86. package/lib/dataflow/sinks/install/fastify/index.test.js +32 -0
  87. package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.test.js +130 -0
  88. package/lib/dataflow/sinks/install/fs.test.js +138 -0
  89. package/lib/dataflow/sinks/install/function.test.js +103 -0
  90. package/lib/dataflow/sinks/install/hapi/index.test.js +32 -0
  91. package/lib/dataflow/sinks/install/hapi/unvalidated-redirect.test.js +130 -0
  92. package/lib/dataflow/sinks/install/http/index.test.js +33 -0
  93. package/lib/dataflow/sinks/install/http/request.test.js +184 -0
  94. package/lib/dataflow/sinks/install/http/server-response.test.js +162 -0
  95. package/lib/dataflow/sinks/install/koa/index.test.js +32 -0
  96. package/lib/dataflow/sinks/install/koa/unvalidated-redirect.test.js +200 -0
  97. package/lib/dataflow/sinks/install/libxmljs.test.js +158 -0
  98. package/lib/dataflow/sinks/install/marsdb.test.js +166 -0
  99. package/lib/dataflow/sinks/install/mongodb.test.js +621 -0
  100. package/lib/dataflow/sinks/install/mssql.test.js +136 -0
  101. package/lib/dataflow/sinks/install/mysql.test.js +233 -0
  102. package/lib/dataflow/sinks/install/node-serialize.test.js +85 -0
  103. package/lib/dataflow/sinks/install/postgres.test.js +158 -0
  104. package/lib/dataflow/sinks/install/restify.test.js +142 -0
  105. package/lib/dataflow/sinks/install/sequelize.test.js +100 -0
  106. package/lib/dataflow/sinks/install/sqlite3.test.js +118 -0
  107. package/lib/dataflow/sinks/install/vm.test.js +326 -0
  108. package/lib/dataflow/sources/handler.test.js +463 -0
  109. package/lib/dataflow/sources/index.test.js +58 -0
  110. package/lib/dataflow/sources/install/body-parser1.test.js +248 -0
  111. package/lib/dataflow/sources/install/busboy.test.js +152 -0
  112. package/lib/dataflow/sources/install/cookie-parser1.test.js +143 -0
  113. package/lib/dataflow/sources/install/express/params.test.js +105 -0
  114. package/lib/dataflow/sources/install/express/parsedUrl.test.js +65 -0
  115. package/lib/dataflow/sources/install/fastify/fastify.js +1 -1
  116. package/lib/dataflow/sources/install/fastify/fastify.test.js +210 -0
  117. package/lib/dataflow/sources/install/fastify/index.test.js +33 -0
  118. package/lib/dataflow/sources/install/formidable1.test.js +119 -0
  119. package/lib/dataflow/sources/install/hapi/hapi.test.js +172 -0
  120. package/lib/dataflow/sources/install/hapi/index.test.js +33 -0
  121. package/lib/dataflow/sources/install/http.test.js +155 -0
  122. package/lib/dataflow/sources/install/koa/index.test.js +40 -0
  123. package/lib/dataflow/sources/install/koa/koa-bodyparsers.test.js +161 -0
  124. package/lib/dataflow/sources/install/koa/koa-multer.test.js +197 -0
  125. package/lib/dataflow/sources/install/koa/koa-routers.test.js +146 -0
  126. package/lib/dataflow/sources/install/koa/koa2.test.js +145 -0
  127. package/lib/dataflow/sources/install/multer1.test.js +145 -0
  128. package/lib/dataflow/sources/install/qs6.test.js +131 -0
  129. package/lib/dataflow/sources/install/querystring.test.js +82 -0
  130. package/lib/dataflow/sources/install/restify/fieldedTextBodyParser.test.js +88 -0
  131. package/lib/dataflow/sources/install/restify/index.test.js +38 -0
  132. package/lib/dataflow/sources/install/restify/jsonBodyParser.test.js +144 -0
  133. package/lib/dataflow/sources/install/restify/router.test.js +83 -0
  134. package/lib/dataflow/tag-utils-complete.test.js +27 -0
  135. package/lib/dataflow/tag-utils.test.js +192 -0
  136. package/lib/dataflow/tracker.test.js +216 -0
  137. package/lib/dataflow/utils/is-safe-content-type.test.js +16 -0
  138. package/lib/dataflow/utils/is-vulnerable.test.js +115 -0
  139. package/lib/event-factory.test.js +321 -0
  140. package/lib/get-policy.test.js +194 -0
  141. package/lib/get-source-context.test.js +108 -0
  142. package/lib/index.test.js +41 -0
  143. package/lib/make-source-context.test.js +50 -0
  144. package/lib/response-scanning/handlers/index.test.js +425 -0
  145. package/lib/response-scanning/handlers/utils.test.js +391 -0
  146. package/lib/response-scanning/index.test.js +41 -0
  147. package/lib/response-scanning/install/http.test.js +175 -0
  148. package/lib/rule-scopes.test.js +27 -0
  149. package/lib/session-configuration/handlers.test.js +84 -0
  150. package/lib/session-configuration/index.test.js +36 -0
  151. package/lib/session-configuration/install/express-session.test.js +220 -0
  152. package/lib/session-configuration/install/fastify-cookie.test.js +65 -0
  153. package/lib/session-configuration/install/hapi.test.js +269 -0
  154. package/lib/session-configuration/install/koa.js +50 -44
  155. package/lib/session-configuration/install/koa.test.js +92 -0
  156. package/package.json +2 -2
@@ -0,0 +1,391 @@
1
+ 'use strict';
2
+
3
+ const { expect } = require('chai');
4
+ const {
5
+ escapeHtml,
6
+ isHtmlContent,
7
+ getElements,
8
+ getAttribute,
9
+ isParseableResponse,
10
+ checkCacheControlValue,
11
+ checkMetaTags,
12
+ getCspHeaders,
13
+ checkCspSources
14
+ } = require('./utils');
15
+
16
+ describe('assess response scanning handlers utils', function() {
17
+ describe('escapeHtml', function() {
18
+ it('returns the original string when there are no characters to be escaped', function() {
19
+ expect(escapeHtml('test')).to.equal('test');
20
+ });
21
+
22
+ it('returns empty string when called with falsy value', function() {
23
+ expect(escapeHtml()).to.equal('');
24
+ });
25
+
26
+ it('retrurns the string with escaped characters where necessary', function() {
27
+ expect(escapeHtml('<input type="text">Test&Go<input type=\'text\'>'))
28
+ .to.equal('&lt;input type=&quot;text&quot;&gt;Test&amp;Go&lt;input type=&#39;text&#39;&gt;');
29
+ });
30
+ });
31
+
32
+ describe('isHtmlContent', function() {
33
+ it('returns true if the passed argument is falsy', function() {
34
+ expect(isHtmlContent()).to.be.true;
35
+ expect(isHtmlContent('')).to.be.true;
36
+ });
37
+
38
+ it('returns true if the passed headers does not have specified content type', function() {
39
+ expect(isHtmlContent({ 'content-type': '' })).to.be.true;
40
+ expect(isHtmlContent({ 'test-header': 'some-value' })).to.be.true;
41
+ });
42
+
43
+ it('returns true if the passed headers have the right content type for html content', function() {
44
+ expect(isHtmlContent({ 'content-type': 'text/css' })).to.be.true;
45
+ expect(isHtmlContent({ 'Content-Type': '*/html' })).to.be.true;
46
+ });
47
+ });
48
+
49
+ describe('getElements', function() {
50
+ [
51
+ {
52
+ tag: 'p',
53
+ content: undefined,
54
+ expectedResult: []
55
+ },
56
+ {
57
+ tag: 'p',
58
+ content: '<div><form><input type="text"></form><div>',
59
+ expectedResult: []
60
+ },
61
+ {
62
+ tag: 'input',
63
+ content: '<div><form><input type="text"></form><div>',
64
+ expectedResult: ['<input type="text">']
65
+ },
66
+ {
67
+ tag: 'label',
68
+ content: '<form><label for="fname">First Name</label><label for="lname">Last Name</label><form>',
69
+ expectedResult: ['<label for="fname">', '<label for="lname">']
70
+ },
71
+ ].forEach(({ tag, content, expectedResult }) => {
72
+ it('gets the correct elements', function() {
73
+ expect(getElements(tag, content)).to.deep.equal(expectedResult);
74
+ });
75
+ });
76
+ });
77
+
78
+ describe('getAttribute', function() {
79
+ it('returns undefined if the attribute is not found', function() {
80
+ expect(getAttribute('method', '<label for="fname">')).to.be.undefined;
81
+ });
82
+
83
+ it('returns undefined if there is an empty space after the attr assignment', function() {
84
+ expect(getAttribute('for', '<label for= "fname">')).to.be.undefined;
85
+ });
86
+
87
+ [
88
+ '<label for="fname">',
89
+ "<label for='fname'>",
90
+ '<label for=fname >',
91
+ '<label for=fname>',
92
+ '<label for=fname/>',
93
+ ].forEach((htmlTag) => {
94
+ it('returns the correct attribute', function() {
95
+ expect(getAttribute('for', htmlTag)).to.equal('fname');
96
+ });
97
+ });
98
+ });
99
+
100
+ describe('isParseableResponse', function() {
101
+ it('returns true if the passed argument is falsy', function() {
102
+ expect(isParseableResponse()).to.be.true;
103
+ });
104
+
105
+ [
106
+ {
107
+ responseHeaders: {},
108
+ expectedResult: true
109
+ },
110
+ {
111
+ responseHeaders: {
112
+ 'content-type': 'multipart/form-data'
113
+ },
114
+ expectedResult: false
115
+ },
116
+ {
117
+ responseHeaders: {
118
+ 'Content-Type': 'text/css'
119
+ },
120
+ expectedResult: true
121
+ },
122
+ {
123
+ responseHeaders: {
124
+ 'Content-Type': '*/html'
125
+ },
126
+ expectedResult: true
127
+ },
128
+ {
129
+ responseHeaders: {
130
+ 'Content-Type': 'application/xml'
131
+ },
132
+ expectedResult: true
133
+ },
134
+ {
135
+ responseHeaders: {
136
+ 'Content-Type': 'application/soap'
137
+ },
138
+ expectedResult: true
139
+ },
140
+ {
141
+ responseHeaders: {
142
+ 'Content-Type': 'application/pdf'
143
+ },
144
+ expectedResult: true
145
+ }
146
+ ].forEach(({ responseHeaders, expectedResult }) => {
147
+ it('assess wheter the response is parsable correctly', function() {
148
+ expect(isParseableResponse(responseHeaders)).to.equal(expectedResult);
149
+ });
150
+ });
151
+ });
152
+
153
+ describe('checkCacheControlValue', function() {
154
+ [
155
+ {
156
+ value: 'no-cache; no-store',
157
+ expectedResult: [true, true]
158
+ },
159
+ {
160
+ value: 'no-cache',
161
+ expectedResult: [true, false]
162
+ },
163
+ {
164
+ value: 'no-store',
165
+ expectedResult: [false, true]
166
+ },
167
+ {
168
+ value: 'no-control-value',
169
+ expectedResult: [false, false]
170
+ },
171
+ {
172
+ value: ['no-cache', 'mock-directive', 'no-store'],
173
+ expectedResult: [true, true]
174
+ },
175
+ {
176
+ value: ['mock-directive', 'no-store'],
177
+ expectedResult: [false, true]
178
+ },
179
+ {
180
+ value: ['mock-directive'],
181
+ expectedResult: [false, false]
182
+ },
183
+ {
184
+ value: ['no-cache', 'mock-directive'],
185
+ expectedResult: [true, false]
186
+ },
187
+ ].forEach(({ value, expectedResult }) => {
188
+ it('checks the cache control values correctly', function() {
189
+ expect(checkCacheControlValue(value)).to.deep.equal(expectedResult);
190
+ });
191
+ });
192
+ });
193
+
194
+ describe('checkMetaTags', function() {
195
+ let instructions = [];
196
+
197
+ beforeEach(function() {
198
+ instructions = [];
199
+ });
200
+
201
+ [
202
+ {
203
+ body: '<meta http-equiv="cache-control">',
204
+ cacheControlHeader: '',
205
+ expectedInstructions: [{ name: 'cache-control', type: 'META tag', value: '<meta http-equiv="cache-control">' }]
206
+ },
207
+ {
208
+ body: '<meta http-equiv="pragma">',
209
+ cacheControlHeader: '',
210
+ expectedInstructions: [{ name: 'pragma', type: 'META tag', value: '<meta http-equiv="pragma">' }]
211
+ },
212
+ {
213
+ body: '<meta http-equiv="expires">',
214
+ cacheControlHeader: '',
215
+ expectedInstructions: [{ name: 'expires', type: 'META tag', value: '<meta http-equiv="expires">' }]
216
+ },
217
+ {
218
+ body: '<meta http-equiv="cache-control" content="no-cache; no-store">',
219
+ cacheControlHeader: '',
220
+ expectedInstructions: []
221
+ },
222
+ {
223
+ body: '<meta http-equiv="cache-control" content="no-store">',
224
+ cacheControlHeader: 'no-cache',
225
+ expectedInstructions: []
226
+ },
227
+ {
228
+ body: '<meta http-equiv="cache-control" content="no-cache">',
229
+ cacheControlHeader: 'no-store',
230
+ expectedInstructions: []
231
+ },
232
+ ].forEach(({ body, cacheControlHeader, expectedInstructions }) => {
233
+ it('checks the meta tags and adds metadata to instructions if needed', function() {
234
+ checkMetaTags({ body, cacheControlHeader, instructions });
235
+
236
+ expect(instructions).to.deep.equal(expectedInstructions);
237
+ });
238
+ });
239
+ });
240
+
241
+ describe('getCspHeaders', function() {
242
+ [
243
+ {
244
+ responseHeaders: {},
245
+ expectedResult: false
246
+ },
247
+ {
248
+ responseHeaders: {
249
+ 'some-header': 'some-value',
250
+ 'content-security-policy': 'default-src "self"'
251
+ },
252
+ expectedResult: 'default-src "self"'
253
+ },
254
+ {
255
+ responseHeaders: {
256
+ 'some-header': 'some-value',
257
+ },
258
+ expectedResult: false
259
+ },
260
+ {
261
+ responseHeaders: {
262
+ 'some-header': 'some-value',
263
+ 'x-content-security-policy': 'default-src "self"'
264
+ },
265
+ expectedResult: 'default-src "self"'
266
+ },
267
+ {
268
+ responseHeaders: {
269
+ 'some-header': 'some-value',
270
+ 'x-webkit-csp': 'default-src "self"'
271
+ },
272
+ expectedResult: 'default-src "self"'
273
+ },
274
+ ].forEach(({ responseHeaders, expectedResult }) => {
275
+ it('returns the CSP header', function() {
276
+ expect(getCspHeaders(responseHeaders)).to.equal(expectedResult);
277
+ });
278
+ });
279
+ });
280
+
281
+ describe('checkCspSources', function() {
282
+ const defaultValues = {
283
+ baseUriSecure: false,
284
+ baseUriValue: '',
285
+ childSrcSecure: false,
286
+ childSrcValue: '',
287
+ connectSrcSecure: false,
288
+ connectSrcValue: '',
289
+ defaultSrcSecure: false,
290
+ defaultSrcValue: '',
291
+ formActionSecure: false,
292
+ formActionValue: '',
293
+ frameAncestorsSecure: false,
294
+ frameAncestorsValue: '',
295
+ frameSrcSecure: false,
296
+ frameSrcValue: '',
297
+ mediaSrcSecure: false,
298
+ mediaSrcValue: '',
299
+ objectSrcSecure: false,
300
+ objectSrcValue: '',
301
+ pluginTypesSecure: false,
302
+ pluginTypesValue: '',
303
+ referrerSecure: true,
304
+ referrerValue: '',
305
+ reflectedXssSecure: true,
306
+ reflectedXssValue: '',
307
+ scriptSrcSecure: false,
308
+ scriptSrcValue: '',
309
+ styleSrcSecure: false,
310
+ styleSrcValue: ''
311
+ };
312
+
313
+ [
314
+ {
315
+ policy: 'default-src *;',
316
+ expectedResult: {
317
+ ...defaultValues,
318
+ defaultSrcValue: '*',
319
+ insecure: true
320
+ }
321
+ },
322
+ {
323
+ policy: 'default-src foobar.com;',
324
+ expectedResult: {
325
+ ...defaultValues,
326
+ childSrcSecure: true,
327
+ connectSrcSecure: true,
328
+ defaultSrcSecure: true,
329
+ defaultSrcValue: 'foobar.com',
330
+ frameSrcSecure: true,
331
+ mediaSrcSecure: true,
332
+ objectSrcSecure: true,
333
+ scriptSrcSecure: true,
334
+ styleSrcSecure: true,
335
+ insecure: true
336
+ }
337
+ },
338
+ {
339
+ policy: 'media-src *;',
340
+ expectedResult: {
341
+ ...defaultValues,
342
+ mediaSrcValue: '*',
343
+ insecure: true
344
+ }
345
+ },
346
+ {
347
+ policy: 'media-src foobar.com',
348
+ expectedResult: {
349
+ ...defaultValues,
350
+ mediaSrcSecure: true,
351
+ mediaSrcValue: 'foobar.com',
352
+ insecure: true
353
+ }
354
+ },
355
+ {
356
+ policy: 'media-src foobar.com *; child-src *;',
357
+ expectedResult: {
358
+ ...defaultValues,
359
+ mediaSrcSecure: false,
360
+ mediaSrcValue: 'foobar.com *',
361
+ childSrcValue: '*',
362
+ insecure: true
363
+ }
364
+ },
365
+ {
366
+ policy: 'referrer *; reflected-xss 1;',
367
+ expectedResult: {
368
+ ...defaultValues,
369
+ reflectedXssValue: '1',
370
+ referrerSecure: false,
371
+ referrerValue: '*',
372
+ insecure: true
373
+ }
374
+ },
375
+ {
376
+ policy: 'referrer unsafe-url',
377
+ expectedResult: {
378
+ ...defaultValues,
379
+ referrerSecure: false,
380
+ referrerValue: 'unsafe-url',
381
+ insecure: true
382
+ }
383
+ },
384
+ ].forEach(({ policy, expectedResult }) => {
385
+ it('checks the CSP sources', function() {
386
+ expect(checkCspSources(policy)).to.deep.equal(expectedResult);
387
+ });
388
+ });
389
+ });
390
+ });
391
+
@@ -0,0 +1,41 @@
1
+ 'use strict';
2
+
3
+ const { Event } = require('@contrast/common');
4
+ const { expect } = require('chai');
5
+ const proxyquire = require('proxyquire');
6
+ const mocks = require('@contrast/test/mocks');
7
+
8
+ const moduleMock = (moduleName, mock) => (core) => {
9
+ core.assess.responseScanning[moduleName] = mock[moduleName];
10
+ };
11
+
12
+ describe('assess response-scanning', function () {
13
+ let core, responseScanningMock, responseScanningModule, modulesWithInstall, reportFindings;
14
+
15
+ beforeEach(function () {
16
+ core = mocks.core();
17
+ core.scopes = mocks.scopes();
18
+ core.assess = mocks.assess();
19
+ responseScanningMock = core.assess.responseScanning;
20
+ responseScanningModule = proxyquire('.', {
21
+ './install/http': moduleMock('httpInstrumentation', responseScanningMock),
22
+ });
23
+ ({ reportFindings } = responseScanningModule(core));
24
+ modulesWithInstall = ['httpInstrumentation'];
25
+ });
26
+
27
+ it('installs the components', function () {
28
+ responseScanningModule(core).install();
29
+
30
+ modulesWithInstall.forEach((module) => {
31
+ expect(responseScanningMock[module].install).to.have.been.calledOnce;
32
+ });
33
+ });
34
+
35
+ it('reports findings through messages', function () {
36
+ reportFindings(null, 'some-metadata');
37
+
38
+ expect(core.messages.emit).to.have.been.calledOnceWith(Event.ASSESS_RESPONSE_SCANNING_FINDING, 'some-metadata');
39
+ });
40
+ });
41
+
@@ -0,0 +1,175 @@
1
+ 'use strict';
2
+
3
+ const { expect } = require('chai');
4
+ const sinon = require('sinon');
5
+ const { initAssessFixture } = require('@contrast/test/fixtures');
6
+
7
+ describe('assess response scanning sinks http', function () {
8
+ const sc = {};
9
+ const store = { assess: sc };
10
+ const expectedArgs = [
11
+ {
12
+ header1: ['value1', 'value2', 'value3'],
13
+ header2: 'value1'
14
+ },
15
+ 'test-body'
16
+ ];
17
+
18
+ const bodyHandlers = [
19
+ 'handleAutoCompleteMissing',
20
+ 'handleCacheControlsMissing',
21
+ 'handleParameterPollution',
22
+ 'handleXxsProtectionHeaderDisabled',
23
+ ];
24
+
25
+ const handlerMethods = [
26
+ 'handleAutoCompleteMissing',
27
+ 'handleCacheControlsMissing',
28
+ 'handleClickJackingControlsMissing',
29
+ 'handleParameterPollution',
30
+ 'handleCspHeader',
31
+ 'handleHstsHeaderMissing',
32
+ 'handleXPoweredByHeader',
33
+ 'handleXContentTypeHeaderMissing',
34
+ 'handleXxsProtectionHeaderDisabled',
35
+ ];
36
+
37
+ let core,
38
+ responseScanning,
39
+ http,
40
+ httpModule,
41
+ http2Module,
42
+ ServerResponse,
43
+ Http2ServerResponse,
44
+ objInstances,
45
+ simulateRequestScope;
46
+
47
+ beforeEach(function () {
48
+ ({ core, simulateRequestScope } = initAssessFixture());
49
+
50
+ responseScanning = core.assess.responseScanning;
51
+ handlerMethods.forEach((method) => sinon.stub(core.assess.responseScanning, method));
52
+
53
+ ServerResponse = function () { };
54
+ Http2ServerResponse = function () { };
55
+ httpModule = {
56
+ ServerResponse,
57
+ };
58
+
59
+ http2Module = {
60
+ Http2ServerResponse,
61
+ };
62
+
63
+ httpModule.ServerResponse.prototype.end = function () {
64
+ return {
65
+ _header: 'header1: value1\r\n' +
66
+ 'header1: value2\r\n' +
67
+ 'header1: value3\r\n' +
68
+ 'header2: value1\r\n\r\n'
69
+ };
70
+ };
71
+ httpModule.ServerResponse.prototype.write = function () {
72
+ return {
73
+ _header: 'header1: value1\r\n' +
74
+ 'header1: value2\r\n' +
75
+ 'header1: value3\r\n' +
76
+ 'header2: value1\r\n\r\n'
77
+ };
78
+ };
79
+
80
+ const headersSymbol = Symbol('headers');
81
+
82
+ http2Module.Http2ServerResponse.prototype = {
83
+ end: () => ({
84
+ [headersSymbol]: {
85
+ header1: ['value1', 'value2', 'value3'],
86
+ header2: 'value1'
87
+ }
88
+ }),
89
+ write: () => { },
90
+ [headersSymbol]: {
91
+ header1: ['value1', 'value2', 'value3'],
92
+ header2: 'value1'
93
+ }
94
+ };
95
+
96
+ http = require('./http')(core);
97
+ core.depHooks.resolve
98
+ .withArgs({ name: 'http' })
99
+ .yields(httpModule);
100
+ core.depHooks.resolve
101
+ .withArgs({ name: 'http2' })
102
+ .yields(http2Module);
103
+
104
+ http.install();
105
+
106
+ objInstances = {
107
+ http: httpModule.ServerResponse.prototype,
108
+ http2: http2Module.Http2ServerResponse.prototype,
109
+ };
110
+ });
111
+
112
+ ['http', 'http2'].forEach((instanceName) => {
113
+ describe(instanceName, function () {
114
+ ['end', 'write'].forEach((method) => {
115
+ it(`skip instrumentation if assess store is missing - Server.Response.${method}`, function () {
116
+ objInstances[instanceName][method]('hello world');
117
+ handlerMethods.forEach((method) => {
118
+ expect(responseScanning[method]).to.not.have.been.called;
119
+ });
120
+ });
121
+ });
122
+
123
+ it('checks only the body related response scanning rules on .write method', function () {
124
+ simulateRequestScope(function () {
125
+ objInstances[instanceName].write('test-body');
126
+ objInstances[instanceName].write();
127
+ bodyHandlers.forEach((method) => {
128
+ const handler = responseScanning[method];
129
+ expect(handler).to.have.callCount(2);
130
+
131
+ if (['handleAutoCompleteMissing', 'handleCacheControlsMissing'].includes(method)) {
132
+ expect(responseScanning[method]).to.have.been.calledWith(sc, ...expectedArgs);
133
+ } else if (method === 'handleXxsProtectionHeaderDisabled') {
134
+ expect(responseScanning[method]).to.have.been.calledWith(sc, expectedArgs[0]);
135
+ } else if (method === 'handleParameterPollution') {
136
+ expect(responseScanning[method]).to.have.been.calledWith(sc, expectedArgs[1]);
137
+ }
138
+ });
139
+ handlerMethods.forEach((method) => {
140
+ if (!bodyHandlers.includes(method)) {
141
+ expect(responseScanning[method]).to.not.have.been.called;
142
+ }
143
+ });
144
+ }, store);
145
+ });
146
+
147
+ it('checks all response scanning rules on .end method', function () {
148
+ simulateRequestScope(function () {
149
+ objInstances[instanceName].end('test-body');
150
+ objInstances[instanceName].end();
151
+ handlerMethods.forEach((method) => {
152
+ expect(responseScanning[method]).to.have.callCount(2);
153
+ if ([
154
+ 'handleAutoCompleteMissing',
155
+ 'handleCacheControlsMissing',
156
+ 'handleClickJackingControlsMissing',
157
+ ].includes(method)) {
158
+ expect(responseScanning[method]).to.have.been.calledWith(sc, ...expectedArgs);
159
+ } else if ([
160
+ 'handleCspHeader',
161
+ 'handleHstsHeaderMissing',
162
+ 'handleXPoweredByHeader',
163
+ 'handleXContentTypeHeaderMissing',
164
+ 'handleXxsProtectionHeaderDisabled',
165
+ ].includes(method)) {
166
+ expect(responseScanning[method]).to.have.been.calledWith(sc, expectedArgs[0]);
167
+ } else if (method === 'handleParameterPollution') {
168
+ expect(responseScanning[method]).to.have.been.calledWith(sc, expectedArgs[1]);
169
+ }
170
+ });
171
+ }, store);
172
+ });
173
+ });
174
+ });
175
+ });
@@ -0,0 +1,27 @@
1
+ 'use strict';
2
+
3
+ const { expect } = require('chai');
4
+ const { Rule } = require('@contrast/common');
5
+ const { initAssessFixture } = require('@contrast/test/fixtures');
6
+
7
+ describe('assess rule-scopes', function () {
8
+ let core;
9
+
10
+ beforeEach(function () {
11
+ ({ core } = initAssessFixture());
12
+ });
13
+
14
+ it('isLocked(ruleId) returns false when not running in rule scope', function () {
15
+ for (const ruleId of Object.values(Rule)) {
16
+ expect(core.assess.ruleScopes.isLocked(ruleId)).to.be.false;
17
+ }
18
+ });
19
+
20
+ it('isLocked(ruleId) will return true when run in scope for ruleId', function (next) {
21
+ core.assess.ruleScopes.run(Rule.REFLECTED_XSS, () => {
22
+ expect(core.assess.ruleScopes.isLocked(Rule.REFLECTED_XSS)).to.be.true;
23
+ expect(core.assess.ruleScopes.isLocked(Rule.SQL_INJECTION)).to.be.false;
24
+ next();
25
+ });
26
+ });
27
+ });