@contrast/assess 1.31.0 → 1.33.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.test.js +210 -0
  116. package/lib/dataflow/sources/install/fastify/index.test.js +33 -0
  117. package/lib/dataflow/sources/install/formidable1.test.js +119 -0
  118. package/lib/dataflow/sources/install/hapi/hapi.test.js +172 -0
  119. package/lib/dataflow/sources/install/hapi/index.test.js +33 -0
  120. package/lib/dataflow/sources/install/http.test.js +155 -0
  121. package/lib/dataflow/sources/install/koa/index.test.js +40 -0
  122. package/lib/dataflow/sources/install/koa/koa-bodyparsers.test.js +161 -0
  123. package/lib/dataflow/sources/install/koa/koa-multer.test.js +197 -0
  124. package/lib/dataflow/sources/install/koa/koa-routers.test.js +146 -0
  125. package/lib/dataflow/sources/install/koa/koa2.test.js +145 -0
  126. package/lib/dataflow/sources/install/multer1.test.js +145 -0
  127. package/lib/dataflow/sources/install/qs6.test.js +131 -0
  128. package/lib/dataflow/sources/install/querystring.test.js +82 -0
  129. package/lib/dataflow/sources/install/restify/fieldedTextBodyParser.test.js +88 -0
  130. package/lib/dataflow/sources/install/restify/index.test.js +38 -0
  131. package/lib/dataflow/sources/install/restify/jsonBodyParser.test.js +144 -0
  132. package/lib/dataflow/sources/install/restify/router.test.js +83 -0
  133. package/lib/dataflow/tag-utils-complete.test.js +27 -0
  134. package/lib/dataflow/tag-utils.test.js +192 -0
  135. package/lib/dataflow/tracker.js +7 -2
  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.js +10 -1
  142. package/lib/get-source-context.test.js +108 -0
  143. package/lib/index.test.js +41 -0
  144. package/lib/make-source-context.test.js +50 -0
  145. package/lib/response-scanning/handlers/index.test.js +425 -0
  146. package/lib/response-scanning/handlers/utils.test.js +391 -0
  147. package/lib/response-scanning/index.test.js +41 -0
  148. package/lib/response-scanning/install/http.test.js +175 -0
  149. package/lib/rule-scopes.test.js +27 -0
  150. package/lib/session-configuration/handlers.test.js +84 -0
  151. package/lib/session-configuration/index.test.js +36 -0
  152. package/lib/session-configuration/install/express-session.test.js +220 -0
  153. package/lib/session-configuration/install/fastify-cookie.test.js +65 -0
  154. package/lib/session-configuration/install/hapi.test.js +269 -0
  155. package/lib/session-configuration/install/koa.test.js +92 -0
  156. package/package.json +2 -2
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ const { isSafeContentType, SAFE_XSS_CONTENT_TYPES } = require('./is-safe-content-type');
4
+ const { expect } = require('chai');
5
+
6
+ describe('assess dataflow utils isSafeContentType', function() {
7
+ SAFE_XSS_CONTENT_TYPES.forEach(type => {
8
+ it(`should returns true for when content type is ${type}`, function() {
9
+ expect(isSafeContentType(type)).to.be.true;
10
+ });
11
+ });
12
+
13
+ it('should return false when content type is not part of the safe types', function() {
14
+ expect(isSafeContentType('something')).to.be.false;
15
+ });
16
+ });
@@ -0,0 +1,115 @@
1
+ 'use strict';
2
+
3
+ const { expect } = require('chai');
4
+ const { isVulnerable } = require('./is-vulnerable');
5
+
6
+ describe('assess dataflow utils isVulnerable', function() {
7
+ [
8
+ {
9
+ args: [
10
+ 'untrusted',
11
+ ['ant'],
12
+ {
13
+ untrusted: [5, 10, 100, 102],
14
+ ant: [4, 104],
15
+ },
16
+ ],
17
+ desc: 'encompasses all',
18
+ expected: false,
19
+ },
20
+ {
21
+ args: [
22
+ 'untrusted',
23
+ ['ant', 'cat'],
24
+ {
25
+ untrusted: [5, 10, 100, 102],
26
+ ant: [0, 0],
27
+ cat: [1, 1, 2, 2, 4, 8],
28
+ },
29
+ ],
30
+ desc: 'no overlaps',
31
+ expected: true,
32
+ },
33
+ {
34
+ args: [
35
+ 'untrusted',
36
+ ['ant', 'cat'],
37
+ {
38
+ untrusted: [5, 10, 100, 102],
39
+ ant: [0, 0],
40
+ cat: [1, 1, 2, 2, 3, 8],
41
+ },
42
+ ],
43
+ desc: 'no overlaps',
44
+ expected: true,
45
+ },
46
+ {
47
+ args: [
48
+ 'untrusted',
49
+ ['ant', 'cat'],
50
+ {
51
+ untrusted: [5, 10, 100, 102],
52
+ ant: [0, 0],
53
+ cat: [1, 5, 3, 8],
54
+ },
55
+ ],
56
+ desc: 'no overlaps',
57
+ expected: true,
58
+ },
59
+ {
60
+ args: [
61
+ 'untrusted',
62
+ ['ant'],
63
+ {
64
+ untrusted: [5, 10, 100, 102],
65
+ ant: [4, 5],
66
+ },
67
+ ],
68
+ desc: 'no overlaps',
69
+ expected: true,
70
+ },
71
+ {
72
+ args: [
73
+ 'untrusted',
74
+ ['ant', 'cat', 'dog'],
75
+ {
76
+ untrusted: [8, 14],
77
+ ant: [4, 5, 7, 10],
78
+ cat: [6, 9],
79
+ dog: [9, 18],
80
+ },
81
+ ],
82
+ desc: 'encompasses all',
83
+ expected: false,
84
+ },
85
+ {
86
+ args: [
87
+ 'untrusted',
88
+ ['ant', 'cat', 'dog'],
89
+ {
90
+ ant: [4, 5, 7, 10],
91
+ cat: [6, 9],
92
+ dog: [9, 18],
93
+ },
94
+ ],
95
+ desc: 'when no target tags found',
96
+ expected: false,
97
+ },
98
+ {
99
+ args: [
100
+ 'untrusted',
101
+ ['ant'],
102
+ {
103
+ untrusted: [4, 5, 7, 10],
104
+ ant: [4, 5, 7, 10],
105
+ },
106
+ ],
107
+ desc: 'when there are multiple ranges but they are all covered',
108
+ expected: false,
109
+ },
110
+ ].forEach(({ args, desc, expected }) => {
111
+ it(`${desc} returns ${expected}`, function() {
112
+ expect(isVulnerable(...args)).to.equal(expected);
113
+ });
114
+ });
115
+ });
@@ -0,0 +1,321 @@
1
+ 'use strict';
2
+
3
+ const { expect } = require('chai');
4
+ const sinon = require('sinon');
5
+ const { InputType } = require('@contrast/common');
6
+ const { initAssessFixture } = require('@contrast/test/fixtures');
7
+
8
+ const os = require('os');
9
+
10
+ const testMethod = os.platform() === 'win32' ? describe.skip : describe;
11
+
12
+ testMethod('assess event-factory', function () {
13
+ let core, createSourceEvent, createPropagationEvent, createSinkEvent;
14
+
15
+ beforeEach(function () {
16
+ ({ core } = initAssessFixture());
17
+ ({
18
+ createSourceEvent,
19
+ createPropagationEvent,
20
+ createSinkEvent
21
+ } = require('./event-factory')(core));
22
+ });
23
+
24
+ describe('createSourceEvent', function () {
25
+ [
26
+ {
27
+ data: {},
28
+ invalidProp: 'result',
29
+ },
30
+ {
31
+ data: { result: { tracked: true, value: 'foo' } },
32
+ invalidProp: 'name',
33
+ },
34
+ {
35
+ data: {
36
+ name: 'frogs',
37
+ result: { tracked: true, value: 'foo' },
38
+ inputType: 'bad-value',
39
+ },
40
+ invalidProp: 'inputType',
41
+ },
42
+ {
43
+ data: {
44
+ name: 'frogs',
45
+ result: { tracked: true, value: 'foo' },
46
+ inputType: InputType.QUERYSTRING,
47
+ },
48
+ invalidProp: 'tags',
49
+ message: 'event has no tags'
50
+ }
51
+ ].forEach(({
52
+ data,
53
+ invalidProp,
54
+ message = `invalid ${invalidProp}`,
55
+ }) => {
56
+ it(`will not create event when props are invalid: ${message}`, function () {
57
+ const event = createSourceEvent(data);
58
+ expect(event).to.equal(null);
59
+ expect(core.logger.debug).to.have.been.calledWith(
60
+ sinon.match.object,
61
+ 'Source event not created: %s',
62
+ message
63
+ );
64
+ });
65
+ });
66
+
67
+ [
68
+ {
69
+ data: {
70
+ name: 'COOKIE_VALUE.frogs',
71
+ result: { tracked: true, value: 'foo' },
72
+ inputType: InputType.COOKIE_VALUE,
73
+ tags: {
74
+ untrusted: [0, 4],
75
+ cookie: [0, 4],
76
+ },
77
+ stack: [],
78
+ },
79
+ }
80
+ ].forEach(({ data, expected }) => {
81
+ it('returns the event data when validated', function () {
82
+ const event = createSourceEvent(data);
83
+ expect(event).to.equal(data);
84
+ expect(core.logger.debug).not.to.have.been.called;
85
+ });
86
+ });
87
+ });
88
+
89
+ describe('createPropagationEvent', function () {
90
+ const validData = {
91
+ name: 'String.prototype.concat',
92
+ history: [{ mock: 'SourceEvent' }],
93
+ object: {
94
+ value: 'test',
95
+ tracked: true
96
+ },
97
+ args: [{ value: '-another-test', tracked: false }],
98
+ result: {
99
+ value: 'test-another-test',
100
+ tracked: true
101
+ },
102
+ source: 'O',
103
+ target: 'R',
104
+ tags: { untrusted: [0, 3] }
105
+ };
106
+ const validStore = {
107
+ assess: {
108
+ propagationEventsCount: 0
109
+ }
110
+ };
111
+ const validResult = {
112
+ name: 'String.prototype.concat',
113
+ history: [{ mock: 'SourceEvent' }],
114
+ object: {
115
+ value: 'test',
116
+ tracked: true
117
+ },
118
+ args: [{ value: '-another-test', tracked: false }],
119
+ result: {
120
+ value: 'test-another-test',
121
+ tracked: true
122
+ },
123
+ tags: { untrusted: [0, 3] },
124
+ addedTags: [],
125
+ removedTags: [],
126
+ source: 'O',
127
+ target: 'R'
128
+ };
129
+
130
+ it('logs a debug statement for missing source context and returns null when executed in the wrong context', function () {
131
+ core.scopes.sources.run({}, function () {
132
+ const result = createPropagationEvent(validData);
133
+
134
+ expect(core.logger.debug).to.have.been.calledOnceWith(
135
+ sinon.match.object,
136
+ 'No sourceContext found during Propagation event creation'
137
+ );
138
+ expect(result).to.be.null;
139
+ });
140
+ });
141
+
142
+ it('logs a debug statement for going above the maximum propagation events limit and returns null when we are above the said limit', function () {
143
+ core.scopes.sources.run({ assess: { propagationEventsCount: 500 } }, function () {
144
+ const result = createPropagationEvent(validData);
145
+
146
+ expect(core.logger.debug).to.have.been.calledOnceWith(
147
+ sinon.match.object,
148
+ 'Maximum number of Propagation events reached. Event not created'
149
+ );
150
+ expect(result).to.be.null;
151
+ });
152
+
153
+ });
154
+
155
+ [
156
+ {
157
+ data: { ...validData, name: '' },
158
+ message: 'invalid name',
159
+ },
160
+ {
161
+ data: { ...validData, source: undefined },
162
+ message: 'invalid source',
163
+ },
164
+ {
165
+ data: { ...validData, history: [] },
166
+ message: 'invalid history',
167
+ },
168
+ {
169
+ data: { ...validData, source: 'S' },
170
+ message: 'invalid source',
171
+ },
172
+ {
173
+ data: { ...validData, target: 'T' },
174
+ message: 'invalid target',
175
+ },
176
+ ].forEach(({ data, message }) => {
177
+ it(`logs a debug statement when insufficient data is passed and returns null: ${message}`, function () {
178
+ core.scopes.sources.run(validStore, function () {
179
+ const result = createPropagationEvent(data);
180
+
181
+ expect(core.logger.debug).to.have.been.calledOnceWith(
182
+ sinon.match.object,
183
+ 'Propagation event not created: %s',
184
+ message,
185
+ );
186
+ expect(result).to.be.null;
187
+ });
188
+ });
189
+ });
190
+
191
+ it('returns an event with stacktrace generator function when stacktraces option is set to "ALL"', function () {
192
+ core.config.assess.stacktraces = 'ALL';
193
+ core.scopes.sources.run(validStore, function () {
194
+ const result = createPropagationEvent(validData);
195
+
196
+ expect(result).to.be.like(validResult);
197
+ expect(result.time).not.to.be.undefined;
198
+ expect(result.stack).to.be.an('array');
199
+ });
200
+ });
201
+
202
+ it('returns an event without stacktrace generator function when stacktraces option is not set to "ALL"', function () {
203
+ core.config.assess.stacktraces = 'SOME';
204
+ core.scopes.sources.run(validStore, function () {
205
+ const result = createPropagationEvent(validData);
206
+
207
+ expect(result).to.be.like(validResult);
208
+ expect(result.time).not.to.be.undefined;
209
+ expect(result.stack).to.deep.equal([]);
210
+ });
211
+ });
212
+ });
213
+
214
+ describe('createSinkEvent', function () {
215
+ const validData = {
216
+ name: 'mysql/lib/Connection.query',
217
+ history: [{ mock: 'SourceEvent' }],
218
+ object: {
219
+ value: 'MySQL.Query#0001',
220
+ tracked: false
221
+ },
222
+ args: [{ value: 'malicious-value', tracked: true }],
223
+ result: {
224
+ value: null,
225
+ tracked: false
226
+ },
227
+ tags: { untrusted: [0, 14] },
228
+ source: 'P0'
229
+ };
230
+ const validStore = {
231
+ assess: {
232
+ propagationEventsCount: 0
233
+ }
234
+ };
235
+ const validResult = {
236
+ name: 'mysql/lib/Connection.query',
237
+ history: [{ mock: 'SourceEvent' }],
238
+ object: {
239
+ value: 'MySQL.Query#0001',
240
+ tracked: false
241
+ },
242
+ args: [{ value: 'malicious-value', tracked: true }],
243
+ result: {
244
+ value: null,
245
+ tracked: false
246
+ },
247
+ tags: { untrusted: [0, 14] },
248
+ source: 'P0',
249
+ };
250
+
251
+ it('logs a debug statement for missing source context and returns null when executed in the wrong context', function () {
252
+ core.scopes.sources.run({}, function () {
253
+ const result = createSinkEvent(validData);
254
+
255
+ expect(core.logger.debug).to.have.been.calledOnceWith(
256
+ sinon.match.object,
257
+ 'no sourceContext found during sink event creation'
258
+ );
259
+ expect(result).to.be.null;
260
+ });
261
+ });
262
+
263
+ [
264
+ {
265
+ data: {
266
+ ...validData,
267
+ name: '',
268
+ },
269
+ message: 'no sink event name'
270
+ },
271
+ {
272
+ data: {
273
+ ...validData,
274
+ history: []
275
+ },
276
+ message: 'empty history for sink event'
277
+ },
278
+ {
279
+ data: {
280
+ ...validData,
281
+ source: 'S'
282
+ },
283
+ message: 'malformed or missing sink event source field',
284
+ },
285
+ ].forEach(({ data, message }) => {
286
+ it(`logs a debug statement when insufficient data is passed and returns null: ${message}`, function () {
287
+ core.scopes.sources.run(validStore, function () {
288
+ const result = createSinkEvent(data);
289
+
290
+ expect(core.logger.debug).to.have.been.calledWith(
291
+ sinon.match.object,
292
+ message
293
+ );
294
+ expect(result).to.be.null;
295
+ });
296
+ });
297
+ });
298
+
299
+ it('returns an event with stacktrace generator function when stacktraces option is not set to "NONE"', function () {
300
+ core.config.assess.stacktraces = 'ALL';
301
+ core.scopes.sources.run(validStore, function () {
302
+ const result = createSinkEvent(validData);
303
+
304
+ expect(result).to.be.like(validResult);
305
+ expect(result.time).not.to.be.undefined;
306
+ expect(result.stack).to.be.instanceOf(Array);
307
+ });
308
+ });
309
+
310
+ it('returns an event without stacktrace generator function when stacktraces option is set to "NONE"', function () {
311
+ core.config.assess.stacktraces = 'NONE';
312
+ core.scopes.sources.run(validStore, function () {
313
+ const result = createSinkEvent(validData);
314
+
315
+ expect(result).to.be.like(validResult);
316
+ expect(result.time).not.to.be.undefined;
317
+ expect(result.stack).to.deep.equal([]);
318
+ });
319
+ });
320
+ });
321
+ });
@@ -0,0 +1,194 @@
1
+ 'use strict';
2
+
3
+ const { expect } = require('chai');
4
+ const { Event } = require('@contrast/common');
5
+ const { initAssessFixture } = require('@contrast/test/fixtures');
6
+
7
+ describe('assess getPolicy', function () {
8
+ let core, getPolicy;
9
+
10
+ beforeEach(function () {
11
+ ({ core } = initAssessFixture());
12
+ getPolicy = require('./get-policy')(core);
13
+ });
14
+
15
+ function assertPolicyOK(policy) {
16
+ expect(policy).to.have.property('enabledRules').and.be.a('Set').not.empty;
17
+ expect(policy).to.have.property('getInputPolicy').and.be.a('Function');
18
+ }
19
+
20
+ it('inits policy and will adjust according to TS settings updates', function() {
21
+ let policy = getPolicy();
22
+
23
+ expect(policy.enabledRules).to.have.length.greaterThan(1);
24
+ expect(policy.enabledRules).to.contain('reflected-xss');
25
+
26
+ core.messages.emit(Event.SERVER_SETTINGS_UPDATE, {
27
+ assess: {
28
+ ['sql-injection']: { enable: true },
29
+ ['reflected-xss']: { enable: false },
30
+ }
31
+ });
32
+
33
+ policy = getPolicy();
34
+
35
+ expect(policy.enabledRules).to.contain('sql-injection');
36
+ expect(policy.enabledRules).not.to.contain('reflected-xss');
37
+ });
38
+
39
+ it('url exclusions can disable all rules', function() {
40
+ const uriPath = '/exclude-all-rules';
41
+ let policy = getPolicy({ uriPath });
42
+
43
+ assertPolicyOK(policy);
44
+
45
+ core.messages.emit(Event.SERVER_SETTINGS_UPDATE, {
46
+ exclusions: {
47
+ url: [{
48
+ urls: ['/exclude-all-rules'],
49
+ matchStrategy: 'ONLY',
50
+ assessmentRules: [],
51
+ assess_rules: [],
52
+ protect_rules: [],
53
+ modes: ['assess', 'defend'],
54
+ name: 'UrlExclusionAllRules'
55
+ }]
56
+ }
57
+ });
58
+
59
+ policy = getPolicy({ uriPath });
60
+ expect(policy).to.be.null;
61
+ });
62
+
63
+ it('url exclusions can disable specific rules', function() {
64
+ const uriPath = '/exclude-some-rules';
65
+ let policy = getPolicy({ uriPath });
66
+
67
+ assertPolicyOK(policy);
68
+
69
+ core.messages.emit(Event.SERVER_SETTINGS_UPDATE, {
70
+ exclusions: {
71
+ url: [{
72
+ urls: ['/exclude-some-rules'],
73
+ matchStrategy: 'ONLY',
74
+ assessmentRules: [],
75
+ assess_rules: ['sql-injection'],
76
+ protect_rules: [],
77
+ modes: ['assess', 'defend'],
78
+ name: 'UrlExclusionAllRules'
79
+ }]
80
+ }
81
+ });
82
+
83
+ policy = getPolicy({ uriPath });
84
+ expect(policy.enabledRules).not.to.contain('sql-injection');
85
+ });
86
+
87
+ [
88
+ {
89
+ inputType: 'HEADER',
90
+ exclusionType: 'HEADER',
91
+ },
92
+ {
93
+ inputType: 'QUERYSTRING',
94
+ exclusionType: 'PARAMETER',
95
+ },
96
+ {
97
+ inputType: 'BODY',
98
+ exclusionType: 'PARAMETER',
99
+ },
100
+ {
101
+ inputType: 'URL_PARAMETER',
102
+ exclusionType: 'PARAMETER',
103
+ },
104
+ {
105
+ inputType: 'COOKIE_VALUE',
106
+ exclusionType: 'COOKIE',
107
+ },
108
+ ].forEach(({ inputType, exclusionType }) => {
109
+ it(`input exclusions can exclude specific rules (input: ${inputType}, exclusion:${exclusionType})`, function() {
110
+ core.messages.emit(Event.SERVER_SETTINGS_UPDATE, {
111
+ exclusions: {
112
+ input: [{
113
+ type: exclusionType,
114
+ urls: ['/exclude-some-inputs', '/exclude-some-inputs-.*'],
115
+ matchStrategy: 'ONLY',
116
+ assessmentRules: [],
117
+ assess_rules: ['sql-injection', 'nosql-injection'],
118
+ protect_rules: [],
119
+ modes: ['assess', 'defend'],
120
+ name: 'abc.*'
121
+ }]
122
+ }
123
+ });
124
+
125
+ const policy = getPolicy({ uriPath: '/exclude-some-inputs' });
126
+ let inputPolicy = policy.getInputPolicy(inputType, 'abcdef');
127
+ expect(inputPolicy).to.have.property('track', true);
128
+ expect(inputPolicy).to.have.property('excludedRules')
129
+ .to.be.a('Set')
130
+ .and.have.lengthOf(2)
131
+ .and.contain('nosql-injection')
132
+ .and.contain('sql-injection');
133
+
134
+ inputPolicy = policy.getInputPolicy(inputType, 'xyz');
135
+ expect(inputPolicy).to.have.property('track', true);
136
+ expect(inputPolicy).to.have.property('excludedRules')
137
+ .to.be.a('Set')
138
+ .and.have.lengthOf(0);
139
+ });
140
+ });
141
+
142
+ [
143
+ {
144
+ inputType: 'HEADER',
145
+ exclusionType: 'HEADER',
146
+ },
147
+ {
148
+ inputType: 'QUERYSTRING',
149
+ exclusionType: 'PARAMETER',
150
+ },
151
+ {
152
+ inputType: 'BODY',
153
+ exclusionType: 'PARAMETER',
154
+ },
155
+ {
156
+ inputType: 'URL_PARAMETER',
157
+ exclusionType: 'PARAMETER',
158
+ },
159
+ {
160
+ inputType: 'COOKIE_VALUE',
161
+ exclusionType: 'COOKIE',
162
+ },
163
+ ].forEach(({ inputType, exclusionType }) => {
164
+ it(`input exclusions can exclude specific rules (input: ${inputType}, exclusion:${exclusionType})`, function() {
165
+ core.messages.emit(Event.SERVER_SETTINGS_UPDATE, {
166
+ exclusions: {
167
+ input: [{
168
+ type: exclusionType,
169
+ urls: ['/exclude-some-inputs', '/exclude-some-inputs-.*'],
170
+ matchStrategy: 'ONLY',
171
+ assessmentRules: [],
172
+ assess_rules: [],
173
+ protect_rules: [],
174
+ modes: ['assess', 'defend'],
175
+ name: 'abc.*'
176
+ }]
177
+ }
178
+ });
179
+
180
+ const policy = getPolicy({ uriPath: '/exclude-some-inputs' });
181
+ let inputPolicy;
182
+
183
+ inputPolicy = policy.getInputPolicy(inputType, 'abcdef');
184
+ expect(inputPolicy).to.have.property('track', false);
185
+ expect(inputPolicy).not.to.have.property('excludedRules');
186
+
187
+ inputPolicy = policy.getInputPolicy(inputType, 'xyz');
188
+ expect(inputPolicy).to.have.property('track', true);
189
+ expect(inputPolicy).to.have.property('excludedRules')
190
+ .to.be.a('Set')
191
+ .and.have.lengthOf(0);
192
+ });
193
+ });
194
+ });
@@ -37,9 +37,18 @@ module.exports = function(core) {
37
37
  */
38
38
  return core.assess.getSourceContext = function getSourceContext(type, ...rest) {
39
39
  const ctx = sources.getStore()?.assess;
40
-
40
+ // <unsafe>
41
+ // This method is expected to be called by all Assess instrumentation components prior to doing work.
42
+ // Until we check policy, and whether instrumentation is locked, any instrumentation that is called before
43
+ // the </unsafe> section below will result in infinite recursion.
44
+ //
45
+ // E.g. Uncommenting any line below will cause a stack overflow:
46
+ // 'asdf'.concat()
47
+ // console.log() // even though this is deadzoned, we haven't checked whether instrumentation is locked yet
48
+ //
41
49
  // policy will not exist if assess is altogether disabled for the active request e.g. url exclusion
42
50
  if (!ctx?.policy || instrumentation.isLocked()) return null;
51
+ // </unsafe> but still be careful
43
52
 
44
53
  switch (type) {
45
54
  case InstrumentationType.PROPAGATOR: {