@contrast/agent 4.13.1 → 4.14.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.
@@ -13,7 +13,7 @@
13
13
  "source": "P",
14
14
  "target": "R",
15
15
  "command": {
16
- "type": "all"
16
+ "type": "all"
17
17
  }
18
18
  },
19
19
  "url.domainToUnicode": {
@@ -21,7 +21,7 @@
21
21
  "source": "P",
22
22
  "target": "R",
23
23
  "command": {
24
- "type": "all"
24
+ "type": "all"
25
25
  }
26
26
  },
27
27
  "joi": {
@@ -65,6 +65,15 @@
65
65
  "type": "all"
66
66
  }
67
67
  },
68
+ "mysql2/lib/connection.Connection.escape": {
69
+ "enabled": true,
70
+ "source": "P",
71
+ "target": "R",
72
+ "tags": ["sql-encoded"],
73
+ "command": {
74
+ "type": "all"
75
+ }
76
+ },
68
77
  "mustache.escape": {
69
78
  "enabled": true,
70
79
  "provider": "./propagators/mustache/escape.js"
@@ -347,8 +356,8 @@
347
356
  },
348
357
  "process.__add": {
349
358
  "enabled": true,
350
- "source":"P",
351
- "target":"R",
359
+ "source": "P",
360
+ "target": "R",
352
361
  "command": {
353
362
  "type": "append"
354
363
  }
@@ -388,6 +388,48 @@
388
388
  ]
389
389
  }
390
390
  },
391
+ "mysql2/lib/connection.Connection.query": {
392
+ "type": "dataflow",
393
+ "enabled": true,
394
+ "conditions": {
395
+ "mode": "or",
396
+ "args": [
397
+ {
398
+ "index": 0,
399
+ "requiredTags": ["untrusted"],
400
+ "disallowedTags": ["sql-encoded", "limited-chars"]
401
+ },
402
+ {
403
+ "index": 0,
404
+ "depth": 1,
405
+ "exclusiveKeys": ["sql"],
406
+ "requiredTags": ["untrusted"],
407
+ "disallowedTags": ["sql-encoded", "limited-chars"]
408
+ }
409
+ ]
410
+ }
411
+ },
412
+ "mysql2/lib/connection.Connection.execute": {
413
+ "type": "dataflow",
414
+ "enabled": true,
415
+ "conditions": {
416
+ "mode": "or",
417
+ "args": [
418
+ {
419
+ "index": 0,
420
+ "requiredTags": ["untrusted"],
421
+ "disallowedTags": ["sql-encoded", "limited-chars"]
422
+ },
423
+ {
424
+ "index": 0,
425
+ "depth": 1,
426
+ "exclusiveKeys": ["sql"],
427
+ "requiredTags": ["untrusted"],
428
+ "disallowedTags": ["sql-encoded", "limited-chars"]
429
+ }
430
+ ]
431
+ }
432
+ },
391
433
  "pg.Connection.prototype.query": {
392
434
  "type": "dataflow",
393
435
  "enabled": true,
@@ -171,6 +171,24 @@
171
171
  "methodName": "prototype.query",
172
172
  "isModule": true
173
173
  },
174
+ "mysql2/lib/connection.Connection.escape": {
175
+ "moduleName": "mysql2",
176
+ "fileName": "lib/connection.js",
177
+ "methodName": "prototype.escape",
178
+ "isModule": true
179
+ },
180
+ "mysql2/lib/connection.Connection.query": {
181
+ "moduleName": "mysql2",
182
+ "fileName": "lib/connection.js",
183
+ "methodName": "prototype.query",
184
+ "isModule": true
185
+ },
186
+ "mysql2/lib/connection.Connection.execute": {
187
+ "moduleName": "mysql2",
188
+ "fileName": "lib/connection.js",
189
+ "methodName": "prototype.execute",
190
+ "isModule": true
191
+ },
174
192
  "sequelize.prototype.query": {
175
193
  "moduleName": "sequelize",
176
194
  "version": ">=5.0.0",
@@ -32,7 +32,7 @@ requireHook.resolve(
32
32
  patchType: ASSESS_PROPAGATOR,
33
33
  alwaysRun: true,
34
34
  post(data) {
35
- if (!agent.config.agent.trust_custom_validators) return;
35
+ if (!agent.config.assess.trust_custom_validators) return;
36
36
 
37
37
  if (data.result && typeof data.result === 'string') {
38
38
  tracker.untrack(data.result);
@@ -49,7 +49,7 @@ requireHook.resolve(
49
49
  patchType: ASSESS_PROPAGATOR,
50
50
  alwaysRun: true,
51
51
  post(data) {
52
- if (!agent.config.agent.trust_custom_validators) return;
52
+ if (!agent.config.assess.trust_custom_validators) return;
53
53
 
54
54
  data.result.then((result) =>
55
55
  traverseObjectAndUntrack(data.obj, data.args[0], result),
@@ -26,6 +26,12 @@ const TagRange = require('../../models/tag-range');
26
26
  const tagRangeUtil = require('../../models/tag-range/util');
27
27
  const agent = require('../../../agent');
28
28
 
29
+ const areThereRules = (obj) =>
30
+ obj &&
31
+ obj.schema &&
32
+ Array.isArray(obj.schema._rules) &&
33
+ obj.schema._rules.length > 0;
34
+
29
35
  /**
30
36
  * Patch joi.string.validate so that it tags input with string-type-checked if validated
31
37
  * @param {Object} string
@@ -38,11 +44,18 @@ function instrumentJoiString(string) {
38
44
  patchType: ASSESS_PROPAGATOR,
39
45
  post(data) {
40
46
  const trackingData = tracker.getData(data.args[0]);
47
+ if (
48
+ areThereRules(data.args[1]) &&
49
+ data.args[1].schema._rules.find((rule) => rule.name === 'pattern') &&
50
+ !agent.config.assess.trust_custom_validators
51
+ )
52
+ return;
53
+
41
54
  if (data.result === undefined && trackingData) {
42
55
  const { event } = trackingData;
43
56
  trackingData.tagRanges = tagRangeUtil.add(
44
57
  trackingData.tagRanges,
45
- new TagRange(0, data.args[0].length - 1, 'string-type-checked'),
58
+ new TagRange(0, data.args[0].length - 1, 'string-type-checked')
46
59
  );
47
60
  trackingData.event = new PropagationEvent({
48
61
  context: new CallContext(data),
@@ -63,7 +76,7 @@ function instrumentJoiString(string) {
63
76
  post(data) {
64
77
  if (
65
78
  !data.obj.$_terms.externals.length ||
66
- !agent.config.agent.trust_custom_validators
79
+ !agent.config.assess.trust_custom_validators
67
80
  )
68
81
  return;
69
82
 
@@ -77,5 +90,5 @@ function instrumentJoiString(string) {
77
90
 
78
91
  requireHook.resolve(
79
92
  { name: 'joi', file: 'lib/types/string.js', version: '>=17.0.0' },
80
- instrumentJoiString,
93
+ instrumentJoiString
81
94
  );
@@ -52,7 +52,7 @@ requireHook.resolve(
52
52
  (SchemaMap) => {
53
53
  if (
54
54
  !agent.config ||
55
- (agent.config && !agent.config.agent.trust_custom_validators)
55
+ (agent.config && !agent.config.assess.trust_custom_validators)
56
56
  ) {
57
57
  return;
58
58
  }
@@ -61,7 +61,7 @@ requireHook.resolve(
61
61
  (SchemaMap) => {
62
62
  if (
63
63
  !agent.config ||
64
- (agent.config && !agent.config.agent.trust_custom_validators)
64
+ (agent.config && !agent.config.assess.trust_custom_validators)
65
65
  ) {
66
66
  return;
67
67
  }
@@ -101,7 +101,7 @@ requireHook.resolve(
101
101
  (SchemaString) => {
102
102
  if (
103
103
  !agent.config ||
104
- (agent.config && !agent.config.agent.trust_custom_validators)
104
+ (agent.config && !agent.config.assess.trust_custom_validators)
105
105
  ) {
106
106
  return;
107
107
  }
@@ -16,13 +16,15 @@ Copyright: 2022 Contrast Security, Inc
16
16
 
17
17
  const logger = require('../../logger')('contrast:async-storage:hooks');
18
18
  const { AsyncStorage } = require('../index');
19
+ const { Scopes } = require('../scopes');
19
20
  const { ASYNC_CONTEXT } = require('../../../constants').PATCH_TYPES;
20
21
  const requireHook = require('../../../hooks/require');
21
22
  const patcher = require('../../../hooks/patcher');
23
+ const { bindFnArgAtIndex } = require('./utils');
22
24
 
23
25
  module.exports = function init() {
24
26
  // done only to stub these fns for tests
25
- const { patchSequence, patchPool } = module.exports;
27
+ const { patchSequence, patchPool, patchQuery } = module.exports;
26
28
 
27
29
  // this callback _must return_ the patched function to set export
28
30
  requireHook.resolve(
@@ -36,12 +38,20 @@ module.exports = function init() {
36
38
  requireHook.resolve({ name: 'mysql', file: 'lib/Pool.js' }, (Pool) =>
37
39
  patchPool(Pool)
38
40
  );
41
+
42
+ requireHook.resolve(
43
+ { name: 'mysql2', file: 'lib/commands/query.js' },
44
+ (Query) => patchQuery(Query)
45
+ );
39
46
  };
40
47
 
41
48
  module.exports.patchSequence = patchSequence;
42
49
  module.exports.sequencePostHook = sequencePostHook;
43
50
  module.exports.patchPool = patchPool;
44
51
  module.exports.poolPreHook = poolPreHook;
52
+ module.exports.patchQuery = patchQuery;
53
+ module.exports.queryPreHook = queryPreHook;
54
+ module.exports.runInAllowAllScope = runInAllowAllScope;
45
55
 
46
56
  /**
47
57
  * Patches the Sequence constructor which the protocol classes inherit.
@@ -62,12 +72,15 @@ function patchSequence(sequenceCtor) {
62
72
  * Typically in a constructor the data.result would be the instance. But mysql
63
73
  * has the subclasses e.g. Query, do Sequence.call(this, cb). In this case the
64
74
  * data.obj is the instance.
65
- * @param {object} data.obj sequnce instance
75
+ * @param {object} data.obj sequence instance
66
76
  */
67
- function sequencePostHook({ obj }) {
77
+ function sequencePostHook({ obj, funcKey: identifier }) {
78
+ // done only to stub this fn for tests
79
+ const { runInAllowAllScope } = module.exports;
68
80
  try {
69
81
  if (obj && obj._callback && typeof obj._callback === 'function') {
70
- obj._callback = AsyncStorage.bind(obj._callback);
82
+ const cb = obj._callback;
83
+ obj._callback = AsyncStorage.bind(runInAllowAllScope(cb, identifier));
71
84
  }
72
85
  AsyncStorage.getNamespace().bindEmitter(obj);
73
86
  } catch (err) {
@@ -75,6 +88,13 @@ function sequencePostHook({ obj }) {
75
88
  }
76
89
  }
77
90
 
91
+ // Created only for the purpose of testing
92
+ function runInAllowAllScope (cb, identifier) {
93
+ return function (...args) {
94
+ return Scopes.runInAllowAllScope(() => cb.call(this, ...args), identifier);
95
+ };
96
+ }
97
+
78
98
  /**
79
99
  * Patches Pool.prototype.getConnection.
80
100
  * @param {function} poolCtor Pool constructor fn
@@ -92,12 +112,43 @@ function patchPool(poolCtor) {
92
112
  * Binds callback (when present) to cls.
93
113
  * @param {object} data.args getConnection arguments
94
114
  */
95
- function poolPreHook({ args }) {
115
+ function poolPreHook({ args, funcKey: identifier }) {
96
116
  try {
97
117
  if (args.length && typeof args[0] === 'function') {
98
- args[0] = AsyncStorage.bind(args[0]);
118
+ bindFnArgAtIndex({ args, idx: 0, identifier });
99
119
  }
100
120
  } catch (err) {
101
121
  logger.warn('Unable to patch Pool.prototype.getConnection: %o', err);
102
122
  }
103
123
  }
124
+
125
+ /**
126
+ * Patches the Query constructor.
127
+ * This _must return_ the patched value to set the export in require hook.
128
+ * @param {function} queryCtor Query constructor fn
129
+ * @returns {function}
130
+ */
131
+ function patchQuery(queryCtor) {
132
+ return patcher.patch(queryCtor, {
133
+ name: 'mysql2.lib/commands/query.js.Query',
134
+ patchType: ASYNC_CONTEXT,
135
+ alwaysRun: true,
136
+ pre: queryPreHook
137
+ });
138
+ }
139
+
140
+ /**
141
+ * Binds callback (when present) to the context the constructor is called in..
142
+ * @param {object} data the argument for the preHook
143
+ * @param {object} data.args the arguments passed to the Query constructor
144
+ * @param {object} data.funcKey Contrast funcKey identifier for a hooked Query function
145
+ */
146
+ function queryPreHook({ args, funcKey: identifier }) {
147
+ try {
148
+ if (args.length && args[1] && typeof args[1] === 'function') {
149
+ bindFnArgAtIndex({ args, idx: 1, identifier });
150
+ }
151
+ } catch (err) {
152
+ logger.warn('Unable to patch Query constructor: %o', err);
153
+ }
154
+ }
@@ -472,12 +472,6 @@ const agent = [
472
472
  fn: parseNum,
473
473
  desc: 'set limit for stack trace size (larger limits will improve accuracy but increase memory usage)',
474
474
  },
475
- {
476
- name: 'agent.trust_custom_validators',
477
- arg: '<trust-custom-validators>',
478
- default: false,
479
- desc: `trust incoming strings when they pass custom validators (Mongoose, Joi)`,
480
- },
481
475
  {
482
476
  name: 'agent.traverse_and_track',
483
477
  arg: '<traverse-and-track>',
@@ -714,6 +708,12 @@ const assess = [
714
708
  fn: castBoolean,
715
709
  desc: 'if false, disable assess for this agent. A restart is required to re-enable',
716
710
  },
711
+ {
712
+ name: 'assess.trust_custom_validators',
713
+ arg: '<trust-custom-validators>',
714
+ default: false,
715
+ desc: 'trust incoming strings when they pass custom validators (Mongoose, Joi)',
716
+ },
717
717
  {
718
718
  name: 'assess.enable_preflight',
719
719
  arg: '[false]',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/agent",
3
- "version": "4.13.1",
3
+ "version": "4.14.0",
4
4
  "description": "Node.js security instrumentation by Contrast Security",
5
5
  "keywords": [
6
6
  "security",
@@ -166,6 +166,7 @@
166
166
  "mongoose": "^6.1.1",
167
167
  "mustache": "^3.0.1",
168
168
  "mysql": "file:test/mock/mysql",
169
+ "mysql2": "file:test/mock/mysql2",
169
170
  "nock": "^12.0.3",
170
171
  "node-fetch": "^2.6.7",
171
172
  "node-serialize": "file:test/mock/node-serialize",