@contrast/agent 4.12.0 → 4.13.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 (41) hide show
  1. package/bin/VERSION +1 -1
  2. package/bin/linux/contrast-service +0 -0
  3. package/bin/mac/contrast-service +0 -0
  4. package/bin/windows/contrast-service.exe +0 -0
  5. package/esm.mjs +1 -32
  6. package/lib/assess/models/base-event.js +1 -1
  7. package/lib/assess/sinks/dynamo.js +65 -30
  8. package/lib/assess/static/read-findings-from-cache.js +40 -0
  9. package/lib/assess/technologies/index.js +12 -13
  10. package/lib/cli-rewriter/index.js +65 -6
  11. package/lib/contrast.js +1 -2
  12. package/lib/core/config/options.js +6 -0
  13. package/lib/core/config/util.js +15 -33
  14. package/lib/core/exclusions/input.js +6 -1
  15. package/lib/core/express/index.js +2 -4
  16. package/lib/hooks/http.js +81 -81
  17. package/lib/hooks/require.js +1 -0
  18. package/lib/instrumentation.js +17 -0
  19. package/lib/protect/errors/handler-async-errors.js +66 -0
  20. package/lib/protect/input-analysis.js +7 -13
  21. package/lib/protect/listeners.js +27 -23
  22. package/lib/protect/rules/base-scanner/index.js +2 -2
  23. package/lib/protect/rules/bot-blocker/bot-blocker-rule.js +4 -2
  24. package/lib/protect/rules/cmd-injection/cmdinjection-rule.js +57 -2
  25. package/lib/protect/rules/cmd-injection-semantic-chained-commands/cmd-injection-semantic-chained-commands-rule.js +31 -2
  26. package/lib/protect/rules/cmd-injection-semantic-dangerous-paths/cmd-injection-semantic-dangerous-paths-rule.js +32 -2
  27. package/lib/protect/rules/index.js +42 -21
  28. package/lib/protect/rules/ip-denylist/ip-denylist-rule.js +2 -2
  29. package/lib/protect/rules/nosqli/nosql-injection-rule.js +103 -38
  30. package/lib/protect/rules/path-traversal/path-traversal-rule.js +3 -0
  31. package/lib/protect/rules/rule-factory.js +6 -6
  32. package/lib/protect/rules/signatures/signature.js +3 -0
  33. package/lib/protect/rules/sqli/sql-injection-rule.js +98 -5
  34. package/lib/protect/rules/sqli/sql-scanner/labels.json +0 -3
  35. package/lib/protect/rules/xss/reflected-xss-rule.js +3 -3
  36. package/lib/protect/sample-aggregator.js +65 -57
  37. package/lib/protect/service.js +709 -104
  38. package/lib/reporter/models/app-activity/sample.js +6 -0
  39. package/lib/reporter/ts-reporter.js +1 -1
  40. package/lib/util/get-file-type.js +47 -0
  41. package/package.json +5 -3
package/bin/VERSION CHANGED
@@ -1 +1 @@
1
- 2.28.14
1
+ 2.28.17
Binary file
Binary file
Binary file
package/esm.mjs CHANGED
@@ -24,45 +24,14 @@ if (enabled) {
24
24
  await loader.resetArgs(process.argv[0], process.argv[1]);
25
25
  const { readFile } = require('fs').promises;
26
26
 
27
- const path = require('path');
28
27
  const agent = require(`./lib/agent.js`);
29
28
  const logger = require(`./lib/core/logger/index.js`)('contrast:esm-loaders');
30
29
  const rewriter = require(`./lib/core/rewrite/index.js`)(agent);
31
30
  const helpers = require(`./lib/hooks/module/helpers.js`);
32
- const parent = require('parent-package-json');
31
+ const getType = require(`./lib/util/get-file-type.js`);
33
32
 
34
33
  const loadedFromCache = new Set();
35
34
 
36
- function getType(url) {
37
- const {protocol, pathname} = new URL(url);
38
-
39
- let parentType = 'commonjs';
40
- try {
41
- parentType = parent(pathname).parse().type;
42
- } catch (err) {
43
- // Node assumes `commonjs ` if there's no `type` set in package.json
44
- }
45
-
46
- if (protocol === 'node:') {
47
- return 'builtin';
48
- }
49
- if (protocol === 'file:') {
50
- const ext = path.extname(pathname);
51
- if (
52
- ext === '.mjs' ||
53
- (ext === '.js' && parentType === 'module')
54
- ){
55
- return 'module';
56
- }
57
- else if (
58
- ext === '.cjs' ||
59
- (ext === '.js' && parentType !== 'module')
60
- ){
61
- return 'commonjs';
62
- }
63
- }
64
- return 'unknown';
65
- }
66
35
  /**
67
36
  * The `getSource` hook is used to provide a custom method for retrieving source
68
37
  * code. In our case, we check for previously rewritten ESM files in our cache
@@ -83,7 +83,7 @@ class BaseEvent {
83
83
  this.parents = sortEvents(this.parents);
84
84
 
85
85
  this.parents.forEach((p) => {
86
- if (!set.has(p)) {
86
+ if (p && !set.has(p)) {
87
87
  set = p.getAllParents(set);
88
88
  set.add(p);
89
89
  }
@@ -23,15 +23,20 @@ const ruleId = 'nosql-injection-dynamodb';
23
23
  const disallowedTags = [
24
24
  'limited-chars',
25
25
  'alphanum-space-hyphen',
26
- 'string-type-checked'
26
+ 'string-type-checked',
27
27
  ];
28
28
  const requiredTags = ['untrusted'];
29
29
  const moduleName = 'aws-sdk';
30
- const relevantKeys = [
31
- 'ExpressionAttributeValues',
32
- 'ExclusiveStartKey',
33
- 'ScanFilter'
34
- ];
30
+ const moduleNameV3 = '@aws-sdk/client-dynamodb';
31
+ const relevantKeys = {
32
+ v2: ['ExpressionAttributeValues', 'ExclusiveStartKey', 'ScanFilter'],
33
+ v3: [
34
+ 'ComparisonOperator',
35
+ 'FilterExpression',
36
+ 'ProjectionExpression',
37
+ 'ScanFilter',
38
+ ],
39
+ };
35
40
  const requests = new WeakSet();
36
41
 
37
42
  // map data types to methods for extracting
@@ -46,24 +51,27 @@ const dataTypes = {
46
51
  M: 'object',
47
52
  L: 'collection',
48
53
  NULL: 'value',
49
- BOOL: 'value'
54
+ BOOL: 'value',
50
55
  };
51
56
 
52
57
  /**
53
58
  * Extracts all values from either a dynamo document client or client
54
59
  *
55
60
  * @param {Object} payload dynamo scan payload
56
- * @param {string} mode client or docClient
61
+ * @param {string} mode client, docClient or command (for aws sdk v3)
62
+ * @param {string} version DynamoDB SDK version
57
63
  * @return {Array} all string values from payload
58
64
  */
59
- function extractValues(payload = {}, mode) {
65
+ function extractValues(payload = {}, mode = null, version = 'v2') {
60
66
  return _.flatten(
61
- relevantKeys.map((key) => {
67
+ relevantKeys[version].map((key) => {
68
+ if (payload[key] === undefined) return;
69
+ if (typeof payload[key] === 'string') return payload[key];
62
70
  const values = _.values(payload[key]);
63
71
  const extractionMethod =
64
72
  mode === 'client' ? findTypedValues.bind(this) : findValues.bind(this);
65
73
  return _.flattenDeep(extractionMethod(values));
66
- })
74
+ }),
67
75
  );
68
76
  }
69
77
 
@@ -76,7 +84,7 @@ function extractValues(payload = {}, mode) {
76
84
  */
77
85
  function findTypedValues(values) {
78
86
  return _.map(values, (value) => {
79
- const type = Object.keys(value)[0];
87
+ const [type] = Object.keys(value);
80
88
  switch (dataTypes[type]) {
81
89
  case 'value':
82
90
  return value[type];
@@ -106,13 +114,13 @@ function notTrackable(value) {
106
114
  }
107
115
 
108
116
  /**
109
- * Extracts all strings from a dynanmo document client payload
117
+ * Extracts all strings from a dynamo document client payload
110
118
  *
111
119
  * Note: There are other data types dynamo supports
112
- * to cast to AttributeValue types but we currently
120
+ * to cast to AttributeValue types, but we currently
113
121
  * do not track null, Boolean, Number, Buffer(a few types here)
114
122
  *
115
- * @param {Object} values for all keys in ExpressionAttributeValues, ExclusiveStartKey or ScanFilter
123
+ * @param {Object} values for all keys in relevant properties
116
124
  * @return {Array} all values
117
125
  */
118
126
  function findValues(values) {
@@ -138,7 +146,7 @@ module.exports = ({ common }) => {
138
146
  /**
139
147
  * Registers the hooks for client and document client scan
140
148
  */
141
- dynamoSink.handle = function() {
149
+ dynamoSink.handle = function () {
142
150
  moduleHook.resolve({ name: moduleName }, (aws) => {
143
151
  const client = aws.DynamoDB.prototype;
144
152
 
@@ -160,24 +168,25 @@ module.exports = ({ common }) => {
160
168
  return;
161
169
  }
162
170
 
163
- // since this is instrumenting a wrapper we only want to to say the args
171
+ // since this is instrumenting a wrapper we only want to say the args
164
172
  // were the 2nd arg which is the dynamo db payload
165
173
  const ctxtData = {
166
174
  args: [data.args[1]],
167
175
  obj: data.obj,
168
- result: data.result
176
+ result: data.result,
169
177
  };
170
- const values = extractValues(data.args[1], 'client');
178
+
179
+ const values = extractValues(data.args[1], 'client', 'v2');
171
180
  dynamoSink.check({
172
181
  values,
173
182
  methodName: 'DynamoDB.prototype.scan',
174
- data: ctxtData
183
+ data: ctxtData,
175
184
  });
176
185
  }
177
- }
186
+ },
178
187
  });
179
- // DocumentClient added in aws-sdk 2.2. Some customers still on more
180
- // ancient versions
188
+
189
+ // DocumentClient added in aws-sdk 2.2.
181
190
  if (aws.DynamoDB.DocumentClient) {
182
191
  const docClient = aws.DynamoDB.DocumentClient.prototype;
183
192
  /**
@@ -192,32 +201,58 @@ module.exports = ({ common }) => {
192
201
  if (data.args[0] === 'scan') {
193
202
  requests.add(data.args[1]);
194
203
  }
195
- }
204
+ },
196
205
  });
197
206
 
198
207
  patcher.patch(docClient, 'scan', {
199
208
  name: 'aws-sdk.DynamoDB.DocumentClient.prototype',
200
209
  patchType: PATCH_TYPES.ASSESS_SINK,
201
210
  post(data) {
202
- const values = extractValues(data.args[0], 'docClient');
211
+ const values = extractValues(data.args[0], 'docClient', 'v2');
203
212
  dynamoSink.check({
204
213
  values,
205
214
  methodName: 'DynamoDB.DocumentClient.prototype.scan',
206
- data
215
+ data,
207
216
  });
208
- }
217
+ },
209
218
  });
210
219
  }
211
220
  });
212
221
 
222
+ moduleHook.resolve({ name: moduleNameV3 }, (aws) => {
223
+ const client = aws.DynamoDBClient.prototype;
224
+
225
+ patcher.patch(client, 'send', {
226
+ name: `${moduleNameV3}.ScanCommand.prototype`,
227
+ patchType: PATCH_TYPES.ASSESS_SINK,
228
+ alwaysRun: true,
229
+ post(data) {
230
+ if (!AsyncStorage.getContext()) return;
231
+
232
+ if (data.args[0] instanceof aws.ScanCommand) {
233
+ const values = extractValues(
234
+ data.args[0].input,
235
+ 'docClient',
236
+ 'v3',
237
+ ).filter(Boolean);
238
+ dynamoSink.check({
239
+ data,
240
+ values,
241
+ methodName: 'DynamoDBClient.ScanCommand',
242
+ });
243
+ }
244
+ },
245
+ });
246
+ });
247
+
213
248
  return dynamoSink;
214
249
  };
215
250
 
216
- dynamoSink.check = function({ data, values, methodName }) {
251
+ dynamoSink.check = function ({ data, values, methodName }) {
217
252
  const vulnerableString = _.compact(
218
253
  values.map((input) =>
219
- isVulnerable({ disallowedTags, requiredTags, input })
220
- )
254
+ isVulnerable({ disallowedTags, requiredTags, input }),
255
+ ),
221
256
  );
222
257
 
223
258
  if (vulnerableString.length) {
@@ -0,0 +1,40 @@
1
+ /**
2
+ Copyright: 2022 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
+ 'use strict';
16
+
17
+ const agentEmitter = require('../../agent-emitter');
18
+ const logger = require('../../core/logger')('contrast:rules:static');
19
+ const fs = require('fs');
20
+
21
+
22
+ module.exports = (agent) => {
23
+ const cacheDir = agent.getCacheDir();
24
+
25
+ if (cacheDir && fs.existsSync(cacheDir)) {
26
+ try {
27
+ const { findings } = JSON.parse(fs.readFileSync(`${cacheDir}/contrast-static-findings.json`));
28
+ if (findings && findings.length) {
29
+ findings.forEach((finding) => {
30
+ const { ruleId } = finding;
31
+ const { source, lineNumber, codeSource } = finding.props;
32
+ agentEmitter.emit('finding', finding);
33
+ logger.debug('%s: %s:%d %s', ruleId, source, lineNumber, codeSource);
34
+ });
35
+ }
36
+ } catch {
37
+ logger.debug('No valid cli-rewriter static findings file found. If you\'ve used cli-rewriter feature static findings will be missing');
38
+ }
39
+ }
40
+ };
@@ -12,17 +12,16 @@ Copyright: 2022 Contrast Security, Inc
12
12
  engineered, modified, repackaged, sold, redistributed or otherwise used in a
13
13
  way not consistent with the End User License Agreement.
14
14
  */
15
+ 'use strict';
16
+
17
+ /*
18
+ These technologies should also exist in teamserver/teamserver-app/src/main/resources/flowmap/technologies.json
19
+ Databases and loggers will be listed as "Service", frameworks, templating and transpilers as "Presentation"
20
+ Transpilers are grouped as "Transpiled JavaScript" and templates as "Templating"
21
+ */
22
+
15
23
  const technologies = {
16
- databases: [
17
- // mysql
18
- 'mysql',
19
- // postgres
20
- 'pg',
21
- // mongo
22
- 'mongodb',
23
- // rethink
24
- 'rethinkdb'
25
- ],
24
+ databases: ['mysql', 'pg', 'mongodb', 'rethinkdb'],
26
25
  http: [
27
26
  '@hapi/hapi',
28
27
  'hapi',
@@ -32,16 +31,16 @@ const technologies = {
32
31
  'restify',
33
32
  'loopback',
34
33
  'kraken-js',
35
- 'sails'
34
+ 'sails',
36
35
  ],
37
36
  templating: ['jade', 'ejs', 'nunjucks', 'mustache', 'dust', 'handlebars'],
38
37
  loggers: ['winston', 'debug'],
39
38
  mvc: ['meteor'],
40
- transpilers: ['babel', 'escodegen'] // XXX is this necessary?
39
+ transpilers: ['babel', 'escodegen'],
41
40
  };
42
41
 
43
42
  let all = [];
44
- Object.keys(technologies).forEach(function(type) {
43
+ Object.keys(technologies).forEach(function (type) {
45
44
  all = all.concat(technologies[type]);
46
45
  });
47
46
 
@@ -26,11 +26,13 @@ const loggerFactory = require('../core/logger');
26
26
  const configOptions = require('../core/config/options');
27
27
  const configUtil = require('../core/config/util');
28
28
  const { ContrastAgent } = require('../agent');
29
+ const agentEmitter = require('../agent-emitter');
29
30
  const moduleHelper = require('../hooks/module/helpers');
30
31
  const injections = require('../core/rewrite/injections');
31
32
 
32
33
  const readFile = util.promisify(fs.readFile);
33
34
  const LOGGER_NS = 'contrast:cli-rewriter';
35
+ const getType = require('../util/get-file-type');
34
36
 
35
37
  class CLIRewriter {
36
38
  /**
@@ -53,6 +55,7 @@ class CLIRewriter {
53
55
  }
54
56
 
55
57
  this.initAgent();
58
+ this.initStaticRulesVisitors();
56
59
  this.initRewriter();
57
60
  }
58
61
 
@@ -75,9 +78,10 @@ class CLIRewriter {
75
78
  loggerFactory.init(this.config);
76
79
 
77
80
  const cacheEnabled = this.config.get('agent.node.rewrite_cache.enable');
78
- if (!cacheEnabled) {
81
+ const assessEnabled = this.config.get('assess.enable');
82
+ if (!cacheEnabled || !assessEnabled) {
79
83
  this.logger.error(
80
- `fatal configuration error: contrast-transpile requires 'agent.node.rewrite_cache.enable=true'`
84
+ 'fatal configuration error: contrast-transpile requires \'agent.node.rewrite_cache.enable=true\' AND \'assess.enable=true'
81
85
  );
82
86
  process.exit(1);
83
87
  }
@@ -94,9 +98,34 @@ class CLIRewriter {
94
98
  this.agent = new ContrastAgent();
95
99
  this.agent.config = this.config;
96
100
  this.agent.staticVisitors.push(CLIRewriter.requireDetector);
101
+ this.agent.staticVisitors.push(CLIRewriter.importDetector);
97
102
  this.agent.appInfo = new AppInfo(this.entrypoint, this.config);
98
103
  }
99
104
 
105
+ initStaticRulesVisitors() {
106
+ const { logger } = this;
107
+ const cacheDir = this.agent.getCacheDir();
108
+ const findingsFilePath = `${cacheDir}/contrast-static-findings.json`;
109
+ const rule = require('../assess/static/hardcoded')({ agent: this.agent });
110
+ const policy = require('../assess/policy/non-dataflow-rules.json');
111
+
112
+ agentEmitter.on('finding', function writeFindingToAFile (finding) {
113
+ try {
114
+ if (!fs.existsSync(findingsFilePath)) {
115
+ if (!fs.existsSync(cacheDir)) {
116
+ fs.mkdirSync(cacheDir, { recursive: true });
117
+ }
118
+ fs.writeFileSync(findingsFilePath, '{\n"findings":\n[\n');
119
+ }
120
+ fs.appendFileSync(`${cacheDir}/contrast-static-findings.json`, `${JSON.stringify(finding)},\n`);
121
+ } catch (error) {
122
+ logger.error('Error writing to static findings file. Static finding won\'t be reported. Error: %s', error);
123
+ }
124
+ });
125
+ rule.handle(policy.rules['hardcoded-password'], 'hardcoded-password');
126
+ rule.handle(policy.rules['hardcoded-key'], 'hardcoded-key');
127
+ }
128
+
100
129
  /**
101
130
  * Loads babel rewriter and utils an initializes them.
102
131
  */
@@ -119,9 +148,23 @@ class CLIRewriter {
119
148
  async rewrite() {
120
149
  const parent = CLIRewriter.getModuleData(this.filename);
121
150
  const start = Date.now();
151
+ const cacheDir = this.agent.getCacheDir();
152
+ const findingsFilePath = `${cacheDir}/contrast-static-findings.json`;
122
153
 
123
154
  await this.traverse(this.filename, parent);
124
155
 
156
+ if (fs.existsSync(findingsFilePath)) {
157
+ try {
158
+ const findingsFileSize = fs.statSync(findingsFilePath).size;
159
+ if (findingsFileSize > 20) {
160
+ fs.truncateSync(findingsFilePath, findingsFileSize - 2);
161
+ }
162
+ fs.appendFileSync(findingsFilePath, '\n]\n}');
163
+ } catch (error) {
164
+ this.logger.error('Error formatting static findings file. Static findings won\'t be reported. Error: %s', error);
165
+ }
166
+ }
167
+
125
168
  this.logger.info('rewriting complete [%ss]', (Date.now() - start) / 1000);
126
169
  }
127
170
 
@@ -133,13 +176,14 @@ class CLIRewriter {
133
176
  * @param {object} parent parent module data
134
177
  */
135
178
  async traverse(filename, parent) {
136
- const fileDependencies = await this.visitDependency(filename);
179
+ const type = getType(filename);
180
+ const fileDependencies = await this.visitDependency(filename, type);
137
181
 
138
182
  return Promise.all(
139
183
  fileDependencies.map((request) => {
140
184
  try {
141
185
  const _filename = Module._resolveFilename(request, parent);
142
- if (_filename.endsWith('.js')) {
186
+ if (_filename.endsWith('.js') || _filename.endsWith('.mjs')) {
143
187
  const _parent = CLIRewriter.getModuleData(_filename);
144
188
  return this.traverse(_filename, _parent);
145
189
  }
@@ -162,7 +206,7 @@ class CLIRewriter {
162
206
  * @param {string} filename name of file to rewrite / cache
163
207
  * @returns {array[string]} list of the file's dependencies
164
208
  */
165
- async visitDependency(filename) {
209
+ async visitDependency(filename, type) {
166
210
  if (this.visited.has(filename)) {
167
211
  return [];
168
212
  }
@@ -170,7 +214,9 @@ class CLIRewriter {
170
214
  this.visited.add(filename);
171
215
 
172
216
  const content = await readFile(filename, 'utf8');
173
- const rewriteData = this.rewriter.rewriteFile(content, filename);
217
+ const rewriteData = this.rewriter.rewriteFile(content, filename, {
218
+ sourceType: type
219
+ });
174
220
 
175
221
  if (rewriteData.code) {
176
222
  await moduleHelper.cacheWithSourceMap(this.agent, filename, rewriteData);
@@ -213,6 +259,19 @@ class CLIRewriter {
213
259
  }
214
260
  }
215
261
  }
262
+
263
+ /**
264
+ * Added to agent's static visitors. Runs during rewriting to detect import
265
+ * calls to discover dependencies.
266
+ * @param {object} node AST node being visited during rewrite
267
+ * @param {string} filename file being rewritten
268
+ * @param {object} state rewriter state
269
+ */
270
+ static importDetector(node, filename, state) {
271
+ if (node.type === 'ImportDeclaration') {
272
+ state.deps.push(node.source.extra.rawValue);
273
+ }
274
+ }
216
275
  }
217
276
 
218
277
  /**
package/lib/contrast.js CHANGED
@@ -277,8 +277,6 @@ contrastAgent.prepare = function(...args) {
277
277
 
278
278
  sourceMapUtility.init(agent);
279
279
 
280
- contrastAgent.logRewriteCacheInfo(agent);
281
-
282
280
  // return to startup if agent is enabled
283
281
  return config.enable;
284
282
  });
@@ -307,6 +305,7 @@ contrastAgent.bootstrap = function(args) {
307
305
  agent.clearIntervals();
308
306
  return;
309
307
  } else {
308
+ contrastAgent.logRewriteCacheInfo(agent);
310
309
  return instrument(agent, reporter);
311
310
  }
312
311
  })
@@ -411,6 +411,12 @@ const agent = [
411
411
  fn: castBoolean,
412
412
  desc: 'do agent-native input analysis prior to any external analysis',
413
413
  },
414
+ {
415
+ name: 'agent.node.analysis_log_dir',
416
+ arg: '<path>',
417
+ default: '.',
418
+ desc: 'directory to use for the native input analysis log file'
419
+ },
414
420
  {
415
421
  name: 'agent.node.unsafe.deadzones',
416
422
  arg: '<modules>',
@@ -15,6 +15,7 @@ Copyright: 2022 Contrast Security, Inc
15
15
  'use strict';
16
16
  const _ = require('lodash');
17
17
 
18
+ const os = require('os');
18
19
  const process = require('process');
19
20
  const path = require('path');
20
21
  const fs = require('fs');
@@ -24,7 +25,6 @@ const stringify = require('json-stable-stringify');
24
25
  const common = require('./options');
25
26
  const configOptions = common.options;
26
27
  const { configPathEnvVars } = common;
27
- const fileFinder = require('../../util/file-finder');
28
28
  const util = module.exports;
29
29
 
30
30
  /**
@@ -143,37 +143,23 @@ class Config {
143
143
 
144
144
  /**
145
145
  * Find location of config given options and name of config file
146
- * @param {Object} cliOptions
147
- * @param {string} cliOptions.script
148
- * @param {string} filename
149
146
  * @return {string|void} path, if valid
150
147
  */
151
- function checkConfigPath({ script }, filename) {
152
- const configDir =
153
- require('os').platform() === 'win32'
154
- ? `${process.env['ProgramData']}\\contrast`
155
- : '/etc/contrast';
156
- const guesses = [
157
- // try to find in cwd
158
- { dir: process.cwd(), attempts: 1 },
159
-
160
- // try to find in /etc/contrast/.. or %ProgramData%\contrast\..
161
- { dir: configDir, attempts: 1 },
162
-
163
- // The directory of the application under test such as node_modules/mocha/bin or server.
164
- { dir: path.dirname(script) },
165
-
166
- // The directory of this Contrast agent module, assuming it
167
- // came packaged with an enterprise-wide contrast.json.
168
- { dir: path.resolve(__dirname, '..', '..'), attempts: 1 }
169
- ];
170
-
171
- for (const guess of guesses) {
172
- const configPath = fileFinder.findFile(guess.dir, filename, guess.attempts);
173
- if (configPath) return configPath;
148
+ function checkConfigPath() {
149
+ const configDir = os.platform() === 'win32'
150
+ ? `${process.env['ProgramData']}\\contrast`
151
+ : '/etc/contrast';
152
+
153
+ for (const dir of [process.cwd(), configDir]) {
154
+ const checkPath = path.resolve(dir, 'contrast_security.yaml');
155
+ if (fs.existsSync(checkPath)) {
156
+ return checkPath;
157
+ }
174
158
  }
159
+ return;
175
160
  }
176
161
 
162
+
177
163
  /**
178
164
  * @param {Object} cliOptions
179
165
  * @param {string} cliOptions.script
@@ -185,11 +171,7 @@ function getConfigPath(cliOptions) {
185
171
  cliOptions.configFile ||
186
172
  process.env[configPathEnvVars.path] ||
187
173
  process.env[configPathEnvVars.deprecated] ||
188
- checkConfigPath(cliOptions, 'contrast_security.yaml') ||
189
- checkConfigPath(cliOptions, 'contrast_security.yml') ||
190
- checkConfigPath(cliOptions, 'contrast.yaml') ||
191
- checkConfigPath(cliOptions, 'contrast.yml') ||
192
- checkConfigPath(cliOptions, 'contrast.json')
174
+ checkConfigPath()
193
175
  );
194
176
  }
195
177
 
@@ -312,7 +294,7 @@ function mergeCliOptions(cliOptions, logger) {
312
294
  // set from default
313
295
  if (value === undefined) {
314
296
  if (required) {
315
- logger.error(`Missing required option '%s'`, name);
297
+ logger.error('Missing required option \'%s\'', name);
316
298
  return options;
317
299
  }
318
300
 
@@ -29,7 +29,8 @@ const generalizedInputTypes = {
29
29
  [INPUT_TYPES.COOKIE_VALUE]: EXCLUSION_INPUT_TYPES.COOKIE,
30
30
  [INPUT_TYPES.HEADER]: EXCLUSION_INPUT_TYPES.HEADER,
31
31
  [INPUT_TYPES.PARAMETER_NAME]: EXCLUSION_INPUT_TYPES.PARAMETER,
32
- [INPUT_TYPES.PARAMETER_VALUE]: EXCLUSION_INPUT_TYPES.PARAMETER
32
+ [INPUT_TYPES.PARAMETER_VALUE]: EXCLUSION_INPUT_TYPES.PARAMETER,
33
+ [INPUT_TYPES.URL_PARAMETER]: EXCLUSION_INPUT_TYPES.PARAMETER
33
34
  };
34
35
  const { BODY, PARAMETER, QUERYSTRING } = EXCLUSION_INPUT_TYPES;
35
36
 
@@ -47,6 +48,10 @@ class InputExclusion extends UrlExclusion {
47
48
  this.inputName = dtm.inputName;
48
49
  }
49
50
 
51
+ shouldExclude(ruleId, type, name) {
52
+ return this.appliesToProtectRule(ruleId) && this.appliesToInputType(type) && this.matches(name);
53
+ }
54
+
50
55
  appliesToInputType(type) {
51
56
  return (
52
57
  this.inputType === InputExclusion.generalizeInputType(type) ||
@@ -237,7 +237,7 @@ class ExpressFramework {
237
237
 
238
238
  if (!app || !app.defaultConfiguration) {
239
239
  logger.error(
240
- `non-express application mistakenly registered`,
240
+ 'non-express application mistakenly registered',
241
241
  new Error().stack,
242
242
  );
243
243
  return;
@@ -336,9 +336,7 @@ class ExpressFramework {
336
336
  }, 'textParser');
337
337
 
338
338
  this.useAfter(function ContrastBodyParsed(req, res, next) {
339
- agentEmitter.emit(EVENTS.BODY_PARSED, req, res, {
340
- type: INPUT_TYPES.BODY,
341
- });
339
+ agentEmitter.emit(EVENTS.BODY_PARSED, req, res, INPUT_TYPES.BODY);
342
340
  next();
343
341
  }, 'urlencodedParser');
344
342