@contrast/assess 1.7.0 → 1.9.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 (68) hide show
  1. package/lib/dataflow/event-factory.js +17 -13
  2. package/lib/dataflow/propagation/index.js +5 -0
  3. package/lib/dataflow/propagation/install/JSON/index.js +34 -0
  4. package/lib/dataflow/propagation/install/JSON/parse-fn.js +248 -0
  5. package/lib/dataflow/propagation/install/JSON/parse.js +196 -0
  6. package/lib/dataflow/propagation/install/JSON/stringify.js +292 -0
  7. package/lib/dataflow/propagation/install/array-prototype-join.js +21 -14
  8. package/lib/dataflow/propagation/install/buffer.js +81 -0
  9. package/lib/dataflow/propagation/install/contrast-methods/add.js +2 -0
  10. package/lib/dataflow/propagation/install/contrast-methods/index.js +1 -0
  11. package/lib/dataflow/propagation/install/contrast-methods/number.js +58 -0
  12. package/lib/dataflow/propagation/install/contrast-methods/string.js +53 -6
  13. package/lib/dataflow/propagation/install/contrast-methods/tag.js +3 -1
  14. package/lib/dataflow/propagation/install/decode-uri-component.js +9 -2
  15. package/lib/dataflow/propagation/install/ejs/escape-xml.js +8 -2
  16. package/lib/dataflow/propagation/install/encode-uri-component.js +9 -2
  17. package/lib/dataflow/propagation/install/escape-html.js +13 -5
  18. package/lib/dataflow/propagation/install/escape.js +9 -2
  19. package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.js +11 -4
  20. package/lib/dataflow/propagation/install/isnumeric-0.js +59 -0
  21. package/lib/dataflow/propagation/install/mongoose/index.js +36 -0
  22. package/lib/dataflow/propagation/install/mongoose/schema-string.js +156 -0
  23. package/lib/dataflow/propagation/install/mysql-connection-escape.js +5 -0
  24. package/lib/dataflow/propagation/install/parse-int.js +60 -0
  25. package/lib/dataflow/propagation/install/pug-runtime-escape.js +9 -2
  26. package/lib/dataflow/propagation/install/querystring/parse.js +11 -9
  27. package/lib/dataflow/propagation/install/sequelize.js +6 -3
  28. package/lib/dataflow/propagation/install/sql-template-strings.js +9 -2
  29. package/lib/dataflow/propagation/install/string/concat.js +8 -2
  30. package/lib/dataflow/propagation/install/string/format-methods.js +7 -2
  31. package/lib/dataflow/propagation/install/string/html-methods.js +15 -5
  32. package/lib/dataflow/propagation/install/string/match.js +14 -9
  33. package/lib/dataflow/propagation/install/string/replace.js +22 -14
  34. package/lib/dataflow/propagation/install/string/slice.js +13 -5
  35. package/lib/dataflow/propagation/install/string/split.js +15 -11
  36. package/lib/dataflow/propagation/install/string/substring.js +16 -6
  37. package/lib/dataflow/propagation/install/string/trim.js +3 -0
  38. package/lib/dataflow/propagation/install/unescape.js +9 -2
  39. package/lib/dataflow/propagation/install/url/domain-parsers.js +7 -2
  40. package/lib/dataflow/propagation/install/validator/hooks.js +6 -2
  41. package/lib/dataflow/sinks/install/child-process.js +116 -50
  42. package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +6 -3
  43. package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +7 -4
  44. package/lib/dataflow/sinks/install/fs.js +44 -12
  45. package/lib/dataflow/sinks/install/http.js +5 -2
  46. package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +7 -4
  47. package/lib/dataflow/sinks/install/marsdb.js +3 -0
  48. package/lib/dataflow/sinks/install/mongodb.js +249 -149
  49. package/lib/dataflow/sinks/install/mssql.js +9 -2
  50. package/lib/dataflow/sinks/install/mysql.js +9 -4
  51. package/lib/dataflow/sinks/install/postgres.js +6 -3
  52. package/lib/dataflow/sinks/install/sequelize.js +7 -5
  53. package/lib/dataflow/sinks/install/sqlite3.js +7 -3
  54. package/lib/dataflow/sources/handler.js +141 -26
  55. package/lib/dataflow/sources/index.js +2 -7
  56. package/lib/dataflow/sources/install/body-parser1.js +19 -6
  57. package/lib/dataflow/sources/install/express/index.js +4 -1
  58. package/lib/dataflow/sources/install/express/params.js +81 -0
  59. package/lib/dataflow/sources/install/express/parsedUrl.js +87 -0
  60. package/lib/dataflow/sources/install/http.js +33 -19
  61. package/lib/dataflow/sources/install/querystring.js +75 -0
  62. package/lib/dataflow/tag-utils.js +92 -1
  63. package/lib/dataflow/tracker.js +6 -6
  64. package/lib/index.js +2 -0
  65. package/lib/response-scanning/handlers/utils.js +2 -2
  66. package/lib/session-configuration/index.js +34 -0
  67. package/lib/session-configuration/install/http.js +79 -0
  68. package/package.json +2 -2
@@ -47,11 +47,16 @@ const collectionMethods = [
47
47
  'updateMany',
48
48
  'deleteOne',
49
49
  'deleteMany',
50
+ 'aggregate',
51
+ 'mapReduce',
52
+ 'group'
50
53
  ];
51
54
  const dbMethods = [
52
55
  'command',
53
- 'eval'
56
+ 'eval',
57
+ 'aggregate'
54
58
  ];
59
+ const methodsWithNestedCalls = ['findOne', 'eval', 'group'];
55
60
 
56
61
  const querySafeTags = [
57
62
  ALPHANUM_SPACE_HYPHEN,
@@ -80,7 +85,7 @@ module.exports = function(core) {
80
85
  const inspect = patcher.unwrap(util.inspect);
81
86
  const instr = core.assess.dataflow.sinks.mongodb = {};
82
87
 
83
- instr.getVulnerabilityInfo = function getVulnerabilityInfo(query) {
88
+ instr.getQueryVulnerabilityInfo = function getQueryVulnerabilityInfo(query) {
84
89
  let vulnInfo = null;
85
90
  let reportSafe = null;
86
91
 
@@ -103,10 +108,10 @@ module.exports = function(core) {
103
108
  const strInfo = tracker.getData(value);
104
109
  if (strInfo) {
105
110
  if (isVulnerable(UNTRUSTED, querySafeTags, strInfo.tags)) {
106
- vulnInfo = { path, strInfo };
111
+ vulnInfo = { path: [...path], strInfo };
107
112
  return true; // halts traversal
108
113
  } else {
109
- reportSafe = { path, strInfo };
114
+ reportSafe = { path: [...path], strInfo };
110
115
  }
111
116
  }
112
117
  });
@@ -114,6 +119,204 @@ module.exports = function(core) {
114
119
  return { vulnInfo, reportSafe };
115
120
  };
116
121
 
122
+ instr.getAggregateVulnerabilityInfo = function getAggregateVulnerabilityInfo(aggregation) {
123
+ let vulnInfo = null;
124
+ let reportSafe = null;
125
+
126
+ if (!isNonEmptyObject(aggregation)) return { vulnInfo, reportSafe };
127
+
128
+ traverseValues(aggregation, (path, _type, value) => {
129
+ const accumulatorFuncProps = [
130
+ 'init',
131
+ 'merge',
132
+ 'accumulate',
133
+ 'finalize'
134
+ ];
135
+ const lastIdx = path.length - 1;
136
+ if (
137
+ (path[lastIdx - 1] === '$function' && path[lastIdx] === 'body') ||
138
+ (path[lastIdx - 1] === '$accumulator' && accumulatorFuncProps.includes(path[lastIdx]))
139
+ ) {
140
+ const strInfo = tracker.getData(value);
141
+ if (strInfo) {
142
+ if (isVulnerable(UNTRUSTED, querySafeTags, strInfo.tags)) {
143
+ vulnInfo = { path: [...path], strInfo };
144
+ return true; // halts traversal
145
+ } else {
146
+ reportSafe = { path: [...path], strInfo };
147
+ }
148
+ }
149
+ }
150
+ });
151
+
152
+ return { vulnInfo, reportSafe };
153
+ };
154
+
155
+ instr.getMapReduceVulnerabilityInfo = function getMapReduceVulnerabilityInfo(argToCheck, argIdx) {
156
+ let vulnInfo = null;
157
+ let reportSafe = null;
158
+
159
+ if (argIdx !== 2 && isString(argToCheck)) {
160
+ const strInfo = tracker.getData(argToCheck);
161
+ if (strInfo) {
162
+ if (isVulnerable(UNTRUSTED, querySafeTags, strInfo.tags)) {
163
+ vulnInfo = { strInfo };
164
+ } else {
165
+ reportSafe = { strInfo };
166
+ }
167
+ }
168
+
169
+ return { vulnInfo, reportSafe };
170
+ }
171
+
172
+ if (!isNonEmptyObject(argToCheck)) return { vulnInfo, reportSafe };
173
+
174
+ traverseValues(argToCheck, (path, _type, value) => {
175
+ const vulnerableProps = [
176
+ 'query',
177
+ 'finalize',
178
+ ];
179
+ if (vulnerableProps.includes(path[0])) {
180
+ const strInfo = tracker.getData(value);
181
+ if (strInfo) {
182
+ if (isVulnerable(UNTRUSTED, querySafeTags, strInfo.tags)) {
183
+ vulnInfo = { path: [...path], strInfo };
184
+ return true; // halts traversal
185
+ } else {
186
+ reportSafe = { path: [...path], strInfo };
187
+ }
188
+ }
189
+ }
190
+ });
191
+
192
+ return { vulnInfo, reportSafe };
193
+ };
194
+
195
+ instr.getGroupVulnerabilityInfo = function getGroupVulnerabilityInfo(argToCheck, argIdx) {
196
+ let vulnInfo = null;
197
+ let reportSafe = null;
198
+
199
+ if (argIdx !== 1 && isString(argToCheck)) {
200
+ const strInfo = tracker.getData(argToCheck);
201
+ if (strInfo) {
202
+ if (isVulnerable(UNTRUSTED, querySafeTags, strInfo.tags)) {
203
+ vulnInfo = { strInfo };
204
+ } else {
205
+ reportSafe = { strInfo };
206
+ }
207
+ }
208
+
209
+ return { vulnInfo, reportSafe };
210
+ }
211
+
212
+ if (!isNonEmptyObject(argToCheck)) return { vulnInfo, reportSafe };
213
+
214
+ traverseValues(argToCheck, (path, _type, value) => {
215
+ const strInfo = tracker.getData(value);
216
+ if (strInfo) {
217
+ if (isVulnerable(UNTRUSTED, querySafeTags, strInfo.tags)) {
218
+ vulnInfo = { path: [...path], strInfo };
219
+ return true; // halts traversal
220
+ } else {
221
+ reportSafe = { path: [...path], strInfo };
222
+ }
223
+ }
224
+ });
225
+
226
+ return { vulnInfo, reportSafe };
227
+ };
228
+
229
+ function createAroundHook(entity, name, method, getInfoMethod, vulnerableArgIdxs) {
230
+ const argsIdxsToCheck = vulnerableArgIdxs || [0];
231
+ return function(next, data) {
232
+ const { obj, args: origArgs } = data;
233
+ const sourceCtx = sources.getStore()?.assess;
234
+
235
+ if (isLocked(NOSQL_INJECTION_MONGO) || instrumentation.isLocked() || !sourceCtx) {
236
+ return next();
237
+ }
238
+
239
+ let vulnInfo;
240
+ let reportSafe;
241
+ let vulnArgIdx;
242
+ const safeReports = [];
243
+
244
+ try {
245
+ for (const argIdx of argsIdxsToCheck) {
246
+ ({ vulnInfo, reportSafe } = getInfoMethod(origArgs[argIdx], argIdx));
247
+
248
+ if (vulnInfo) {
249
+ vulnArgIdx = argIdx;
250
+ break;
251
+ }
252
+ if (reportSafe) safeReports.push({ ...reportSafe, argIdx });
253
+ }
254
+
255
+ if (!vulnInfo) {
256
+ if (safeReports.length && config.assess.safe_positives.enable) {
257
+ const safeTags = safeReports.map((report) => filterSafeTags(querySafeTags, report.strInfo));
258
+ const strInfo = safeReports.map((report) => {
259
+ const tags = report.path ? getAdjustedQueryTags(report.path, report.strInfo, inspect(origArgs[report.argIdx], { depth: 4 })) : report.strInfo?.tags;
260
+
261
+ return {
262
+ value: inspect(origArgs[report.argIdx], { depth: 4 }),
263
+ tags
264
+ };
265
+ });
266
+
267
+ reportSafePositive({
268
+ name,
269
+ ruleId: NOSQL_INJECTION_MONGO,
270
+ safeTags: safeTags.length === 1 ? safeTags[0] : safeTags,
271
+ strInfo: strInfo.length === 1 ? strInfo[0] : strInfo
272
+ });
273
+ }
274
+
275
+ return methodsWithNestedCalls.includes(method) ? runInActiveSink(NOSQL_INJECTION_MONGO, async () => await next()) : next();
276
+ }
277
+
278
+ const { path, strInfo } = vulnInfo;
279
+ const objName = getObjectName(obj, entity);
280
+ const args = origArgs.map((arg, idx) => ({
281
+ value: isString(arg) ? arg : inspect(arg, { depth: 4 }),
282
+ tracked: idx === vulnArgIdx,
283
+ }));
284
+
285
+ const tags = path ? getAdjustedQueryTags(path, strInfo, args[vulnArgIdx].value) : strInfo?.tags;
286
+ const resultVal = args[args.length - 1].value.startsWith('[Function') ? '' : 'Promise';
287
+ const sinkEvent = createSinkEvent({
288
+ args,
289
+ context: `${objName}.${method}(${args.map((a, idx) => isString(origArgs[idx]) ? `'${a.value}'` : a.value)})`,
290
+ history: [strInfo],
291
+ object: {
292
+ tracked: false,
293
+ value: `mongodb.${entity}`,
294
+ },
295
+ name,
296
+ moduleName: 'mongodb',
297
+ methodName: `${entity}.prototype.${method}`,
298
+ result: {
299
+ tracked: false,
300
+ value: resultVal,
301
+ },
302
+ source: `P${vulnArgIdx}`,
303
+ stacktraceOpts: {
304
+ constructorOpt: data.hooked,
305
+ },
306
+ tags,
307
+ });
308
+
309
+ if (sinkEvent) {
310
+ reportFindings({ ruleId: NOSQL_INJECTION_MONGO, sinkEvent });
311
+ }
312
+ } catch (err) {
313
+ core.logger.error({ name, err }, 'assess sink analysis failed');
314
+ }
315
+
316
+ return methodsWithNestedCalls.includes(method) ? runInActiveSink(NOSQL_INJECTION_MONGO, async () => await next()) : next();
317
+ };
318
+ }
319
+
117
320
  instr.install = function() {
118
321
  depHooks.resolve({ name: 'mongodb' }, (mongodb, version) => {
119
322
  patchCollection(mongodb, version);
@@ -133,78 +336,31 @@ module.exports = function(core) {
133
336
  continue;
134
337
  }
135
338
 
136
- patcher.patch(proto, method, {
137
- name,
138
- patchType,
139
- around(next, data) {
140
- const { obj, args: origArgs } = data;
141
- const sourceCtx = sources.getStore()?.assess;
142
-
143
- if (isLocked(NOSQL_INJECTION_MONGO) || instrumentation.isLocked() || !sourceCtx) {
144
- return next();
145
- }
146
-
147
- const argIdx = 0;
148
- try {
149
- const { vulnInfo, reportSafe } = instr.getVulnerabilityInfo(origArgs[argIdx]);
150
-
151
- if (!vulnInfo) {
152
- reportSafe && config.assess.safe_positives.enable && reportSafePositive({
153
- name,
154
- ruleId: NOSQL_INJECTION_MONGO,
155
- safeTags: filterSafeTags(querySafeTags, reportSafe.strInfo),
156
- strInfo: {
157
- value: inspect(origArgs[argIdx]),
158
- tags: getAdjustedQueryTags(reportSafe.path, reportSafe.strInfo, inspect(origArgs[argIdx])),
159
- },
160
- });
161
- return method === 'findOne' ? runInActiveSink(NOSQL_INJECTION_MONGO, async () => await next()) : next();
162
- }
163
-
164
- const { path, strInfo } = vulnInfo;
165
- const objName = getObjectName(obj);
166
- const args = origArgs.map((arg, idx) => ({
167
- value: inspect(arg),
168
- tracked: idx === argIdx,
169
- }));
170
-
171
- const tags = getAdjustedQueryTags(path, strInfo, args[argIdx].value);
172
- const resultVal = args[args.length - 1].value.startsWith('[Function') ? '' : 'Promise';
173
- const sinkEvent = createSinkEvent({
174
- args,
175
- context: `${objName}.${method}(${args.map((a) => a.value)})`,
176
- history: [strInfo],
177
- object: {
178
- tracked: false,
179
- value: 'mongodb.Collection',
180
- },
181
- name,
182
- result: {
183
- tracked: false,
184
- value: resultVal,
185
- },
186
- source: `P${argIdx}`,
187
- stacktraceOpts: {
188
- constructorOpt: data.hooked,
189
- },
190
- tags,
191
- });
192
-
193
- if (sinkEvent) {
194
- reportFindings({ ruleId: NOSQL_INJECTION_MONGO, sinkEvent });
195
- }
196
- } catch (err) {
197
- core.logger.error({ name, err }, 'assess sink analysis failed');
198
- }
199
-
200
- if (method === 'findOne') {
201
- // `findOne` will call `find` so don't analyze in nested call
202
- return runInActiveSink(NOSQL_INJECTION_MONGO, async () => await next());
203
- }
204
-
205
- return next();
206
- },
207
- });
339
+ if (method === 'aggregate') {
340
+ patcher.patch(proto, method, {
341
+ name,
342
+ patchType,
343
+ around: createAroundHook('Collection', name, method, instr.getAggregateVulnerabilityInfo)
344
+ });
345
+ } else if (method === 'mapReduce') {
346
+ patcher.patch(proto, method, {
347
+ name,
348
+ patchType,
349
+ around: createAroundHook('Collection', name, method, instr.getMapReduceVulnerabilityInfo, [0, 1, 2])
350
+ });
351
+ } else if (method === 'group') {
352
+ patcher.patch(proto, method, {
353
+ name,
354
+ patchType,
355
+ around: createAroundHook('Collection', name, method, instr.getGroupVulnerabilityInfo, [0, 1, 3])
356
+ });
357
+ } else {
358
+ patcher.patch(proto, method, {
359
+ name,
360
+ patchType,
361
+ around: createAroundHook('Collection', name, method, instr.getQueryVulnerabilityInfo)
362
+ });
363
+ }
208
364
  }
209
365
  }
210
366
 
@@ -218,79 +374,19 @@ module.exports = function(core) {
218
374
  continue;
219
375
  }
220
376
 
221
- patcher.patch(proto, method, {
222
- name,
223
- patchType,
224
- around(next, data) {
225
- const { obj, args: origArgs } = data;
226
- const sourceCtx = sources.getStore()?.assess;
227
-
228
- if (isLocked(NOSQL_INJECTION_MONGO) || instrumentation.isLocked() || !sourceCtx) {
229
- return next();
230
- }
231
-
232
- const argIdx = 0;
233
- try {
234
- const { vulnInfo, reportSafe } = instr.getVulnerabilityInfo(origArgs[argIdx]);
235
-
236
- if (!vulnInfo) {
237
- const tags = reportSafe?.path ? getAdjustedQueryTags(reportSafe.path, reportSafe.strInfo, inspect(origArgs[argIdx])) : reportSafe?.strInfo?.tags;
238
- reportSafe && config.assess.safe_positives.enable && reportSafePositive({
239
- name,
240
- ruleId: NOSQL_INJECTION_MONGO,
241
- safeTags: filterSafeTags(querySafeTags, reportSafe.strInfo),
242
- strInfo: {
243
- value: inspect(origArgs[argIdx]),
244
- tags
245
- },
246
- });
247
- return method === 'eval' ? runInActiveSink(NOSQL_INJECTION_MONGO, async () => await next()) : next();
248
- }
249
-
250
- const { path, strInfo } = vulnInfo;
251
- const objName = getObjectName(obj, 'Db');
252
- const args = origArgs.map((arg, idx) => ({
253
- value: isString(arg) ? arg : inspect(arg),
254
- tracked: idx === argIdx,
255
- }));
256
-
257
- const tags = path ? getAdjustedQueryTags(path, strInfo, args[argIdx].value) : strInfo?.tags;
258
- const resultVal = args[args.length - 1].value.startsWith('[Function') ? '' : 'Promise';
259
- const sinkEvent = createSinkEvent({
260
- args,
261
- context: `${objName}.${method}(${args.map((a) => a.value)})`,
262
- history: [strInfo],
263
- object: {
264
- tracked: false,
265
- value: 'mongodb.Db',
266
- },
267
- name,
268
- result: {
269
- tracked: false,
270
- value: resultVal,
271
- },
272
- source: `P${argIdx}`,
273
- stacktraceOpts: {
274
- constructorOpt: data.hooked,
275
- },
276
- tags,
277
- });
278
-
279
- if (sinkEvent) {
280
- reportFindings({ ruleId: NOSQL_INJECTION_MONGO, sinkEvent });
281
- }
282
- } catch (err) {
283
- core.logger.error({ name, err }, 'assess sink analysis failed');
284
- }
285
-
286
- if (method === 'eval') {
287
- // `eval` will call `command` so don't analyze in nested call
288
- return runInActiveSink(NOSQL_INJECTION_MONGO, async () => await next());
289
- }
290
-
291
- return next();
292
- },
293
- });
377
+ if (method === 'aggregate') {
378
+ patcher.patch(proto, method, {
379
+ name,
380
+ patchType,
381
+ around: createAroundHook('Db', name, method, instr.getAggregateVulnerabilityInfo)
382
+ });
383
+ } else {
384
+ patcher.patch(proto, method, {
385
+ name,
386
+ patchType,
387
+ around: createAroundHook('Db', name, method, instr.getQueryVulnerabilityInfo)
388
+ });
389
+ }
294
390
  }
295
391
  }
296
392
 
@@ -310,6 +406,10 @@ module.exports = function(core) {
310
406
  const { tags } = strInfo;
311
407
  let idx = -1;
312
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
+
313
413
  idx = argString.indexOf(str, idx);
314
414
  if (idx == -1) {
315
415
  idx = -1;
@@ -30,7 +30,7 @@ const safeTags = [
30
30
  CUSTOM_ENCODED,
31
31
  ];
32
32
 
33
- module.exports = function (core) {
33
+ module.exports = function(core) {
34
34
  const {
35
35
  depHooks,
36
36
  patcher,
@@ -44,7 +44,7 @@ module.exports = function (core) {
44
44
  },
45
45
  } = core;
46
46
 
47
- const pre = (name, obj, version) => (data) => {
47
+ const pre = (name, method, obj, version) => (data) => {
48
48
  const store = sources.getStore()?.assess;
49
49
  if (
50
50
  !store ||
@@ -60,6 +60,9 @@ module.exports = function (core) {
60
60
 
61
61
  const event = createSinkEvent({
62
62
  name,
63
+ moduleName: 'mssql',
64
+ methodName: `PreparedStatement.prototype.${method}`,
65
+ context: `mssql.PreparedStatement.${method}('${strInfo.value}')`,
63
66
  history: [strInfo],
64
67
  object: {
65
68
  value: `[${createModuleLabel('mssql', version)}].${obj}`,
@@ -75,6 +78,7 @@ module.exports = function (core) {
75
78
  source: 'P0',
76
79
  stacktraceOpts: {
77
80
  contructorOpt: data.hooked,
81
+ prependFrames: [data.orig]
78
82
  },
79
83
  });
80
84
 
@@ -96,6 +100,7 @@ module.exports = function (core) {
96
100
  patchType,
97
101
  pre: pre(
98
102
  'mssql/lib/base/prepared-statement.prototype.prepare',
103
+ 'prepare',
99
104
  'PreparedStatement',
100
105
  version,
101
106
  ),
@@ -111,6 +116,7 @@ module.exports = function (core) {
111
116
  patchType,
112
117
  pre: pre(
113
118
  'mssql/lib/base/request.prototype.batch',
119
+ 'batch',
114
120
  'Request',
115
121
  version,
116
122
  ),
@@ -121,6 +127,7 @@ module.exports = function (core) {
121
127
  patchType,
122
128
  pre: pre(
123
129
  'mssql/lib/base/request.prototype.query',
130
+ 'query',
124
131
  'Request',
125
132
  version,
126
133
  ),
@@ -28,6 +28,7 @@ const {
28
28
  LIMITED_CHARS,
29
29
  UNTRUSTED
30
30
  },
31
+ inspect
31
32
  } = require('@contrast/common');
32
33
 
33
34
  const safeTags = [
@@ -39,7 +40,7 @@ const safeTags = [
39
40
  LIMITED_CHARS,
40
41
  ];
41
42
 
42
- module.exports = function (core) {
43
+ module.exports = function(core) {
43
44
  const {
44
45
  depHooks,
45
46
  patcher,
@@ -63,7 +64,7 @@ module.exports = function (core) {
63
64
  }
64
65
  }
65
66
 
66
- const pre = (module, file, obj) => (data) => {
67
+ const pre = (module, file, obj, method) => (data) => {
67
68
  const store = sources.getStore()?.assess;
68
69
  if (
69
70
  !store ||
@@ -81,6 +82,9 @@ module.exports = function (core) {
81
82
 
82
83
  const event = createSinkEvent({
83
84
  name: `${module}/${file}`,
85
+ moduleName: module,
86
+ methodName: `prototype.${method}`,
87
+ context: `${module}.${method}(${inspect(data.args[0])})`,
84
88
  history: [strInfo],
85
89
  object: {
86
90
  value: `${module}.${obj}`,
@@ -96,6 +100,7 @@ module.exports = function (core) {
96
100
  source: 'P0',
97
101
  stacktraceOpts: {
98
102
  contructorOpt: data.hooked,
103
+ prependFrames: [data.orig]
99
104
  },
100
105
  });
101
106
 
@@ -115,7 +120,7 @@ module.exports = function (core) {
115
120
  patcher.patch(Connection.prototype, 'query', {
116
121
  name: 'Connection.prototype.query',
117
122
  patchType,
118
- pre: pre('mysql', 'lib/Connection.query', 'Connection')
123
+ pre: pre('mysql', 'lib/Connection.query', 'Connection', 'query')
119
124
  });
120
125
  },
121
126
  );
@@ -126,7 +131,7 @@ module.exports = function (core) {
126
131
  patcher.patch(connection.prototype, `${method}`, {
127
132
  name: `connection.prototype.${method}`,
128
133
  patchType,
129
- pre: pre('mysql2', `lib/connection.Connection.${method}`, 'connection')
134
+ pre: pre('mysql2', `lib/connection.Connection.${method}`, 'connection', method)
130
135
  });
131
136
  });
132
137
  },
@@ -19,11 +19,11 @@ const util = require('util');
19
19
  const {
20
20
  DataflowTag: { UNTRUSTED, SQL_ENCODED, LIMITED_CHARS, CUSTOM_VALIDATED, CUSTOM_ENCODED },
21
21
  Rule: { SQL_INJECTION: ruleId },
22
- isString
22
+ isString,
23
23
  } = require('@contrast/common');
24
24
  const { filterSafeTags, patchType } = require('../common');
25
25
 
26
- module.exports = function (core) {
26
+ module.exports = function(core) {
27
27
  const {
28
28
  config,
29
29
  depHooks,
@@ -74,6 +74,8 @@ module.exports = function (core) {
74
74
  context: `${objValue}.query(${arg0Val})`,
75
75
  history: [strInfo],
76
76
  name: methodSignature,
77
+ moduleName: `${methodSignature.includes('pool') ? 'pg-pool' : 'pg'}`,
78
+ methodName: 'Connection.prototype.query',
77
79
  object: {
78
80
  tracked: false,
79
81
  value: objValue,
@@ -86,6 +88,7 @@ module.exports = function (core) {
86
88
  source: 'P0',
87
89
  stacktraceOpts: {
88
90
  constructorOpt: data.hooked,
91
+ prependFrames: [data.orig]
89
92
  },
90
93
  });
91
94
 
@@ -108,7 +111,7 @@ module.exports = function (core) {
108
111
  }
109
112
  };
110
113
 
111
- postgres.install = function () {
114
+ postgres.install = function() {
112
115
  const pgClientQueryPatchName = 'pg.Client.prototype.query';
113
116
  depHooks.resolve(
114
117
  { name: 'pg', file: 'lib/client.js' },
@@ -28,7 +28,7 @@ const {
28
28
  } = require('@contrast/common');
29
29
  const { patchType, filterSafeTags } = require('../common');
30
30
 
31
- module.exports = function (core) {
31
+ module.exports = function(core) {
32
32
  const {
33
33
  depHooks,
34
34
  patcher,
@@ -54,7 +54,7 @@ module.exports = function (core) {
54
54
 
55
55
  const sequelize = (core.assess.dataflow.sinks.sequelize = {});
56
56
 
57
- sequelize.install = function () {
57
+ sequelize.install = function() {
58
58
  const sequelizeQueryPatchName = 'sequelize.prototype.query';
59
59
  depHooks.resolve({ name: 'sequelize' }, (sequelize) => {
60
60
  patcher.patch(sequelize.prototype, 'query', {
@@ -69,7 +69,7 @@ module.exports = function (core) {
69
69
 
70
70
  try {
71
71
  const queryInfo = tracker.getData(query);
72
- const isVulnerableQuery = isVulnerable(requiredTag, safeTags, queryInfo.tags);
72
+ const isVulnerableQuery = queryInfo && isVulnerable(requiredTag, safeTags, queryInfo.tags);
73
73
 
74
74
  if (queryInfo && !isVulnerableQuery && config.assess.safe_positives.enable) {
75
75
  reportSafePositive({
@@ -94,8 +94,8 @@ module.exports = function (core) {
94
94
  typeof args[0] === 'string' ? args[0] : inspect(args[0]);
95
95
  const inspectedOptions = args[1] ? inspect(args[1]) : '';
96
96
  const contextArgs = args[1]
97
- ? `${sqlValue}, ${inspectedOptions}`
98
- : sqlValue;
97
+ ? `'${sqlValue}', ${inspectedOptions}`
98
+ : `'${sqlValue}'`;
99
99
 
100
100
  const reportedArgs = [{ value: sqlValue, tracked: true }];
101
101
  args[1] &&
@@ -104,6 +104,8 @@ module.exports = function (core) {
104
104
  const event = createSinkEvent({
105
105
  context: `sequelize.prototype.query(${contextArgs})`,
106
106
  name: sequelizeQueryPatchName,
107
+ moduleName: 'sequelize',
108
+ methodName: 'prototype.query',
107
109
  history: [queryInfo],
108
110
  object: {
109
111
  value: 'sequelize.prototype',
@@ -29,7 +29,7 @@ const safeTags = [
29
29
  CUSTOM_ENCODED,
30
30
  ];
31
31
 
32
- module.exports = function (core) {
32
+ module.exports = function(core) {
33
33
  const {
34
34
  depHooks,
35
35
  patcher,
@@ -43,7 +43,7 @@ module.exports = function (core) {
43
43
  },
44
44
  } = core;
45
45
 
46
- const pre = (name) => (data) => {
46
+ const pre = (name, method) => (data) => {
47
47
  const store = sources.getStore()?.assess;
48
48
  if (
49
49
  !store ||
@@ -59,6 +59,9 @@ module.exports = function (core) {
59
59
 
60
60
  const event = createSinkEvent({
61
61
  name,
62
+ moduleName: 'sqlite3',
63
+ methodName: `Database.prototype.${method}`,
64
+ context: `db.${method}('${strInfo.value}')`,
62
65
  history: [strInfo],
63
66
  object: {
64
67
  value: '[Module<sqlite3>].Database',
@@ -74,6 +77,7 @@ module.exports = function (core) {
74
77
  source: 'P0',
75
78
  stacktraceOpts: {
76
79
  contructorOpt: data.hooked,
80
+ prependFrames: [data.orig]
77
81
  },
78
82
  });
79
83
 
@@ -93,7 +97,7 @@ module.exports = function (core) {
93
97
  patcher.patch(sqlite3.Database.prototype, method, {
94
98
  name,
95
99
  patchType,
96
- pre: pre(name)
100
+ pre: pre(name, method)
97
101
  });
98
102
  });
99
103
  });