@contrast/agent 4.5.0 → 4.5.1

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.
package/bin/VERSION CHANGED
@@ -1 +1 @@
1
- 2.26.0
1
+ 2.27.3
Binary file
Binary file
Binary file
@@ -294,10 +294,9 @@ module.exports = class SourceMembrane extends Membrane {
294
294
  if (!(metadata.sourceType && metadata.path)) {
295
295
  return false;
296
296
  }
297
- const koaQueryString = metadata.path.match(/(\w+)=/);
298
- if (koaQueryString) {
299
- // get 1st capture group, fall back to `metadata.path` if for some reason this does not exist
300
- metadata.path = koaQueryString[1] || metadata.path;
297
+ const koaQueryString = metadata.path.split('=');
298
+ if (koaQueryString[1]) {
299
+ metadata.path = koaQueryString[0];
301
300
  }
302
301
  return true;
303
302
  }
@@ -23,6 +23,26 @@ module.exports = function() {
23
23
  moduleHook.resolve({ name: 'bluebird' }, (bluebird) => {
24
24
  module.exports.patchConfig(bluebird);
25
25
  module.exports.patchAddCallbacks(bluebird);
26
+ module.exports.patchGetNewLibraryCopy(bluebird);
27
+ });
28
+ };
29
+
30
+ /**
31
+ * Ensures that new library copies are also instrumented.
32
+ * @param {function} bluebird the library export
33
+ */
34
+ module.exports.patchGetNewLibraryCopy = function(bluebird) {
35
+ if (typeof bluebird.getNewLibraryCopy !== 'function') {
36
+ return;
37
+ }
38
+
39
+ patcher.patch(bluebird, 'getNewLibraryCopy', {
40
+ alwaysRun: true,
41
+ name: 'bluebird.getNewLibraryCopy',
42
+ patchType: PATCH_TYPES.ASYNC_CONTEXT,
43
+ post(data) {
44
+ module.exports.patchAddCallbacks(data.result);
45
+ }
26
46
  });
27
47
  };
28
48
 
@@ -21,7 +21,6 @@ const _isAgentPath = require('../util/is-agent-path');
21
21
 
22
22
  const STACK_TRACE_LIMIT = 25;
23
23
  const EVAL_ORIGIN_REGEX = /\((.*?):(\d+):\d+\)/;
24
- const EJS_EVAL_ORIGIN_REGEX = /(.*\.ejs$)/;
25
24
  const EVENTS_FILE = semver.gte(process.version, '16.0.0')
26
25
  ? 'node:events'
27
26
  : 'events.js';
@@ -146,8 +145,7 @@ class Factory {
146
145
  if (callsite.isEval()) {
147
146
  evalOrigin = Factory.formatFileName(callsite.getEvalOrigin());
148
147
  [, file, lineNumber, columnNumber] =
149
- evalOrigin.match(EVAL_ORIGIN_REGEX) ||
150
- evalOrigin.match(EJS_EVAL_ORIGIN_REGEX);
148
+ evalOrigin.match(EVAL_ORIGIN_REGEX) || evalOrigin.endsWith('.ejs');
151
149
  }
152
150
 
153
151
  file = file || callsite.getFileName();
@@ -160,7 +160,8 @@ class FeatureSet {
160
160
  );
161
161
 
162
162
  return disabledConfig
163
- .split(/\s*,\s*/)
163
+ .split(',')
164
+ .map((c) => c.trim())
164
165
  .some((disabledId) => id === disabledId);
165
166
  }
166
167
 
@@ -22,19 +22,20 @@ Copyright: 2021 Contrast Security, Inc
22
22
  // the arguments keyword instead of providing a `...args` rest param.
23
23
  /* eslint-disable prefer-rest-params */
24
24
 
25
- const { promisify } = require('util');
26
25
  const logger = require('../core/logger')('contrast:hooks');
27
26
  const agent = require('../agent');
28
- const perfLogger = require('../core/logger/perf-logger')(agent);
29
- const _ = require('lodash');
30
27
  const perfLoggingEnabled =
31
28
  agent.config && agent.config.agent.node.req_perf_logging;
29
+ const perfLogger = perfLoggingEnabled
30
+ ? require('../core/logger/perf-logger')(agent)
31
+ : undefined;
32
32
  const tracker = require('../tracker.js');
33
33
  const { AsyncStorage } = require('../core/async-storage');
34
34
  const {
35
35
  Scopes,
36
36
  SCOPE_NAMES: { NO_PROPAGATION }
37
37
  } = require('../core/async-storage/scopes');
38
+ const promisifyCustom = Symbol.for('nodejs.util.promisify.custom');
38
39
 
39
40
  // When a pre-hook returns the result of this function, the value passed will
40
41
  // be used in place of the original function's return value, and the original
@@ -43,12 +44,8 @@ function preempt(value) {
43
44
  return new PatcherPreempt(value);
44
45
  }
45
46
 
46
- const { toString } = {};
47
-
48
47
  function isFunction(fn) {
49
- return (
50
- toString.call(this, fn) === '[object Function]' || fn instanceof Function
51
- );
48
+ return fn instanceof Function || typeof fn === 'function';
52
49
  }
53
50
 
54
51
  function PatcherPreempt(v) {
@@ -112,15 +109,6 @@ function runHooks(type, options, data, fn, thisTarget) {
112
109
  });
113
110
  }
114
111
 
115
- /**
116
- * Checks if any of the provided arguments are tracked.
117
- * @param {any[]} args
118
- * @return {boolean}
119
- */
120
- function argsTracked(args) {
121
- return _.some(args, (arg) => arg && tracker.getData(arg).tracked);
122
- }
123
-
124
112
  /**
125
113
  * Whether to skip the running of instrumentation in registered pre/post hooks.
126
114
  * When this returns `false` (don't skip), the pre/post hooks will execute in
@@ -232,8 +220,11 @@ function hookFunction(fn, options) {
232
220
  }
233
221
 
234
222
  // short-circuit optimization for add
235
- if (fn.name === '__add' && !argsTracked(args)) {
236
- return args[0] + args[1];
223
+ const [arg1, arg2] = args;
224
+ if (fn.name === '__add' && arg1 && arg2) {
225
+ if (!tracker.getData(arg1).tracked && !tracker.getData(arg2).tracked) {
226
+ return arg1 + arg2;
227
+ }
237
228
  }
238
229
 
239
230
  const data = {
@@ -292,7 +283,7 @@ function hookFunction(fn, options) {
292
283
  ...Object.getOwnPropertyNames(descriptors)
293
284
  ]) {
294
285
  if (
295
- key === promisify.custom &&
286
+ key === promisifyCustom &&
296
287
  typeof descriptors[key].value === 'function'
297
288
  ) {
298
289
  // We must assume that custom promisified function values are true aliases
@@ -330,8 +321,7 @@ function hookableMethods(obj, props, callback) {
330
321
  }
331
322
 
332
323
  // Execute callback for every method in the props array.
333
- let i = props.length;
334
- while (i--) {
324
+ for (let i = props.length; i >= 0; i--) {
335
325
  // If the property isn't a function...
336
326
  if (
337
327
  !isFunction(obj[props[i]]) ||
@@ -482,7 +472,7 @@ function patch(obj, props, options) {
482
472
  }
483
473
 
484
474
  const hookedMethods = hookableMethods(obj, props).map(function(prop) {
485
- const opts = _.clone(options);
475
+ const opts = Object.assign({}, options);
486
476
 
487
477
  // staticName means do not append methods to final name
488
478
  // only used for Function(aka global.ContrastFunction)
@@ -14,7 +14,7 @@ Copyright: 2021 Contrast Security, Inc
14
14
  */
15
15
  'use strict';
16
16
  const { INPUT_TYPES } = require('../common');
17
- const SINK_EXPLOIT_PATTERN = /(?:^|\\|\/)(?:sh|bash|zsh|ksh|tcsh|csh|fish|cmd)([-/].*)*[-/][a-zA-Z]*c/i;
17
+ const SINK_EXPLOIT_PATTERN_START = /(?:^|\\|\/)(?:sh|bash|zsh|ksh|tcsh|csh|fish|cmd)/;
18
18
  const stripWhiteSpace = (str) => str.replace(/\s/g, '');
19
19
  const REQUEST_KEYS = {
20
20
  queryParams: INPUT_TYPES.QUERYSTRING,
@@ -96,8 +96,8 @@ module.exports = class BackdoorDetector {
96
96
  const normalizedParam = stripWhiteSpace(requestValue);
97
97
  return (
98
98
  normalizedParam === this.normalizedCmd ||
99
- (SINK_EXPLOIT_PATTERN.test(this.normalizedCmd) &&
100
- this.normalizedCmd.endsWith(normalizedParam))
99
+ (this.normalizedCmd.endsWith(normalizedParam) &&
100
+ SINK_EXPLOIT_PATTERN_START.test(this.normalizedCmd))
101
101
  );
102
102
  }
103
103
  };
@@ -73,7 +73,7 @@ class FunctionCall {
73
73
  hasMultipleUnquotedBarewords() {
74
74
  let rc = false;
75
75
  const QUOTES = new RegExp('[\'|"]');
76
- const MULTI_WORDS = new RegExp('[a-zA-Z]+\\s+[a-zA-Z]+');
76
+ const MULTI_WORDS = new RegExp('[\\w\\s]+');
77
77
  if (!this.expression.match(QUOTES)) {
78
78
  rc = this.expression.match(MULTI_WORDS);
79
79
  }
@@ -72,7 +72,7 @@ class FunctionCall {
72
72
  hasMultipleUnquotedBarewords() {
73
73
  let rc = false;
74
74
  const QUOTES = new RegExp('[\'|"]');
75
- const MULTI_WORDS = new RegExp('[a-zA-Z]+\\s+[a-zA-Z]+');
75
+ const MULTI_WORDS = new RegExp('[\\w\\s]+');
76
76
  if (!this.expression.match(QUOTES)) {
77
77
  rc = this.expression.match(MULTI_WORDS);
78
78
  }
@@ -202,7 +202,7 @@ const makeFrame = (callsite) => {
202
202
  evalOrigin = CleanStack.formatFileName(callsite.getEvalOrigin());
203
203
  [, file, lineNumber, columnNumber] = callsite
204
204
  .getEvalOrigin()
205
- .match(/\((.*?):(\d+):\d+\)/);
205
+ .match(/\((.{3,4095}?):(\d+):\d+\)/);
206
206
  }
207
207
 
208
208
  file = file || callsite.getFileName();
@@ -40,7 +40,7 @@ class Brackets {
40
40
  }
41
41
 
42
42
  /**
43
- * Coerces occurrances of substrings that look like
43
+ * Coerces occurrences of substrings that look like
44
44
  * bracket accessors (['abcd'], ["efgh"]) into their
45
45
  * dot-accessor equivalents (.abcd, .efgh).
46
46
  * @param {} str
@@ -58,8 +58,8 @@ function coerceToDotAccessors(str) {
58
58
  }
59
59
 
60
60
  const bracketed = str.substring(startIdx, stopIdx + 1);
61
- const patt = /^\[\s*(?:`|'|")([a-zA-Z_]+[a-zA-Z0-9_]*)(?:`|'|")\s*\]$/;
62
- const match = patt.exec(bracketed);
61
+ const pattern = /^\[\s*[`'"]([a-zA-Z_]+[a-zA-Z0-9_]*)[`'"]\s*]$/;
62
+ const match = pattern.exec(bracketed);
63
63
 
64
64
  return !match
65
65
  ? str
@@ -66,7 +66,7 @@ const getReqAddresses = (req = {}) => {
66
66
  */
67
67
  const getForwardedFor = (req = {}) => {
68
68
  const header = req.headers && req.headers['x-forwarded-for'];
69
- return header ? header.split(/\s*,\s*/) : [];
69
+ return header ? header.split(',').map((x) => x.trim()) : [];
70
70
  };
71
71
 
72
72
  /**
@@ -38,7 +38,7 @@ const UP_DIR_LINUX = '../';
38
38
  const UP_DIR_WIN = '..\\';
39
39
  const ENTITY_TYPES = { SYSTEM: 'SYSTEM', PUBLIC: 'PUBLIC' };
40
40
 
41
- const EXTERNAL_RX = /<!ENTITY\s+[a-zA-Z0-f]+\s+(?:SYSTEM|PUBLIC)\s+(?:.*?)>/g;
41
+ const EXTERNAL_RX = /<!ENTITY\s+[a-zA-Z0-f]+\s+(?:SYSTEM|PUBLIC)\s+"(.*?)"\s*>/g;
42
42
  // <!ENTITY name SYSTEM "URI">
43
43
  const SYSTEM_ID_REGEX = /<!ENTITY\s+([a-zA-Z0-9]+)\s+SYSTEM\s+"(.*?)"\s*>/;
44
44
  // <!ENTITY name PUBLIC "public_ID" "URI">
@@ -309,7 +309,7 @@ endif
309
309
 
310
310
  quiet_cmd_regen_makefile = ACTION Regenerating $@
311
311
  cmd_regen_makefile = cd $(srcdir); /opt/hostedtoolcache/node/12.22.7/x64/lib/node_modules/npm/node_modules/node-gyp/gyp/gyp_main.py -fmake --ignore-environment "-Dlibrary=shared_library" "-Dvisibility=default" "-Dnode_root_dir=/home/runner/.cache/node-gyp/12.22.7" "-Dnode_gyp_dir=/opt/hostedtoolcache/node/12.22.7/x64/lib/node_modules/npm/node_modules/node-gyp" "-Dnode_lib_file=/home/runner/.cache/node-gyp/12.22.7/<(target_arch)/node.lib" "-Dmodule_root_dir=/home/runner/work/node-agent/node-agent/target/node_modules/unix-dgram" "-Dnode_engine=v8" "--depth=." "-Goutput_dir=." "--generator-output=build" -I/home/runner/work/node-agent/node-agent/target/node_modules/unix-dgram/build/config.gypi -I/opt/hostedtoolcache/node/12.22.7/x64/lib/node_modules/npm/node_modules/node-gyp/addon.gypi -I/home/runner/.cache/node-gyp/12.22.7/include/node/common.gypi "--toplevel-dir=." binding.gyp
312
- Makefile: $(srcdir)/build/config.gypi $(srcdir)/../../../../../../../../opt/hostedtoolcache/node/12.22.7/x64/lib/node_modules/npm/node_modules/node-gyp/addon.gypi $(srcdir)/../../../../../../.cache/node-gyp/12.22.7/include/node/common.gypi $(srcdir)/binding.gyp
312
+ Makefile: $(srcdir)/../../../../../../.cache/node-gyp/12.22.7/include/node/common.gypi $(srcdir)/build/config.gypi $(srcdir)/binding.gyp $(srcdir)/../../../../../../../../opt/hostedtoolcache/node/12.22.7/x64/lib/node_modules/npm/node_modules/node-gyp/addon.gypi
313
313
  $(call do_cmd,regen_makefile)
314
314
 
315
315
  # "all" is a concatenation of the "all" targets from all the included
@@ -126,7 +126,7 @@
126
126
  "progress": "",
127
127
  "https_proxy": "",
128
128
  "save_prod": "",
129
- "npm_session": "f073c21e90fab724",
129
+ "npm_session": "70d49993d405ee64",
130
130
  "audit": "true",
131
131
  "cidr": "",
132
132
  "onload_script": "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/agent",
3
- "version": "4.5.0",
3
+ "version": "4.5.1",
4
4
  "description": "Node.js security instrumentation by Contrast Security",
5
5
  "keywords": [
6
6
  "security",