@contrast/assess 1.18.0 → 1.20.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 (160) hide show
  1. package/LICENSE +1 -1
  2. package/lib/constants.js +26 -0
  3. package/lib/crypto-analysis/common.js +20 -0
  4. package/lib/crypto-analysis/index.js +44 -0
  5. package/lib/crypto-analysis/install/crypto.js +156 -0
  6. package/lib/crypto-analysis/install/math.js +104 -0
  7. package/lib/dataflow/index.js +1 -1
  8. package/lib/dataflow/propagation/common.js +1 -1
  9. package/lib/dataflow/propagation/index.js +1 -1
  10. package/lib/dataflow/propagation/install/JSON/index.js +1 -1
  11. package/lib/dataflow/propagation/install/JSON/parse-fn.js +1 -1
  12. package/lib/dataflow/propagation/install/JSON/parse.js +15 -14
  13. package/lib/dataflow/propagation/install/JSON/stringify.js +2 -2
  14. package/lib/dataflow/propagation/install/array-prototype-join.js +1 -1
  15. package/lib/dataflow/propagation/install/buffer.js +1 -1
  16. package/lib/dataflow/propagation/install/contrast-methods/add.js +1 -1
  17. package/lib/dataflow/propagation/install/contrast-methods/index.js +1 -1
  18. package/lib/dataflow/propagation/install/contrast-methods/number.js +4 -3
  19. package/lib/dataflow/propagation/install/contrast-methods/string.js +1 -1
  20. package/lib/dataflow/propagation/install/contrast-methods/tag.js +1 -1
  21. package/lib/dataflow/propagation/install/decode-uri-component.js +1 -1
  22. package/lib/dataflow/propagation/install/ejs/escape-xml.js +3 -3
  23. package/lib/dataflow/propagation/install/ejs/index.js +2 -1
  24. package/lib/dataflow/propagation/install/ejs/template.js +79 -0
  25. package/lib/dataflow/propagation/install/encode-uri.js +1 -1
  26. package/lib/dataflow/propagation/install/escape-html.js +1 -1
  27. package/lib/dataflow/propagation/install/escape.js +1 -1
  28. package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.js +1 -1
  29. package/lib/dataflow/propagation/install/isnumeric-0.js +3 -3
  30. package/lib/dataflow/propagation/install/joi/any.js +1 -1
  31. package/lib/dataflow/propagation/install/joi/boolean.js +1 -1
  32. package/lib/dataflow/propagation/install/joi/expression.js +1 -1
  33. package/lib/dataflow/propagation/install/joi/index.js +1 -1
  34. package/lib/dataflow/propagation/install/joi/keys.js +1 -1
  35. package/lib/dataflow/propagation/install/joi/number.js +1 -1
  36. package/lib/dataflow/propagation/install/joi/object.js +1 -1
  37. package/lib/dataflow/propagation/install/joi/string-schema.js +1 -1
  38. package/lib/dataflow/propagation/install/joi/utils.js +1 -1
  39. package/lib/dataflow/propagation/install/joi/values.js +1 -1
  40. package/lib/dataflow/propagation/install/mongoose/common.js +1 -1
  41. package/lib/dataflow/propagation/install/mongoose/index.js +1 -1
  42. package/lib/dataflow/propagation/install/mongoose/schema-map.js +1 -1
  43. package/lib/dataflow/propagation/install/mongoose/schema-mixed.js +1 -1
  44. package/lib/dataflow/propagation/install/mongoose/schema-string.js +1 -1
  45. package/lib/dataflow/propagation/install/mustache-escape.js +1 -1
  46. package/lib/dataflow/propagation/install/mysql-connection-escape.js +1 -1
  47. package/lib/dataflow/propagation/install/parse-int.js +3 -3
  48. package/lib/dataflow/propagation/install/path/basename.js +1 -1
  49. package/lib/dataflow/propagation/install/path/common.js +1 -1
  50. package/lib/dataflow/propagation/install/path/dirname.js +1 -1
  51. package/lib/dataflow/propagation/install/path/extname.js +1 -1
  52. package/lib/dataflow/propagation/install/path/format.js +1 -1
  53. package/lib/dataflow/propagation/install/path/index.js +1 -1
  54. package/lib/dataflow/propagation/install/path/join-and-resolve.js +1 -1
  55. package/lib/dataflow/propagation/install/path/normalize.js +1 -1
  56. package/lib/dataflow/propagation/install/path/parse.js +1 -1
  57. package/lib/dataflow/propagation/install/path/relative.js +1 -1
  58. package/lib/dataflow/propagation/install/path/toNamespacedPath.js +1 -1
  59. package/lib/dataflow/propagation/install/pug/index.js +3 -3
  60. package/lib/dataflow/propagation/install/pug-runtime-escape.js +1 -1
  61. package/lib/dataflow/propagation/install/querystring/escape.js +1 -1
  62. package/lib/dataflow/propagation/install/querystring/index.js +1 -1
  63. package/lib/dataflow/propagation/install/querystring/parse.js +1 -1
  64. package/lib/dataflow/propagation/install/querystring/stringify.js +1 -1
  65. package/lib/dataflow/propagation/install/reg-exp-prototype-exec.js +1 -1
  66. package/lib/dataflow/propagation/install/send.js +1 -1
  67. package/lib/dataflow/propagation/install/sequelize/index.js +1 -1
  68. package/lib/dataflow/propagation/install/sequelize/query-generator.js +1 -1
  69. package/lib/dataflow/propagation/install/sequelize/sql-string.js +1 -1
  70. package/lib/dataflow/propagation/install/sql-template-strings.js +1 -1
  71. package/lib/dataflow/propagation/install/string/concat.js +1 -1
  72. package/lib/dataflow/propagation/install/string/format-methods.js +1 -1
  73. package/lib/dataflow/propagation/install/string/html-methods.js +1 -1
  74. package/lib/dataflow/propagation/install/string/index.js +1 -1
  75. package/lib/dataflow/propagation/install/string/match-all.js +1 -1
  76. package/lib/dataflow/propagation/install/string/match.js +1 -1
  77. package/lib/dataflow/propagation/install/string/replace.js +1 -1
  78. package/lib/dataflow/propagation/install/string/slice.js +1 -1
  79. package/lib/dataflow/propagation/install/string/split.js +1 -1
  80. package/lib/dataflow/propagation/install/string/substring.js +1 -1
  81. package/lib/dataflow/propagation/install/string/trim.js +1 -1
  82. package/lib/dataflow/propagation/install/unescape.js +1 -1
  83. package/lib/dataflow/propagation/install/url/domain-parsers.js +1 -1
  84. package/lib/dataflow/propagation/install/url/index.js +1 -1
  85. package/lib/dataflow/propagation/install/url/parse.js +1 -1
  86. package/lib/dataflow/propagation/install/url/searchParams.js +1 -1
  87. package/lib/dataflow/propagation/install/url/url.js +1 -1
  88. package/lib/dataflow/propagation/install/util-format.js +10 -4
  89. package/lib/dataflow/propagation/install/validator/hooks.js +1 -1
  90. package/lib/dataflow/propagation/install/validator/index.js +1 -1
  91. package/lib/dataflow/propagation/install/validator/methods.js +1 -1
  92. package/lib/dataflow/sinks/common.js +1 -1
  93. package/lib/dataflow/sinks/index.js +1 -1
  94. package/lib/dataflow/sinks/install/child-process.js +21 -15
  95. package/lib/dataflow/sinks/install/eval.js +17 -15
  96. package/lib/dataflow/sinks/install/express/index.js +1 -1
  97. package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +15 -9
  98. package/lib/dataflow/sinks/install/fastify/index.js +1 -1
  99. package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +13 -6
  100. package/lib/dataflow/sinks/install/fs.js +8 -8
  101. package/lib/dataflow/sinks/install/function.js +9 -13
  102. package/lib/dataflow/sinks/install/http/index.js +1 -1
  103. package/lib/dataflow/sinks/install/http/request.js +17 -9
  104. package/lib/dataflow/sinks/install/http/server-response.js +12 -3
  105. package/lib/dataflow/sinks/install/koa/index.js +1 -1
  106. package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +16 -9
  107. package/lib/dataflow/sinks/install/libxmljs.js +16 -11
  108. package/lib/dataflow/sinks/install/marsdb.js +17 -12
  109. package/lib/dataflow/sinks/install/mongodb.js +32 -22
  110. package/lib/dataflow/sinks/install/mssql.js +21 -10
  111. package/lib/dataflow/sinks/install/mysql.js +16 -9
  112. package/lib/dataflow/sinks/install/node-serialize.js +16 -18
  113. package/lib/dataflow/sinks/install/postgres.js +18 -5
  114. package/lib/dataflow/sinks/install/sequelize.js +23 -17
  115. package/lib/dataflow/sinks/install/sqlite3.js +21 -8
  116. package/lib/dataflow/sinks/install/vm.js +20 -18
  117. package/lib/dataflow/sources/common.js +1 -1
  118. package/lib/dataflow/sources/handler.js +11 -10
  119. package/lib/dataflow/sources/index.js +2 -2
  120. package/lib/dataflow/sources/install/body-parser1.js +11 -13
  121. package/lib/dataflow/sources/install/{busboy1.js → busboy.js} +15 -15
  122. package/lib/dataflow/sources/install/cookie-parser1.js +7 -6
  123. package/lib/dataflow/sources/install/express/index.js +1 -1
  124. package/lib/dataflow/sources/install/express/params.js +9 -10
  125. package/lib/dataflow/sources/install/express/parsedUrl.js +1 -1
  126. package/lib/dataflow/sources/install/fastify/fastify.js +6 -7
  127. package/lib/dataflow/sources/install/fastify/index.js +1 -1
  128. package/lib/dataflow/sources/install/formidable1.js +8 -6
  129. package/lib/dataflow/sources/install/http.js +17 -45
  130. package/lib/dataflow/sources/install/koa/index.js +2 -1
  131. package/lib/dataflow/sources/install/koa/koa-bodyparsers.js +10 -9
  132. package/lib/dataflow/sources/install/koa/koa-multer.js +102 -0
  133. package/lib/dataflow/sources/install/koa/koa-routers.js +6 -8
  134. package/lib/dataflow/sources/install/koa/koa2.js +42 -38
  135. package/lib/dataflow/sources/install/multer1.js +26 -52
  136. package/lib/dataflow/sources/install/qs6.js +7 -6
  137. package/lib/dataflow/sources/install/querystring.js +5 -8
  138. package/lib/dataflow/tag-utils.js +1 -1
  139. package/lib/dataflow/tracker.js +1 -1
  140. package/lib/dataflow/utils/is-safe-content-type.js +1 -1
  141. package/lib/dataflow/utils/is-vulnerable.js +1 -1
  142. package/lib/event-factory.js +75 -26
  143. package/lib/get-policy.js +68 -0
  144. package/lib/get-source-context.js +62 -0
  145. package/lib/index.d.ts +64 -0
  146. package/lib/index.js +21 -20
  147. package/lib/make-source-context.js +78 -0
  148. package/lib/response-scanning/handlers/index.js +56 -29
  149. package/lib/response-scanning/handlers/utils.js +1 -1
  150. package/lib/response-scanning/index.js +1 -1
  151. package/lib/response-scanning/install/http.js +14 -8
  152. package/lib/rule-scopes.js +48 -0
  153. package/lib/session-configuration/common.js +1 -1
  154. package/lib/session-configuration/handlers.js +66 -47
  155. package/lib/session-configuration/index.js +3 -1
  156. package/lib/session-configuration/install/express-session.js +23 -26
  157. package/lib/session-configuration/install/fastify-cookie.js +110 -0
  158. package/lib/session-configuration/install/hapi.js +8 -11
  159. package/lib/session-configuration/install/koa.js +101 -0
  160. package/package.json +2 -2
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -28,17 +28,14 @@ module.exports = (core) => {
28
28
  (querystring) => patcher.patch(querystring, 'parse', {
29
29
  name,
30
30
  patchType,
31
- post({ args, hooked, orig, result }) {
31
+ post({ args, hooked, orig, result, funcKey }) {
32
32
  const sourceContext = core.scopes.sources.getStore()?.assess;
33
33
  const inputType = InputType.QUERYSTRING;
34
34
 
35
- if (!sourceContext) {
36
- logger.error({ name }, 'unable to handle source. Missing `sourceContext`');
37
- return;
38
- }
35
+ if (!sourceContext) return;
39
36
 
40
37
  if (sourceContext.parsedQuery) {
41
- logger.trace({ name }, 'values already tracked');
38
+ logger.trace({ funcKey }, 'values already tracked');
42
39
  return;
43
40
  }
44
41
 
@@ -61,7 +58,7 @@ module.exports = (core) => {
61
58
  // we do not set the `parsedQuery` value here so that frameworks
62
59
  // may handle queries in their own more specific manner.
63
60
  } catch (err) {
64
- logger.error({ err, name }, 'unable to handle source');
61
+ logger.error({ err, funcKey }, 'unable to handle source');
65
62
  }
66
63
  }
67
64
  }
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -16,9 +16,11 @@
16
16
  'use strict';
17
17
 
18
18
  const { InputType, match } = require('@contrast/common');
19
- const annotationRegExp = /^(A|O|R|P|P\d+)$/;
19
+ const ANNOTATION_REGEX = /^(A|O|R|P|P\d+)$/;
20
+ const SOURCE_EVENT_MSG = 'Source event not created: %s';
21
+ const PROPAGATION_EVENT_MSG = 'Propagation event not created: %s';
20
22
 
21
- module.exports = function(core) {
23
+ module.exports = function (core) {
22
24
  const {
23
25
  createSnapshot,
24
26
  config,
@@ -30,7 +32,7 @@ module.exports = function(core) {
30
32
 
31
33
  eventFactory.createdEvents = new WeakSet();
32
34
 
33
- eventFactory.createSourceEvent = function(data = {}) {
35
+ eventFactory.createSourceEvent = function (data = {}) {
34
36
  const {
35
37
  name,
36
38
  result = { value: null, tracked: false },
@@ -39,31 +41,29 @@ module.exports = function(core) {
39
41
  stack,
40
42
  } = data;
41
43
 
42
- const baseMessage = 'Source event not created: %s';
43
-
44
44
  if (!result.value) {
45
- logger.debug({ result }, baseMessage, 'invalid result');
45
+ logger.debug({ data }, SOURCE_EVENT_MSG, 'invalid result');
46
46
  return null;
47
47
  }
48
48
 
49
49
  if (!name) {
50
- logger.debug({ data }, baseMessage, 'invalid name');
50
+ logger.debug({ data }, SOURCE_EVENT_MSG, 'invalid name');
51
51
  return null;
52
52
  }
53
53
 
54
54
  if (!(inputType in InputType)) {
55
- logger.debug({ inputType }, baseMessage, 'invalid inputType');
55
+ logger.debug({ data }, SOURCE_EVENT_MSG, 'invalid inputType');
56
56
  return null;
57
57
  }
58
58
 
59
59
  if (!tags) {
60
- logger.debug({ data }, baseMessage, 'event has no tags');
60
+ logger.debug({ data }, SOURCE_EVENT_MSG, 'event has no tags');
61
61
  return null;
62
62
  }
63
63
 
64
64
 
65
65
  if (!stack || !Array.isArray(stack)) {
66
- logger.debug({ data }, baseMessage, 'invalid stack');
66
+ logger.debug({ data }, SOURCE_EVENT_MSG, 'invalid stack');
67
67
  return null;
68
68
  }
69
69
 
@@ -73,7 +73,7 @@ module.exports = function(core) {
73
73
  return data;
74
74
  };
75
75
 
76
- eventFactory.createPropagationEvent = function(data) {
76
+ eventFactory.createPropagationEvent = function (data) {
77
77
  const {
78
78
  name = '',
79
79
  moduleName,
@@ -94,30 +94,32 @@ module.exports = function(core) {
94
94
  const sourceContext = sources.getStore()?.assess;
95
95
 
96
96
  if (!sourceContext) {
97
- logger.debug({ name }, 'No sourceContext found during Propagation event creation');
97
+ logger.debug({ data }, 'No sourceContext found during Propagation event creation');
98
98
  return null;
99
99
  }
100
100
 
101
101
  if (sourceContext.propagationEventsCount >= config.assess.max_propagation_events) {
102
- logger.debug({ name }, 'Maximum number of Propagation events reached. Event not created');
102
+ logger.debug({ data }, 'Maximum number of Propagation events reached. Event not created');
103
103
  return null;
104
104
  }
105
105
 
106
106
  if (!name) {
107
- logger.debug({ name }, 'Propagation event not created: invalid name');
107
+ logger.debug({ data }, PROPAGATION_EVENT_MSG, 'invalid name');
108
108
  return null;
109
109
  }
110
110
 
111
111
  if (!history.length) {
112
- logger.debug({ name, history }, 'Propagation event not created: invalid history');
112
+ logger.debug({ data }, PROPAGATION_EVENT_MSG, 'invalid history');
113
113
  return null;
114
114
  }
115
115
 
116
- if (
117
- (!source || !match(source, annotationRegExp)) ||
118
- (!target || !match(target, annotationRegExp))
119
- ) {
120
- logger.debug({ name, source, target }, 'Propagation event not created: %s', 'invalid source/target');
116
+ if (!source || !match(source, ANNOTATION_REGEX)) {
117
+ logger.debug({ data }, PROPAGATION_EVENT_MSG, 'invalid source');
118
+ return null;
119
+ }
120
+
121
+ if (!target || !match(target, ANNOTATION_REGEX)) {
122
+ logger.debug({ data }, PROPAGATION_EVENT_MSG, 'invalid target');
121
123
  return null;
122
124
  }
123
125
 
@@ -152,7 +154,7 @@ module.exports = function(core) {
152
154
  return event;
153
155
  };
154
156
 
155
- eventFactory.createSinkEvent = function(data) {
157
+ eventFactory.createSinkEvent = function (data) {
156
158
  const {
157
159
  context,
158
160
  name = '',
@@ -169,7 +171,7 @@ module.exports = function(core) {
169
171
 
170
172
  const sourceContext = sources.getStore()?.assess;
171
173
  if (!sourceContext) {
172
- logger.debug('no sourceContext found during sink event creation');
174
+ logger.debug({ data }, 'no sourceContext found during sink event creation');
173
175
  return null;
174
176
  }
175
177
  if (!name) {
@@ -181,7 +183,7 @@ module.exports = function(core) {
181
183
  return null;
182
184
  }
183
185
  if (
184
- (!source || !source.match(annotationRegExp))
186
+ (!source || !source.match(ANNOTATION_REGEX))
185
187
  ) {
186
188
  logger.debug({ data }, 'malformed or missing sink event source field');
187
189
  return null;
@@ -214,7 +216,7 @@ module.exports = function(core) {
214
216
  return event;
215
217
  };
216
218
 
217
- eventFactory.createSessionEvent = function(data) {
219
+ eventFactory.createSessionEvent = function (data) {
218
220
  const {
219
221
  context,
220
222
  name = '',
@@ -235,7 +237,7 @@ module.exports = function(core) {
235
237
  }
236
238
 
237
239
  if (
238
- (!source || !source.match(annotationRegExp))
240
+ (!source || !source.match(ANNOTATION_REGEX))
239
241
  ) {
240
242
  logger.debug({ data }, 'malformed or missing sink event source field');
241
243
  return null;
@@ -270,5 +272,52 @@ module.exports = function(core) {
270
272
  return event;
271
273
  };
272
274
 
275
+
276
+ /**
277
+ * @param {{
278
+ * context: string,
279
+ * name: string,
280
+ * moduleName: string,
281
+ * methodName: string,
282
+ * object: { value: any, tracked: boolean },
283
+ * args: any[],
284
+ * result: { value: vany, tracked: boolean },
285
+ * source: string,
286
+ * stacktraceOpts: { constructorOpt?: Function},
287
+ * }} data
288
+ * @returns {any}
289
+ */
290
+ eventFactory.createCryptoAnalysisEvent = function (data) {
291
+ const {
292
+ name = '',
293
+ source,
294
+ stacktraceOpts,
295
+ } = data;
296
+
297
+ if (!name) {
298
+ logger.debug({ data }, 'no sink event name');
299
+ return null;
300
+ }
301
+
302
+ if (!source || !source.match(ANNOTATION_REGEX)) {
303
+ logger.debug({ data }, 'malformed or missing sink event source field');
304
+ return null;
305
+ }
306
+
307
+ let stack;
308
+ if (config.assess.stacktraces !== 'NONE') {
309
+ stack = createSnapshot(stacktraceOpts)();
310
+ } else {
311
+ stack = [];
312
+ }
313
+
314
+ data.stack = stack;
315
+ data.time = Date.now();
316
+
317
+ eventFactory.createdEvents.add(data);
318
+
319
+ return data;
320
+ };
321
+
273
322
  return eventFactory;
274
323
  };
@@ -0,0 +1,68 @@
1
+ /*
2
+ * Copyright: 2024 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 {
19
+ Rule,
20
+ ResponseScanningRule,
21
+ SessionConfigurationRule,
22
+ Event,
23
+ } = require('@contrast/common');
24
+
25
+ const rulesIds = Object.values({
26
+ ...Rule,
27
+ ...ResponseScanningRule,
28
+ ...SessionConfigurationRule,
29
+ });
30
+
31
+ /**
32
+ * @param {{
33
+ * config: import('@contrast/config').Config,
34
+ * logger: import('@contrast/logger').Logger,
35
+ * messages: import('@contrast/common').Messages,
36
+ * }} core
37
+ * @returns {import('@contrast/common').Installable}
38
+ */
39
+ module.exports = function assess(core) {
40
+ const { logger, messages } = core;
41
+
42
+ const enabledRules = new Set(rulesIds);
43
+
44
+ messages.on(Event.SERVER_SETTINGS_UPDATE, (msg) => {
45
+ if (!msg.assess) return;
46
+
47
+ for (const ruleId of rulesIds) {
48
+ const enable = msg.assess[ruleId]?.enable;
49
+ if (enable === true) {
50
+ enabledRules.add(ruleId);
51
+ if (ruleId === Rule.NOSQL_INJECTION) enabledRules.add(Rule.NOSQL_INJECTION_MONGO);
52
+ } else if (enable === false) {
53
+ if (ruleId === Rule.NOSQL_INJECTION) enabledRules.delete(Rule.NOSQL_INJECTION_MONGO);
54
+ enabledRules.delete(ruleId);
55
+ }
56
+ }
57
+ logger.info({
58
+ enabledRules: Array.from(enabledRules)
59
+ }, 'Assess policy updated');
60
+ });
61
+
62
+ return core.assess.getPolicy = function getPolicy() {
63
+ // creates copy of local policy for request store
64
+ return {
65
+ enabledRules: new Set(enabledRules),
66
+ };
67
+ };
68
+ };
@@ -0,0 +1,62 @@
1
+ /*
2
+ * Copyright: 2024 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 { InstrumentationType } = require('./constants');
19
+
20
+ /**
21
+ * @param {{
22
+ * assess: import('@contrast/assess').Assess,
23
+ * config: import('@contrast/config').Config,
24
+ * scopes: import('@contrast/scopes').Scopes,
25
+ * }} core
26
+ */
27
+ module.exports = function(core) {
28
+ const {
29
+ config,
30
+ scopes: { sources, instrumentation },
31
+ assess: { ruleScopes },
32
+ } = core;
33
+
34
+ /**
35
+ * @param {import('./constants.js').InstrumentationType} type
36
+ * @returns {import('@contrast/assess').SourceContext|null} the assess store
37
+ */
38
+ return core.assess.getSourceContext = function getSourceContext(type, ...rest) {
39
+ const ctx = sources.getStore()?.assess;
40
+
41
+ if (!ctx || instrumentation.isLocked()) return null;
42
+
43
+ switch (type) {
44
+ case InstrumentationType.PROPAGATOR: {
45
+ if (ctx.propagationEventsCount > config.assess.max_propagation_events) return null;
46
+ break;
47
+ }
48
+ case InstrumentationType.SOURCE: {
49
+ if (ctx.sourceEventsCount > config.assess.max_context_source_events) return null;
50
+ break;
51
+ }
52
+ case InstrumentationType.RULE: {
53
+ const [ruleId] = rest;
54
+ if (!ruleId) break;
55
+ if (!ctx.policy.enabledRules.has(ruleId) || ruleScopes.isLocked(ruleId)) return null;
56
+ break;
57
+ }
58
+ }
59
+
60
+ return ctx;
61
+ };
62
+ };
package/lib/index.d.ts ADDED
@@ -0,0 +1,64 @@
1
+ /*
2
+ * Copyright: 2024 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
+ import { IncomingMessage, ServerResponse } from 'node:http';
16
+ import {
17
+ Rule,
18
+ SessionConfigurationRule,
19
+ } from '@contrast/common';
20
+
21
+ export enum InstrumentationType {
22
+ SOURCE = 'source',
23
+ PROPAGATOR = 'propagator',
24
+ RULE = 'rule',
25
+ }
26
+
27
+ export interface SourceContext {
28
+ reqData: object,
29
+ responseData: {
30
+ contentType: string,
31
+ },
32
+ policy: {
33
+ enabledRules: Set<string>,
34
+ }
35
+ }
36
+
37
+ export interface Policy {
38
+ enabledRules: Set<Rule>,
39
+ }
40
+
41
+ export interface RuleScopes {
42
+ run(ruleId: Rule, cb: void): void;
43
+ isLocked(ruleId: Rule): boolean;
44
+ }
45
+
46
+ export interface SessionRuleState {
47
+ reported: boolean,
48
+ valuesAnalyzed: Set<string>,
49
+ }
50
+
51
+ export interface RuleState {
52
+ [SessionConfigurationRule.HTTPONLY]?: SessionRuleState,
53
+ [SessionConfigurationRule.SECURE_FLAG_MISSING]?: SessionRuleState,
54
+ }
55
+
56
+ export interface Assess {
57
+ getPolicy(): Policy,
58
+ getSourceContext(instrType?: InstrumentationType, opts?: any): SourceContext,
59
+ makeSourceContext(req: IncomingMessage, res: ServerResponse): SourceContext,
60
+ ruleScopes: RuleScopes,
61
+ ruleState: RuleState,
62
+ }
63
+
64
+ export function getSourceContext(instrType?: InstrumentationType, ops?: any): SourceContext;
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -16,32 +16,33 @@
16
16
  'use strict';
17
17
 
18
18
  const { callChildComponentMethodsSync } = require('@contrast/common');
19
- const sessionConfiguration = require('./session-configuration');
20
- const dataflow = require('./dataflow');
21
- const responseScanning = require('./response-scanning');
22
- const eventFactory = require('./event-factory');
23
19
 
24
20
  module.exports = function assess(core) {
25
- const assess = core.assess = {};
21
+ const assess = core.assess = {
22
+ install() {
23
+ if (!core.config.getEffectiveValue('assess.enable')) {
24
+ core.logger.debug('assess is disabled, skipping installation');
25
+ return;
26
+ }
27
+
28
+ core.rewriter.install('assess');
29
+ callChildComponentMethodsSync(core.assess, 'install');
30
+ },
31
+ };
26
32
 
27
- eventFactory(core);
28
- dataflow(core);
29
- responseScanning(core);
30
- sessionConfiguration(core);
33
+ require('./rule-scopes')(core);
34
+ require('./get-policy')(core);
35
+ require('./make-source-context')(core);
36
+ require('./get-source-context')(core);
37
+ require('./event-factory')(core);
38
+ require('./crypto-analysis')(core);
39
+ require('./dataflow')(core);
40
+ require('./response-scanning')(core);
41
+ require('./session-configuration')(core);
31
42
 
32
43
  // todo
33
44
  // crypto rule implementations
34
45
  // static rule implementations in coordination with (CLI) rewriter
35
46
 
36
- assess.install = function() {
37
- if (!core.config.getEffectiveValue('assess.enable')) {
38
- core.logger.debug('assess is disabled, skipping installation');
39
- return;
40
- }
41
-
42
- core.rewriter.install('assess');
43
- callChildComponentMethodsSync(core.assess, 'install');
44
- };
45
-
46
47
  return assess;
47
48
  };
@@ -0,0 +1,78 @@
1
+ /*
2
+ * Copyright: 2024 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 { toLowerCase } = require('@contrast/common');
19
+
20
+ /**
21
+ * @param {{
22
+ * assess: import('@contrast/assess').Assess,
23
+ * logger: import('@contrast/logger').Logger,
24
+ * }} core
25
+ */
26
+ module.exports = function(core) {
27
+ const {
28
+ assess: { getPolicy },
29
+ logger,
30
+ } = core;
31
+
32
+ /**
33
+ * @returns {import('@contrast/assess').SourceContext}
34
+ */
35
+ return core.assess.makeSourceContext = function (req, res) {
36
+ let contentType, queries, uriPath;
37
+
38
+ try {
39
+ const ix = req.url.indexOf('?');
40
+ if (ix >= 0) {
41
+ uriPath = req.url.slice(0, ix);
42
+ queries = req.url.slice(ix + 1);
43
+ } else {
44
+ uriPath = req.url;
45
+ queries = '';
46
+ }
47
+
48
+ // copy to avoid storing tracked values
49
+ const headers = { ...req.headers };
50
+ if (headers['content-type']) {
51
+ contentType = toLowerCase(headers['content-type']);
52
+ }
53
+
54
+ return {
55
+ propagationEventsCount: 0,
56
+ sourceEventsCount: 0,
57
+ policy: getPolicy(),
58
+ reqData: {
59
+ ip: req.socket.remoteAddress,
60
+ httpVersion: req.httpVersion,
61
+ method: req.method,
62
+ headers,
63
+ uriPath,
64
+ queries,
65
+ contentType,
66
+ },
67
+ responseData: {},
68
+ ruleState: {},
69
+ };
70
+ } catch (err) {
71
+ logger.error(
72
+ { err },
73
+ 'unable to construct assess store. assess will be disabled for request.'
74
+ );
75
+ return null;
76
+ }
77
+ };
78
+ };