@contrast/agent 4.6.0 → 4.7.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.
package/bin/VERSION CHANGED
@@ -1 +1 @@
1
- 2.27.3
1
+ 2.28.0
Binary file
Binary file
Binary file
@@ -39,14 +39,6 @@ const signature = new Signature({
39
39
  isModule: false
40
40
  });
41
41
 
42
- const numericCheck = /^[0-9]+$/;
43
- function isNumeric(input) {
44
- // regex from validator.js/lib/isNumeric.js:
45
- // https://github.com/validatorjs/validator.js/blob/master/lib/isNumeric.js
46
- // do we want to allow symbols (plus/minus sign, decimal point?)
47
- return numericCheck.test(input);
48
- }
49
-
50
42
  module.exports = class SourceMembrane extends Membrane {
51
43
  /**
52
44
  * @param {object} config agent config to use for array sampling.
@@ -304,8 +296,7 @@ module.exports = class SourceMembrane extends Membrane {
304
296
  /**
305
297
  * The `TagRanges` returned will include the `untrusted` tag. There will be
306
298
  * `exclusion:${ruleId}` tags for input exclusions pertaining to specific
307
- * rules and whose name matches the property name described in metadata. And
308
- * `limited-chars` tags are included if string is numeric.
299
+ * rules and whose name matches the property name described in metadata.
309
300
  * @param {string} str string being tracked by membrane
310
301
  * @param {object} metadata metadata about source type and key name
311
302
  * @returns {TagRange[]}
@@ -342,10 +333,6 @@ module.exports = class SourceMembrane extends Membrane {
342
333
  }
343
334
  }
344
335
 
345
- if (isNumeric(str)) {
346
- tagRanges.push(new TagRange(start, stop, 'limited-chars'));
347
- }
348
-
349
336
  if (metadata.sourceType === 'header') {
350
337
  if (metadata.path.toLocaleLowerCase() !== 'referer') {
351
338
  tagRanges.push(new TagRange(start, stop, 'header'));
@@ -59,33 +59,15 @@
59
59
  },
60
60
  "mustache.escape": {
61
61
  "enabled": true,
62
- "source": "P",
63
- "target": "R",
64
- "tags": ["html-encoded"],
65
- "type": "overload",
66
- "command": {
67
- "type": "keep"
68
- }
62
+ "provider": "./propagators/mustache/escape.js"
69
63
  },
70
64
  "dust.escapeHtml": {
71
65
  "enabled": true,
72
- "source": "P",
73
- "target": "R",
74
- "tags": ["html-encoded"],
75
- "type": "overload",
76
- "command": {
77
- "type": "keep"
78
- }
66
+ "provider": "./propagators/dustjs/escape-html.js"
79
67
  },
80
68
  "dust.escapeJs": {
81
69
  "enabled": true,
82
- "source": "P",
83
- "target": "R",
84
- "tags": ["javascript-encoded"],
85
- "type": "overload",
86
- "command": {
87
- "type": "keep"
88
- }
70
+ "provider": "./propagators/dustjs/escape-js.js"
89
71
  },
90
72
  "pug.compile": {
91
73
  "enabled": true,
@@ -349,23 +331,11 @@
349
331
  },
350
332
  "encodeURI": {
351
333
  "enabled": true,
352
- "type": "overload",
353
- "tags": ["weak-url-encoded"],
354
- "source": "P",
355
- "target": "R",
356
- "command": {
357
- "type": "keep"
358
- }
334
+ "provider": "./propagators/encode-uri/encode-uri.js"
359
335
  },
360
336
  "encodeURIComponent": {
361
337
  "enabled": true,
362
- "type": "overload",
363
- "tags": ["url-encoded"],
364
- "source": "P",
365
- "target": "R",
366
- "command": {
367
- "type": "keep"
368
- }
338
+ "provider": "./propagators/encode-uri/encode-uri-component.js"
369
339
  },
370
340
  "process.__add": {
371
341
  "enabled": true,
@@ -1371,6 +1371,11 @@
1371
1371
  "type": "http",
1372
1372
  "provider": "./sinks/hapi-16-xss"
1373
1373
  },
1374
+ "reflected-xss_dustjs-linkedin": {
1375
+ "enabled": true,
1376
+ "type": "http",
1377
+ "provider": "./sinks/dustjs-linkedin-xss"
1378
+ },
1374
1379
  "reflected-xss": {
1375
1380
  "enabled": true,
1376
1381
  "type": "hook",
@@ -1317,6 +1317,11 @@
1317
1317
  "moduleName": "node-serialize",
1318
1318
  "methodName": "unserialize",
1319
1319
  "isModule": true
1320
+ },
1321
+ "dustjs-linkedin": {
1322
+ "moduleName": "dustjs-linkedin",
1323
+ "methodName": "pipe",
1324
+ "isModule": true
1320
1325
  }
1321
1326
  }
1322
1327
  }
@@ -0,0 +1,22 @@
1
+ /**
2
+ Copyright: 2021 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
+ const { propagate } = require('../template-escape');
17
+
18
+ function handler(data) {
19
+ propagate(data, 'html-encoded', 'dustjs-linkedin.escapeHtml');
20
+ }
21
+
22
+ module.exports.handle = handler;
@@ -0,0 +1,22 @@
1
+ /**
2
+ Copyright: 2021 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
+ const { propagate } = require('../template-escape');
17
+
18
+ function handler(data) {
19
+ propagate(data, 'javascript-encoded', 'dustjs-linkedin.escapeJs');
20
+ }
21
+
22
+ module.exports.handle = handler;
@@ -0,0 +1,22 @@
1
+ /**
2
+ Copyright: 2021 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
+ const { propagate } = require('../template-escape');
17
+
18
+ function handler(data) {
19
+ propagate(data, 'url-encoded', 'global.encodeURIComponent');
20
+ }
21
+
22
+ module.exports.handle = handler;
@@ -0,0 +1,22 @@
1
+ /**
2
+ Copyright: 2021 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
+ const { propagate } = require('../template-escape');
17
+
18
+ function handler(data) {
19
+ propagate(data, 'weak-url-encoded', 'global.encodeURI');
20
+ }
21
+
22
+ module.exports.handle = handler;
@@ -128,8 +128,6 @@ const generateHookWrappers = (agent, policyNode, key) => {
128
128
  } else {
129
129
  ({ pre, post } = provider.handle);
130
130
  }
131
-
132
- propagatorDescriptor.provider = provider.handle;
133
131
  } else {
134
132
  // generic propagator
135
133
  post = new Propagator(agent, propagatorDescriptor);
@@ -51,18 +51,9 @@ function instrumentJoiValues(values) {
51
51
  return;
52
52
  }
53
53
 
54
- const resultIsString = _.isString(result.value);
55
- const argIsString = _.isString(value);
56
-
57
54
  if (result.ref) {
58
- // result === false means ref resolution failed
59
- if (resultIsString && argIsString) {
60
- const resolvedTrackData = tracker.getData(result.value);
61
- const refTrackData = tracker.getData(value);
62
- const handler = getRefHandler(resolvedTrackData, refTrackData);
63
- handler && handler(data, resolvedTrackData, refTrackData);
64
- }
65
- } else if (resultIsString) {
55
+ handler(result.value, value, data);
56
+ } else if (_.isString(result.value)) {
66
57
  // use case is .valid() - safe
67
58
  tracker.untrack(result.value);
68
59
  }
@@ -70,6 +61,30 @@ function instrumentJoiValues(values) {
70
61
  });
71
62
  }
72
63
 
64
+ const stringHandler = (resultValue, argValue, data) => {
65
+ const resultIsString = _.isString(resultValue);
66
+ const argIsString = _.isString(argValue);
67
+
68
+ if (resultIsString && argIsString) {
69
+ const resolvedTrackData = tracker.getData(resultValue);
70
+ const refTrackData = tracker.getData(argValue);
71
+ const handler = getRefHandler(resolvedTrackData, refTrackData);
72
+ handler && handler(data, resolvedTrackData, refTrackData);
73
+ }
74
+ };
75
+
76
+ const handler = (resultValue, argValue, data) => {
77
+ if (_.isString(resultValue)) {
78
+ return stringHandler(resultValue, argValue, data);
79
+ }
80
+
81
+ if (_.isObject(resultValue)) {
82
+ for (const [key, value] of Object.entries(resultValue)) {
83
+ handler(value, argValue[key], data);
84
+ }
85
+ }
86
+ };
87
+
73
88
  /**
74
89
  * Depending on which values are tracked, ref and/or target, returns the
75
90
  * appropriate function to handle the scenario.
@@ -0,0 +1,22 @@
1
+ /**
2
+ Copyright: 2021 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
+ const { propagate } = require('../template-escape');
17
+
18
+ function handler(data) {
19
+ propagate(data, 'html-encoded', 'mustache.escape');
20
+ }
21
+
22
+ module.exports.handle = handler;
@@ -0,0 +1,84 @@
1
+ /**
2
+ Copyright: 2021 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 tracker = require('../../tracker');
18
+ const TagRange = require('../models/tag-range');
19
+ const { CallContext, PropagationEvent, Signature } = require('../models');
20
+
21
+ function getEscapedTagRanges(input, result, start, stop, tag) {
22
+ const textArr = input.split('').slice(start, stop + 1);
23
+ const escapedArr = result.split('');
24
+ const overlap = textArr.filter((x) => {
25
+ if (escapedArr.includes(x)) {
26
+ return x;
27
+ }
28
+ });
29
+ if (overlap.length === 0) {
30
+ return [];
31
+ }
32
+ const newTagRanges = [];
33
+ let firstIndex = escapedArr.indexOf(overlap[0]);
34
+ let currIndex = firstIndex;
35
+ let nextIndex;
36
+ for (let i = 1; i < overlap.length; i++) {
37
+ nextIndex = escapedArr.indexOf(overlap[i], currIndex + 1);
38
+ if (nextIndex !== currIndex + 1) {
39
+ newTagRanges.push(new TagRange(firstIndex, currIndex, tag));
40
+ firstIndex = nextIndex;
41
+ }
42
+ if (i === overlap.length - 1) {
43
+ newTagRanges.push(new TagRange(firstIndex, nextIndex, tag));
44
+ }
45
+ currIndex = nextIndex;
46
+ }
47
+ return newTagRanges;
48
+ }
49
+
50
+ function propagator(data, tagName, signatureName) {
51
+ const input = data.args[0];
52
+
53
+ if (!input || !tracker.getData(input).tracked) {
54
+ return;
55
+ }
56
+
57
+ // adjust tag ranges
58
+ const tagRanges = [];
59
+ tracker.getData(input).tagRanges.forEach((range) => {
60
+ const { start, stop, tag } = range;
61
+ tagRanges.push(
62
+ ...getEscapedTagRanges(input, data.result, start, stop, tag)
63
+ );
64
+ });
65
+ tagRanges.push(new TagRange(0, data.result.length - 1, tagName));
66
+ const result = tracker.track(data.result);
67
+ const trackData = tracker.getData(result);
68
+ trackData.tagRanges = tagRanges;
69
+ trackData.event = new PropagationEvent({
70
+ context: new CallContext({
71
+ ...data,
72
+ obj: null
73
+ }),
74
+ parents: [trackData.event],
75
+ signature: new Signature(signatureName),
76
+ source: 'P',
77
+ target: 'R',
78
+ tagRanges,
79
+ tags: tagName
80
+ });
81
+ data.result = result;
82
+ }
83
+
84
+ module.exports.propagate = propagator;
@@ -21,10 +21,9 @@ const tracker = require('../../tracker.js');
21
21
 
22
22
  const { PropagationEvent, Signature, CallContext } = require('../models');
23
23
  const { isString } = require('../../util/is-string');
24
+ const injections = require('../../core/rewrite/injections');
24
25
 
25
- const ContrastMethods = require('../../core/rewrite/injections').get(
26
- 'ContrastMethods'
27
- );
26
+ const ContrastMethods = injections.get('ContrastMethods');
28
27
 
29
28
  /**
30
29
  * In order to propagate through template literals, we leverage rewriting to
@@ -0,0 +1,131 @@
1
+ /**
2
+ Copyright: 2021 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 patcher = require('../../hooks/patcher');
18
+ const { PATCH_TYPES } = require('../../constants');
19
+ const moduleHook = require('../../hooks/require');
20
+ const { CallContext, Signature } = require('../models');
21
+ const {
22
+ RULES: { REFLECTED_XSS }
23
+ } = require('../../constants');
24
+
25
+ const signature = new Signature({
26
+ moduleName: 'http.ClientResponse',
27
+ methodName: 'write',
28
+ isModule: true
29
+ });
30
+ const moduleName = 'dustjs-linkedin';
31
+
32
+ class Handler {
33
+ constructor({ report, isVulnerable, requiredTags, xss: { disallowedTags } }) {
34
+ this._isVulnerable = isVulnerable;
35
+ this.report = report;
36
+ this.requiredTags = requiredTags;
37
+ this.disallowedTags = disallowedTags;
38
+ }
39
+
40
+ isVulnerable(input) {
41
+ const { requiredTags, disallowedTags } = this;
42
+ const ret = this._isVulnerable({
43
+ input,
44
+ ruleId: REFLECTED_XSS,
45
+ disallowedTags,
46
+ requiredTags
47
+ });
48
+ return ret;
49
+ }
50
+
51
+ handle() {
52
+ moduleHook.resolve({ name: moduleName }, (dust) =>
53
+ this.handleRequire(dust)
54
+ );
55
+ }
56
+
57
+ getType(obj) {
58
+ return obj && obj.constructor && obj.constructor.name;
59
+ }
60
+
61
+ isResponseType(typeName) {
62
+ return typeName === 'ServerResponse';
63
+ }
64
+
65
+ patchDust(dust) {
66
+ const self = this;
67
+ patcher.patch(dust, 'stream', {
68
+ name: moduleName,
69
+ patchType: PATCH_TYPES.ASSESS_SINK,
70
+ alwaysRun: true,
71
+ post(data) {
72
+ self.patchStream(data.result);
73
+ }
74
+ });
75
+ }
76
+
77
+ /**
78
+ * Patch the pipe method and check wether the stream is targeting a
79
+ * response instance. If so, add the instance to the set of streams
80
+ * we should follow.
81
+ */
82
+ patchStream(streamInstance) {
83
+ const self = this;
84
+ patcher.patch(streamInstance, 'pipe', {
85
+ name: 'dust.Stream.prototype',
86
+ patchType: PATCH_TYPES.ASSESS_SINK,
87
+ alwaysRun: true,
88
+ pre({ args: [maybeRes] }) {
89
+ self.patchStreamTarget(maybeRes);
90
+ }
91
+ });
92
+ }
93
+
94
+ patchStreamTarget(target) {
95
+ const self = this;
96
+ const typeName = self.getType(target);
97
+
98
+ if (self.isResponseType(typeName)) {
99
+ patcher.patch(target, 'write', {
100
+ name: 'dust.pipeTarget',
101
+ patchType: PATCH_TYPES.ASSESS_SINK,
102
+ alwaysRun: true,
103
+ post({ args: [str], hooked }) {
104
+ if (self.isVulnerable(str)) {
105
+ self.report({
106
+ ruleId: REFLECTED_XSS,
107
+ signature,
108
+ input: str,
109
+ ctxt: new CallContext({
110
+ obj: typeName,
111
+ args: [str],
112
+ result: str,
113
+ stackOpts: {
114
+ constructorOpt: hooked
115
+ }
116
+ })
117
+ });
118
+ }
119
+ }
120
+ });
121
+ }
122
+ }
123
+
124
+ handleRequire(dust) {
125
+ this.patchDust(dust);
126
+ return dust;
127
+ }
128
+ }
129
+
130
+ module.exports = ({ common }) => new Handler(common);
131
+ module.exports.Handler = Handler;
@@ -0,0 +1,53 @@
1
+ /**
2
+ Copyright: 2021 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 { PATCH_TYPES } = require('../../constants');
19
+ const ModuleHook = require('../../hooks/require');
20
+ const patcher = require('../../hooks/patcher');
21
+ const logger = require('../logger')('contrast:arch-component');
22
+
23
+ ModuleHook.resolve({ name: 'rethinkdb' }, (rethinkdb) => {
24
+ patcher.patch(rethinkdb, 'connect', {
25
+ name: 'rethinkdb.arch_component',
26
+ patchType: PATCH_TYPES.ARCH_COMPONENT,
27
+ alwaysRun: true,
28
+ post(ctx) {
29
+ ctx.result
30
+ .then((res) => {
31
+ if (res.open) {
32
+ const url =
33
+ res.host == 'localhost'
34
+ ? 'http://localhost'
35
+ : new URL(res.host).toString();
36
+ agentEmitter.emit('architectureComponent', {
37
+ vendor: 'RethinkDB',
38
+ url,
39
+ remotePort: res.port
40
+ });
41
+ } else {
42
+ logger.warn('unable to open RethinkDB connection');
43
+ }
44
+ })
45
+ .catch((err) => {
46
+ logger.warn(
47
+ 'unable to report RethinkDB architecture component\n%o',
48
+ err
49
+ );
50
+ });
51
+ }
52
+ });
53
+ });
@@ -481,7 +481,7 @@ const agent = [
481
481
  {
482
482
  name: 'agent.stack_trace_limit',
483
483
  arg: '<limit>',
484
- default: 25,
484
+ default: 10,
485
485
  fn: parseNum,
486
486
  desc:
487
487
  'set limit for stack trace size (larger limits will improve accuracy but increase memory usage)'
@@ -97,12 +97,7 @@ Function.prototype._contrast_toString = function() {
97
97
  return Reflect.apply(functionToString, this, arguments);
98
98
  };
99
99
 
100
- function runHooks(type, options, data, fn, thisTarget) {
101
- const fnHooks = hooks.get(fn);
102
- if (!fnHooks) {
103
- return;
104
- }
105
-
100
+ function runHooks(type, data, thisTarget, fnHooks) {
106
101
  fnHooks.forEach((hook, key) => {
107
102
  if (hook[type]) {
108
103
  hook[type].apply(thisTarget, [data]);
@@ -185,11 +180,17 @@ function runOriginalFunction(fn, { args, name }, target) {
185
180
  return fn.apply(this, args);
186
181
  }
187
182
 
188
- function recordTime(func, funcKey, type) {
183
+ const runWrapper =
184
+ !process.hrtime.bigint || !perfLoggingEnabled
185
+ ? runWithoutRecordingTime
186
+ : runAndRecordTime;
187
+
188
+ function runWithoutRecordingTime(func) {
189
+ return () => func();
190
+ }
191
+
192
+ function runAndRecordTime(func, funcKey, type) {
189
193
  return () => {
190
- if (!process.hrtime.bigint || !perfLoggingEnabled) {
191
- return func();
192
- }
193
194
  const start = process.hrtime.bigint();
194
195
  const rv = func();
195
196
  perfLogger[type](funcKey, Number(process.hrtime.bigint() - start));
@@ -217,6 +218,7 @@ function hookFunction(fn, options) {
217
218
  const { funcKey } = options;
218
219
  logger.trace(`hook ${funcKey}`);
219
220
 
221
+ // eslint-disable-next-line complexity
220
222
  function hooked(...args) {
221
223
  const target = new.target;
222
224
 
@@ -247,36 +249,59 @@ function hookFunction(fn, options) {
247
249
  perfLogger.logEvent(funcKey);
248
250
  }
249
251
 
250
- // Run the pre event in a no instrumentation scope
252
+ let hasPreHook, hasPostHook;
253
+ const fnHooks = hooks && hooks.get(hooked);
254
+
255
+ // If there's a pre event run it in a no instrumentation scope
251
256
  // this will prevent other instrumentation from running
252
257
  // within the pre function
253
- Scopes.runInNoInstrumentationScope(
254
- recordTime(
255
- () => runHooks('pre', options, data, hooked, this),
256
- funcKey,
257
- 'pre'
258
- ),
259
- `${funcKey} pre`
260
- );
258
+ if (fnHooks) {
259
+ for (const storedHooks of fnHooks.values()) {
260
+ hasPreHook = Boolean(hasPreHook || (storedHooks && storedHooks.pre));
261
+ hasPostHook = Boolean(hasPostHook || (storedHooks && storedHooks.post));
262
+ }
263
+ }
261
264
 
262
- // run original function in scope that was passed in
263
- data.result = Scopes.runIn(
264
- options.scope,
265
- recordTime(() => getResult.call(this, fn, data, target), funcKey, 'orig'),
266
- funcKey
267
- );
265
+ if (hasPreHook) {
266
+ Scopes.runInNoInstrumentationScope(
267
+ runWrapper(() => runHooks('pre', data, this, fnHooks), funcKey, 'pre'),
268
+ `${funcKey} pre`
269
+ );
270
+ }
268
271
 
269
- // Run the post event in a no instrumentation scope
272
+ // Run original function in scope that was passed in
273
+ // or just run the original function when the passed scope is undefined
274
+ if (options.scope) {
275
+ data.result = Scopes.runIn(
276
+ options.scope,
277
+ runWrapper(
278
+ () => getResult.call(this, fn, data, target),
279
+ funcKey,
280
+ 'orig'
281
+ ),
282
+ funcKey
283
+ );
284
+ } else {
285
+ data.result = runWrapper(
286
+ () => getResult.call(this, fn, data, target),
287
+ funcKey,
288
+ 'orig'
289
+ )();
290
+ }
291
+
292
+ // If there's a post event run it in a no instrumentation scope
270
293
  // this will prevent other instrumentation from running
271
294
  // within the post function
272
- Scopes.runInNoInstrumentationScope(
273
- recordTime(
274
- () => runHooks('post', options, data, hooked, this),
275
- funcKey,
276
- 'post'
277
- ),
278
- `${funcKey} post`
279
- );
295
+ if (hasPostHook) {
296
+ Scopes.runInNoInstrumentationScope(
297
+ runWrapper(
298
+ () => runHooks('post', data, this, fnHooks),
299
+ funcKey,
300
+ 'post'
301
+ ),
302
+ `${funcKey} post`
303
+ );
304
+ }
280
305
 
281
306
  return data.result;
282
307
  }
@@ -436,8 +461,7 @@ function hook(obj, prop, opts) {
436
461
  }
437
462
  } catch (err) {
438
463
  logger.info(
439
- `unable to patch unconfigurable property ${opts.name}:${prop}
440
- `,
464
+ `unable to patch unconfigurable property ${opts.name}:${prop}`,
441
465
  err
442
466
  );
443
467
  }
@@ -543,7 +567,6 @@ function unwrap(fn) {
543
567
  module.exports = {
544
568
  patch,
545
569
  preempt,
546
- recordTime,
547
570
  resetInstrumentation,
548
571
  unpatch,
549
572
  unwrap
@@ -133,7 +133,7 @@ class SourceMapUtility {
133
133
  const { line, source } = consumer.originalPositionFor({
134
134
  line: lineNumber,
135
135
  column
136
- });
136
+ }) || { line: undefined, source: undefined };
137
137
 
138
138
  if (line) lineNumber = line;
139
139
  if (source) file = this.replaceSource(file, source);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/agent",
3
- "version": "4.6.0",
3
+ "version": "4.7.0",
4
4
  "description": "Node.js security instrumentation by Contrast Security",
5
5
  "keywords": [
6
6
  "security",
@@ -165,7 +165,7 @@
165
165
  "proxyquire": "^2.1.0",
166
166
  "qs": "^6.9.4",
167
167
  "rethinkdb": "file:test/mock/rethinkdb",
168
- "sequelize": "^6.3.3",
168
+ "sequelize": "^6.11.0",
169
169
  "shellcheck": "^1.0.0",
170
170
  "sinon": "^7.2.2",
171
171
  "sinon-chai": "^3.3.0",