@contrast/assess 1.9.0 → 1.11.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 (116) hide show
  1. package/lib/dataflow/event-factory.js +1 -1
  2. package/lib/dataflow/index.js +1 -1
  3. package/lib/dataflow/propagation/common.js +1 -1
  4. package/lib/dataflow/propagation/index.js +3 -1
  5. package/lib/dataflow/propagation/install/JSON/index.js +1 -1
  6. package/lib/dataflow/propagation/install/JSON/parse-fn.js +1 -1
  7. package/lib/dataflow/propagation/install/JSON/parse.js +1 -1
  8. package/lib/dataflow/propagation/install/JSON/stringify.js +1 -1
  9. package/lib/dataflow/propagation/install/array-prototype-join.js +1 -1
  10. package/lib/dataflow/propagation/install/buffer.js +1 -1
  11. package/lib/dataflow/propagation/install/contrast-methods/add.js +1 -1
  12. package/lib/dataflow/propagation/install/contrast-methods/index.js +1 -1
  13. package/lib/dataflow/propagation/install/contrast-methods/number.js +1 -1
  14. package/lib/dataflow/propagation/install/contrast-methods/string.js +1 -1
  15. package/lib/dataflow/propagation/install/contrast-methods/tag.js +1 -1
  16. package/lib/dataflow/propagation/install/decode-uri-component.js +1 -1
  17. package/lib/dataflow/propagation/install/ejs/escape-xml.js +1 -1
  18. package/lib/dataflow/propagation/install/ejs/index.js +1 -1
  19. package/lib/dataflow/propagation/install/encode-uri-component.js +1 -1
  20. package/lib/dataflow/propagation/install/escape-html.js +1 -1
  21. package/lib/dataflow/propagation/install/escape.js +1 -1
  22. package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.js +1 -1
  23. package/lib/dataflow/propagation/install/isnumeric-0.js +1 -1
  24. package/lib/dataflow/propagation/install/mongoose/common.js +20 -0
  25. package/lib/dataflow/propagation/install/mongoose/index.js +5 -9
  26. package/lib/dataflow/propagation/install/mongoose/schema-map.js +149 -0
  27. package/lib/dataflow/propagation/install/mongoose/schema-mixed.js +162 -0
  28. package/lib/dataflow/propagation/install/mongoose/schema-string.js +91 -37
  29. package/lib/dataflow/propagation/install/mysql-connection-escape.js +1 -1
  30. package/lib/dataflow/propagation/install/parse-int.js +1 -1
  31. package/lib/dataflow/propagation/install/path/basename.js +124 -0
  32. package/lib/dataflow/propagation/install/path/common.js +176 -0
  33. package/lib/dataflow/propagation/install/path/index.js +32 -0
  34. package/lib/dataflow/propagation/install/path/join-and-resolve.js +141 -0
  35. package/lib/dataflow/propagation/install/path/normalize.js +123 -0
  36. package/lib/dataflow/propagation/install/pug/index.js +1 -1
  37. package/lib/dataflow/propagation/install/pug-runtime-escape.js +1 -1
  38. package/lib/dataflow/propagation/install/querystring/index.js +1 -1
  39. package/lib/dataflow/propagation/install/querystring/parse.js +2 -2
  40. package/lib/dataflow/propagation/install/reg-exp-prototype-exec.js +182 -0
  41. package/lib/dataflow/propagation/install/sequelize.js +1 -1
  42. package/lib/dataflow/propagation/install/sql-template-strings.js +1 -1
  43. package/lib/dataflow/propagation/install/string/concat.js +1 -1
  44. package/lib/dataflow/propagation/install/string/format-methods.js +1 -1
  45. package/lib/dataflow/propagation/install/string/html-methods.js +1 -1
  46. package/lib/dataflow/propagation/install/string/index.js +65 -1
  47. package/lib/dataflow/propagation/install/string/match-all.js +236 -0
  48. package/lib/dataflow/propagation/install/string/match.js +84 -38
  49. package/lib/dataflow/propagation/install/string/replace.js +3 -3
  50. package/lib/dataflow/propagation/install/string/slice.js +2 -2
  51. package/lib/dataflow/propagation/install/string/split.js +2 -2
  52. package/lib/dataflow/propagation/install/string/substring.js +3 -3
  53. package/lib/dataflow/propagation/install/string/trim.js +2 -2
  54. package/lib/dataflow/propagation/install/unescape.js +1 -1
  55. package/lib/dataflow/propagation/install/url/domain-parsers.js +1 -1
  56. package/lib/dataflow/propagation/install/url/index.js +4 -1
  57. package/lib/dataflow/propagation/install/url/parse.js +131 -0
  58. package/lib/dataflow/propagation/install/url/searchParams.js +133 -0
  59. package/lib/dataflow/propagation/install/url/url.js +185 -0
  60. package/lib/dataflow/propagation/install/validator/hooks.js +1 -1
  61. package/lib/dataflow/propagation/install/validator/index.js +1 -1
  62. package/lib/dataflow/propagation/install/validator/methods.js +1 -1
  63. package/lib/dataflow/sinks/common.js +1 -1
  64. package/lib/dataflow/sinks/index.js +9 -5
  65. package/lib/dataflow/sinks/install/child-process.js +1 -1
  66. package/lib/dataflow/sinks/install/eval.js +138 -0
  67. package/lib/dataflow/sinks/install/express/index.js +1 -1
  68. package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +2 -2
  69. package/lib/dataflow/sinks/install/fastify/index.js +1 -1
  70. package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +3 -2
  71. package/lib/dataflow/sinks/install/fs.js +4 -4
  72. package/lib/dataflow/sinks/install/function.js +160 -0
  73. package/lib/dataflow/sinks/install/http/index.js +31 -0
  74. package/lib/dataflow/sinks/install/http/request.js +152 -0
  75. package/lib/dataflow/sinks/install/{http.js → http/server-response.js} +3 -3
  76. package/lib/dataflow/sinks/install/koa/index.js +1 -1
  77. package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +2 -2
  78. package/lib/dataflow/sinks/install/marsdb.js +1 -1
  79. package/lib/dataflow/sinks/install/mongodb.js +34 -46
  80. package/lib/dataflow/sinks/install/mssql.js +45 -32
  81. package/lib/dataflow/sinks/install/mysql.js +1 -1
  82. package/lib/dataflow/sinks/install/postgres.js +1 -1
  83. package/lib/dataflow/sinks/install/sequelize.js +1 -1
  84. package/lib/dataflow/sinks/install/sqlite3.js +1 -1
  85. package/lib/dataflow/sinks/install/vm.js +276 -0
  86. package/lib/dataflow/sources/common.js +1 -1
  87. package/lib/dataflow/sources/handler.js +1 -1
  88. package/lib/dataflow/sources/index.js +1 -1
  89. package/lib/dataflow/sources/install/body-parser1.js +1 -1
  90. package/lib/dataflow/sources/install/busboy1.js +1 -1
  91. package/lib/dataflow/sources/install/cookie-parser1.js +1 -1
  92. package/lib/dataflow/sources/install/express/index.js +1 -1
  93. package/lib/dataflow/sources/install/express/params.js +1 -1
  94. package/lib/dataflow/sources/install/express/parsedUrl.js +1 -1
  95. package/lib/dataflow/sources/install/fastify/fastify.js +1 -1
  96. package/lib/dataflow/sources/install/fastify/index.js +1 -1
  97. package/lib/dataflow/sources/install/formidable1.js +1 -1
  98. package/lib/dataflow/sources/install/http.js +1 -1
  99. package/lib/dataflow/sources/install/koa/index.js +1 -1
  100. package/lib/dataflow/sources/install/koa/koa-bodyparsers.js +1 -1
  101. package/lib/dataflow/sources/install/koa/koa-routers.js +1 -1
  102. package/lib/dataflow/sources/install/koa/koa2.js +1 -1
  103. package/lib/dataflow/sources/install/qs6.js +1 -1
  104. package/lib/dataflow/sources/install/querystring.js +1 -1
  105. package/lib/dataflow/tag-utils.js +71 -2
  106. package/lib/dataflow/tracker.js +1 -1
  107. package/lib/dataflow/utils/is-safe-content-type.js +1 -1
  108. package/lib/dataflow/utils/is-vulnerable.js +1 -1
  109. package/lib/index.js +1 -1
  110. package/lib/response-scanning/handlers/index.js +36 -30
  111. package/lib/response-scanning/handlers/utils.js +1 -1
  112. package/lib/response-scanning/index.js +1 -1
  113. package/lib/response-scanning/install/http.js +3 -3
  114. package/lib/session-configuration/index.js +1 -1
  115. package/lib/session-configuration/install/http.js +1 -1
  116. package/package.json +2 -2
@@ -0,0 +1,160 @@
1
+ /*
2
+ * Copyright: 2023 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
+ isString,
20
+ inspect,
21
+ join,
22
+ DataflowTag: {
23
+ UNTRUSTED,
24
+ CUSTOM_ENCODED_TRUST_BOUNDARY_VIOLATION,
25
+ CUSTOM_ENCODED,
26
+ CUSTOM_VALIDATED_TRUST_BOUNDARY_VIOLATION,
27
+ CUSTOM_VALIDATED,
28
+ LIMITED_CHARS,
29
+ },
30
+ } = require('@contrast/common');
31
+ const { patchType, filterSafeTags } = require('../common');
32
+
33
+ const ruleId = 'unsafe-code-execution';
34
+ const safeTags = [
35
+ CUSTOM_ENCODED_TRUST_BOUNDARY_VIOLATION,
36
+ CUSTOM_ENCODED,
37
+ CUSTOM_VALIDATED_TRUST_BOUNDARY_VIOLATION,
38
+ CUSTOM_VALIDATED,
39
+ LIMITED_CHARS,
40
+ ];
41
+
42
+ module.exports = function(core) {
43
+ const {
44
+ config,
45
+ logger,
46
+ patcher,
47
+ scopes: { sources, instrumentation },
48
+ assess: {
49
+ dataflow: {
50
+ tracker,
51
+ sinks: { isVulnerable, reportFindings, reportSafePositive },
52
+ eventFactory: { createSinkEvent },
53
+ },
54
+ },
55
+ } = core;
56
+
57
+ core.assess.dataflow.sinks.contrastFunction = {
58
+ install() {
59
+ if (!global.ContrastMethods?.Function) {
60
+ logger.error(
61
+ 'Cannot install `Function` instrumentation - Contrast method DNE'
62
+ );
63
+ return;
64
+ }
65
+
66
+ Object.assign(global.ContrastMethods, {
67
+ Function: patcher.patch(global.ContrastMethods.Function, {
68
+ name: 'global.ContrastMethods.Function',
69
+ patchType,
70
+ pre({ args: origArgs, hooked, orig, name }) {
71
+ const store = sources.getStore()?.assess;
72
+ const fnBody = origArgs[origArgs.length - 1];
73
+ if (
74
+ !store ||
75
+ instrumentation.isLocked() ||
76
+ !fnBody ||
77
+ !isString(fnBody)
78
+ )
79
+ return;
80
+
81
+ const strInfo = tracker.getData(fnBody);
82
+
83
+ if (!strInfo) return;
84
+
85
+ const isArgVulnerable = isVulnerable(UNTRUSTED, safeTags, strInfo.tags);
86
+
87
+ if (!isArgVulnerable && config.assess.safe_positives.enable) {
88
+ const foundSafeTags = filterSafeTags(safeTags, strInfo);
89
+ const safeStrInfo = {
90
+ value: strInfo.value,
91
+ tags: strInfo.tags,
92
+ };
93
+
94
+ reportSafePositive({
95
+ name,
96
+ ruleId,
97
+ safeTags: foundSafeTags,
98
+ strInfo: safeStrInfo,
99
+ });
100
+ }
101
+
102
+ if (isArgVulnerable) {
103
+ const args = origArgs.map((arg, idx) => {
104
+ if (idx === origArgs.length - 1) {
105
+ return {
106
+ value: strInfo.value,
107
+ tracked: true,
108
+ inspectedValue: `'${strInfo.value}'`,
109
+ };
110
+ }
111
+
112
+ const inspectedValue = inspect(arg);
113
+ const argInfo = arg && isString(arg) ? tracker.getData(arg) : null;
114
+
115
+ return {
116
+ value: argInfo ? argInfo.value : inspectedValue,
117
+ tracked: !!argInfo,
118
+ inspectedValue
119
+ };
120
+ });
121
+ const event = createSinkEvent({
122
+ name,
123
+ context: `${name}(${join(
124
+ args.map((a) => a.inspectedValue),
125
+ ', '
126
+ )})`,
127
+ history: [strInfo],
128
+ object: {
129
+ value: 'global.ContrastMethods',
130
+ tracked: false,
131
+ },
132
+ moduleName: 'global.ContrastMethods',
133
+ methodName: 'Function',
134
+ args: args.map((a) => ({
135
+ value: a.value,
136
+ tracked: a.tracked,
137
+ })),
138
+ tags: strInfo.tags,
139
+ source: `P${origArgs.length - 1}`,
140
+ stacktraceOpts: {
141
+ contructorOpt: hooked,
142
+ prependFrames: [orig],
143
+ },
144
+ });
145
+
146
+ if (event) {
147
+ reportFindings({
148
+ ruleId,
149
+ sinkEvent: event,
150
+ });
151
+ }
152
+ }
153
+ },
154
+ }),
155
+ });
156
+ },
157
+ };
158
+
159
+ return core.assess.dataflow.sinks.contrastFunction;
160
+ };
@@ -0,0 +1,31 @@
1
+ /*
2
+ * Copyright: 2023 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 } = require('@contrast/common');
19
+
20
+ module.exports = function(core) {
21
+ const http = core.assess.dataflow.sinks.http = {};
22
+
23
+ require('./request')(core);
24
+ require('./server-response')(core);
25
+
26
+ http.install = function() {
27
+ callChildComponentMethodsSync(http, 'install');
28
+ };
29
+
30
+ return http;
31
+ };
@@ -0,0 +1,152 @@
1
+ /*
2
+ * Copyright: 2023 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
+ inspect,
20
+ isString,
21
+ DataflowTag: {
22
+ UNTRUSTED,
23
+ CUSTOM_ENCODED_SSRF,
24
+ CUSTOM_ENCODED,
25
+ CUSTOM_VALIDATED_SSRF,
26
+ CUSTOM_VALIDATED,
27
+ LIMITED_CHARS
28
+ }
29
+ } = require('@contrast/common');
30
+ const Url = require('url');
31
+ const trustedLibs = [/^(?!.*(newrelic)).*http.*$/];
32
+ const { patchType } = require('../../common');
33
+ const { createAppendTags } = require('../../../tag-utils');
34
+
35
+ module.exports = function(core) {
36
+ const {
37
+ depHooks,
38
+ patcher,
39
+ scopes: { sources },
40
+ assess: {
41
+ dataflow: {
42
+ tracker,
43
+ sinks: {
44
+ isVulnerable,
45
+ reportFindings
46
+ },
47
+ eventFactory: { createSinkEvent },
48
+ },
49
+ },
50
+ } = core;
51
+ const http = core.assess.dataflow.sinks.http.request = {};
52
+
53
+ const safeTags = [
54
+ CUSTOM_ENCODED_SSRF,
55
+ CUSTOM_ENCODED,
56
+ CUSTOM_VALIDATED_SSRF,
57
+ CUSTOM_VALIDATED,
58
+ LIMITED_CHARS
59
+ ];
60
+
61
+ function getValueFromReq(value, key) {
62
+ if (isString(value)) {
63
+ const url = new Url.URL(value);
64
+ if (isString(url[key])) {
65
+ return url[key];
66
+ }
67
+ }
68
+
69
+ if (isString(value[key])) {
70
+ return value[key];
71
+ }
72
+ }
73
+
74
+ function containsTrustedLib(stack) {
75
+ for (const { file } of stack) {
76
+ for (const trusted of trustedLibs) {
77
+ if (trusted.exec(file)) {
78
+ return true;
79
+ }
80
+ }
81
+ }
82
+
83
+ return false;
84
+ }
85
+
86
+ http.install = function() {
87
+ ['http', 'https'].forEach((moduleName) => {
88
+ depHooks.resolve({ name: moduleName }, (module) => {
89
+ const name = `${moduleName}.request`;
90
+ const methodName = 'request';
91
+ patcher.patch(module, methodName, {
92
+ name,
93
+ patchType,
94
+ pre(data) {
95
+ const sourceContext = sources.getStore()?.assess;
96
+ if (!sourceContext) return;
97
+
98
+ const [req] = data.args;
99
+ if (!req) return;
100
+
101
+ ['host', 'hostname', 'localAddress', 'protocol'].forEach((key) => {
102
+ const value = getValueFromReq(req, key);
103
+ if (!value) return;
104
+
105
+ const strInfo = tracker.getData(value);
106
+ if (!strInfo) return;
107
+
108
+ if (containsTrustedLib(strInfo.stack)) return;
109
+
110
+ const arg0 = isString(req) ? req : inspect(req);
111
+ const idx = arg0.indexOf(value);
112
+ const urlTags = createAppendTags({}, strInfo.tags, idx);
113
+
114
+ if (urlTags && isVulnerable(UNTRUSTED, safeTags, urlTags)) {
115
+ const event = createSinkEvent({
116
+ name,
117
+ moduleName,
118
+ methodName: 'request',
119
+ context: `${moduleName}.request(${arg0})`,
120
+ history: [strInfo],
121
+ object: {
122
+ tracked: false,
123
+ value: moduleName
124
+ },
125
+ args: [{
126
+ value: arg0,
127
+ tracked: true
128
+ }],
129
+ source: 'P0',
130
+ stacktraceOpts: {
131
+ constructorOpt: data.hooked,
132
+ prependFrames: [data.orig]
133
+ },
134
+ tags: urlTags,
135
+ });
136
+
137
+ if (event) {
138
+ reportFindings({
139
+ ruleId: 'ssrf',
140
+ sinkEvent: event
141
+ });
142
+ }
143
+ }
144
+ });
145
+ }
146
+ });
147
+ });
148
+ });
149
+ };
150
+
151
+ return http;
152
+ };
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2022 Contrast Security, Inc
2
+ * Copyright: 2023 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -31,7 +31,7 @@ const {
31
31
  },
32
32
  Rule: { REFLECTED_XSS: ruleId },
33
33
  } = require('@contrast/common');
34
- const { patchType, filterSafeTags } = require('../common');
34
+ const { patchType, filterSafeTags } = require('../../common');
35
35
 
36
36
  module.exports = function(core) {
37
37
  const {
@@ -52,7 +52,7 @@ module.exports = function(core) {
52
52
  },
53
53
  },
54
54
  } = core;
55
- const http = core.assess.dataflow.sinks.http = {};
55
+ const http = core.assess.dataflow.sinks.http.serverResponse = {};
56
56
 
57
57
  const safeTags = [
58
58
  ALPHANUM_SPACE_HYPHEN,
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2022 Contrast Security, Inc
2
+ * Copyright: 2023 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2022 Contrast Security, Inc
2
+ * Copyright: 2023 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -84,7 +84,7 @@ module.exports = function(core) {
84
84
  let urlPathTags = strInfo.tags;
85
85
 
86
86
  if (url.indexOf('?') > -1) {
87
- urlPathTags = createSubsetTags(strInfo.tags, 0, url.indexOf('?'));
87
+ urlPathTags = createSubsetTags(strInfo.tags, 0, url.indexOf('?') + 1);
88
88
  }
89
89
 
90
90
  if (urlPathTags && isVulnerable(UNTRUSTED, safeTags, urlPathTags)) {
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2022 Contrast Security, Inc
2
+ * Copyright: 2023 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2022 Contrast Security, Inc
2
+ * Copyright: 2023 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -15,7 +15,6 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const util = require('util');
19
18
  const {
20
19
  DataflowTag: {
21
20
  UNTRUSTED,
@@ -28,7 +27,8 @@ const {
28
27
  Rule: { NOSQL_INJECTION_MONGO },
29
28
  isNonEmptyObject,
30
29
  traverseValues,
31
- isString
30
+ isString,
31
+ inspect
32
32
  } = require('@contrast/common');
33
33
  const utils = require('../../tag-utils');
34
34
  const { patchType, filterSafeTags } = require('../common');
@@ -82,20 +82,19 @@ module.exports = function(core) {
82
82
  }
83
83
  } = core;
84
84
 
85
- const inspect = patcher.unwrap(util.inspect);
86
85
  const instr = core.assess.dataflow.sinks.mongodb = {};
87
86
 
88
87
  instr.getQueryVulnerabilityInfo = function getQueryVulnerabilityInfo(query) {
88
+ const reportSafe = [];
89
89
  let vulnInfo = null;
90
- let reportSafe = null;
91
90
 
92
91
  if (isString(query)) {
93
92
  const strInfo = tracker.getData(query);
94
93
  if (strInfo) {
95
94
  if (isVulnerable(UNTRUSTED, querySafeTags, strInfo.tags)) {
96
95
  vulnInfo = { strInfo };
97
- } else {
98
- reportSafe = { strInfo };
96
+ } else if (config.assess.safe_positives.enable) {
97
+ reportSafe.push({ strInfo });
99
98
  }
100
99
  }
101
100
 
@@ -110,8 +109,8 @@ module.exports = function(core) {
110
109
  if (isVulnerable(UNTRUSTED, querySafeTags, strInfo.tags)) {
111
110
  vulnInfo = { path: [...path], strInfo };
112
111
  return true; // halts traversal
113
- } else {
114
- reportSafe = { path: [...path], strInfo };
112
+ } else if (config.assess.safe_positives.enable) {
113
+ reportSafe.push({ path: [...path], strInfo });
115
114
  }
116
115
  }
117
116
  });
@@ -120,8 +119,8 @@ module.exports = function(core) {
120
119
  };
121
120
 
122
121
  instr.getAggregateVulnerabilityInfo = function getAggregateVulnerabilityInfo(aggregation) {
122
+ const reportSafe = [];
123
123
  let vulnInfo = null;
124
- let reportSafe = null;
125
124
 
126
125
  if (!isNonEmptyObject(aggregation)) return { vulnInfo, reportSafe };
127
126
 
@@ -142,8 +141,8 @@ module.exports = function(core) {
142
141
  if (isVulnerable(UNTRUSTED, querySafeTags, strInfo.tags)) {
143
142
  vulnInfo = { path: [...path], strInfo };
144
143
  return true; // halts traversal
145
- } else {
146
- reportSafe = { path: [...path], strInfo };
144
+ } else if (config.assess.safe_positives.enable) {
145
+ reportSafe.push({ path: [...path], strInfo });
147
146
  }
148
147
  }
149
148
  }
@@ -153,16 +152,16 @@ module.exports = function(core) {
153
152
  };
154
153
 
155
154
  instr.getMapReduceVulnerabilityInfo = function getMapReduceVulnerabilityInfo(argToCheck, argIdx) {
155
+ const reportSafe = [];
156
156
  let vulnInfo = null;
157
- let reportSafe = null;
158
157
 
159
158
  if (argIdx !== 2 && isString(argToCheck)) {
160
159
  const strInfo = tracker.getData(argToCheck);
161
160
  if (strInfo) {
162
161
  if (isVulnerable(UNTRUSTED, querySafeTags, strInfo.tags)) {
163
162
  vulnInfo = { strInfo };
164
- } else {
165
- reportSafe = { strInfo };
163
+ } else if (config.assess.safe_positives.enable) {
164
+ reportSafe.push({ strInfo });
166
165
  }
167
166
  }
168
167
 
@@ -182,8 +181,8 @@ module.exports = function(core) {
182
181
  if (isVulnerable(UNTRUSTED, querySafeTags, strInfo.tags)) {
183
182
  vulnInfo = { path: [...path], strInfo };
184
183
  return true; // halts traversal
185
- } else {
186
- reportSafe = { path: [...path], strInfo };
184
+ } else if (config.assess.safe_positives.enable) {
185
+ reportSafe.push({ path: [...path], strInfo });
187
186
  }
188
187
  }
189
188
  }
@@ -193,16 +192,16 @@ module.exports = function(core) {
193
192
  };
194
193
 
195
194
  instr.getGroupVulnerabilityInfo = function getGroupVulnerabilityInfo(argToCheck, argIdx) {
195
+ const reportSafe = [];
196
196
  let vulnInfo = null;
197
- let reportSafe = null;
198
197
 
199
198
  if (argIdx !== 1 && isString(argToCheck)) {
200
199
  const strInfo = tracker.getData(argToCheck);
201
200
  if (strInfo) {
202
201
  if (isVulnerable(UNTRUSTED, querySafeTags, strInfo.tags)) {
203
202
  vulnInfo = { strInfo };
204
- } else {
205
- reportSafe = { strInfo };
203
+ } else if (config.assess.safe_positives.enable) {
204
+ reportSafe.push({ strInfo });
206
205
  }
207
206
  }
208
207
 
@@ -217,8 +216,8 @@ module.exports = function(core) {
217
216
  if (isVulnerable(UNTRUSTED, querySafeTags, strInfo.tags)) {
218
217
  vulnInfo = { path: [...path], strInfo };
219
218
  return true; // halts traversal
220
- } else {
221
- reportSafe = { path: [...path], strInfo };
219
+ } else if (config.assess.safe_positives.enable) {
220
+ reportSafe.push({ path: [...path], strInfo });
222
221
  }
223
222
  }
224
223
  });
@@ -249,14 +248,21 @@ module.exports = function(core) {
249
248
  vulnArgIdx = argIdx;
250
249
  break;
251
250
  }
252
- if (reportSafe) safeReports.push({ ...reportSafe, argIdx });
251
+
252
+ if (config.assess.safe_positives.enable && reportSafe.length) {
253
+ reportSafe.forEach(el => safeReports.push({ ...el, argIdx }));
254
+ }
253
255
  }
254
256
 
255
257
  if (!vulnInfo) {
256
258
  if (safeReports.length && config.assess.safe_positives.enable) {
257
- const safeTags = safeReports.map((report) => filterSafeTags(querySafeTags, report.strInfo));
259
+ const safeTags = safeReports
260
+ .map((report) => filterSafeTags(querySafeTags, report.strInfo))
261
+ .flat()
262
+ .filter((value, index, self) => index === self.indexOf(value));
263
+
258
264
  const strInfo = safeReports.map((report) => {
259
- const tags = report.path ? getAdjustedQueryTags(report.path, report.strInfo, inspect(origArgs[report.argIdx], { depth: 4 })) : report.strInfo?.tags;
265
+ const tags = report.path ? utils.createAdjustedQueryTags(report.path, report.strInfo.tags, report.strInfo.value, inspect(origArgs[report.argIdx], { depth: 4 })) : report.strInfo?.tags;
260
266
 
261
267
  return {
262
268
  value: inspect(origArgs[report.argIdx], { depth: 4 }),
@@ -267,7 +273,7 @@ module.exports = function(core) {
267
273
  reportSafePositive({
268
274
  name,
269
275
  ruleId: NOSQL_INJECTION_MONGO,
270
- safeTags: safeTags.length === 1 ? safeTags[0] : safeTags,
276
+ safeTags,
271
277
  strInfo: strInfo.length === 1 ? strInfo[0] : strInfo
272
278
  });
273
279
  }
@@ -282,7 +288,7 @@ module.exports = function(core) {
282
288
  tracked: idx === vulnArgIdx,
283
289
  }));
284
290
 
285
- const tags = path ? getAdjustedQueryTags(path, strInfo, args[vulnArgIdx].value) : strInfo?.tags;
291
+ const tags = path ? utils.createAdjustedQueryTags(path, strInfo.tags, strInfo.value, args[vulnArgIdx].value) : strInfo?.tags;
286
292
  const resultVal = args[args.length - 1].value.startsWith('[Function') ? '' : 'Promise';
287
293
  const sinkEvent = createSinkEvent({
288
294
  args,
@@ -324,8 +330,6 @@ module.exports = function(core) {
324
330
  });
325
331
  };
326
332
 
327
- return instr;
328
-
329
333
  function patchCollection(mongodb, version) {
330
334
  for (const method of collectionMethods) {
331
335
  const proto = mongodb.Collection.prototype;
@@ -402,21 +406,5 @@ module.exports = function(core) {
402
406
  return name;
403
407
  }
404
408
 
405
- function getAdjustedQueryTags(path, strInfo, argString) {
406
- const { tags } = strInfo;
407
- let idx = -1;
408
- for (const str of [...path, strInfo.value]) {
409
- // This is the case with the `.aggregate` method
410
- // where the argument is an array
411
- if (str === 0) continue;
412
-
413
- idx = argString.indexOf(str, idx);
414
- if (idx == -1) {
415
- idx = -1;
416
- break;
417
- }
418
- }
419
-
420
- return idx > 0 ? utils.createAppendTags([], tags, idx) : strInfo.tags;
421
- }
409
+ return instr;
422
410
  };