@contrast/agent 4.8.0 → 4.10.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.
Files changed (59) 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/bootstrap.js +12 -2
  6. package/esm.mjs +33 -0
  7. package/lib/assess/index.js +2 -0
  8. package/lib/assess/models/source-event.js +6 -0
  9. package/lib/assess/policy/rules.json +29 -0
  10. package/lib/assess/policy/signatures.json +6 -0
  11. package/lib/assess/propagators/JSON/stringify.js +77 -7
  12. package/lib/assess/propagators/joi/any.js +48 -0
  13. package/lib/assess/propagators/joi/index.js +2 -0
  14. package/lib/assess/propagators/joi/object.js +61 -0
  15. package/lib/assess/propagators/joi/string-base.js +16 -0
  16. package/lib/assess/propagators/mongoose/helpers.js +36 -1
  17. package/lib/assess/propagators/mongoose/index.js +1 -0
  18. package/lib/assess/propagators/mongoose/map.js +19 -31
  19. package/lib/assess/propagators/mongoose/mixed.js +71 -0
  20. package/lib/assess/propagators/mongoose/string.js +8 -0
  21. package/lib/assess/sinks/rethinkdb-nosql-injection.js +142 -0
  22. package/lib/assess/sources/event-handler.js +307 -0
  23. package/lib/assess/sources/index.js +93 -5
  24. package/lib/assess/spdy/index.js +23 -0
  25. package/lib/assess/spdy/sinks/index.js +23 -0
  26. package/lib/assess/spdy/sinks/xss.js +84 -0
  27. package/lib/assess/technologies/index.js +2 -1
  28. package/lib/constants.js +2 -1
  29. package/lib/contrast.js +6 -6
  30. package/lib/core/arch-components/index.js +1 -0
  31. package/lib/core/arch-components/mongodb.js +22 -18
  32. package/lib/core/arch-components/mysql.js +25 -15
  33. package/lib/core/arch-components/postgres.js +40 -12
  34. package/lib/core/arch-components/sqlite3.js +3 -5
  35. package/lib/core/arch-components/util.js +49 -0
  36. package/lib/core/config/options.js +37 -1
  37. package/lib/core/exclusions/exclusion.js +2 -5
  38. package/lib/core/express/index.js +28 -2
  39. package/lib/core/express/utils.js +8 -3
  40. package/lib/core/fastify/index.js +2 -1
  41. package/lib/core/hapi/index.js +2 -1
  42. package/lib/core/koa/index.js +9 -1
  43. package/lib/core/rewrite/callees.js +16 -0
  44. package/lib/core/rewrite/import-declaration.js +71 -0
  45. package/lib/core/rewrite/index.js +9 -7
  46. package/lib/core/rewrite/injections.js +5 -1
  47. package/lib/hooks/frameworks/index.js +2 -0
  48. package/lib/hooks/frameworks/spdy.js +87 -0
  49. package/lib/hooks/http.js +11 -0
  50. package/lib/protect/restify/sources.js +35 -0
  51. package/lib/protect/rules/nosqli/nosql-injection-rule.js +30 -16
  52. package/lib/protect/rules/nosqli/nosql-scanner/index.js +1 -1
  53. package/lib/protect/rules/nosqli/nosql-scanner/rethinkdbscanner.js +26 -0
  54. package/lib/protect/sinks/index.js +2 -0
  55. package/lib/protect/sinks/mongodb.js +1 -3
  56. package/lib/protect/sinks/rethinkdb.js +47 -0
  57. package/lib/reporter/translations/to-protobuf/dtm/trace-event/index.js +4 -4
  58. package/lib/util/source-map.js +3 -3
  59. package/package.json +18 -12
@@ -0,0 +1,23 @@
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 ReflectedXss = require('./xss');
18
+
19
+ module.exports = class SpdySinks {
20
+ constructor(agent) {
21
+ new ReflectedXss(agent);
22
+ }
23
+ };
@@ -0,0 +1,84 @@
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 { HTTP_RESPONSE_HOOKED_METHOD_KEYS } = require('../../../constants');
19
+ const policy = require('../../policy');
20
+ const { Signature, CallContext } = require('../../models');
21
+
22
+ class SpdyXss {
23
+ constructor(agent) {
24
+ this.common = require('../../sinks/common')(agent);
25
+ this.rules = policy.rules;
26
+ this.ruleId = 'reflected-xss';
27
+ this.signature = new Signature({
28
+ moduleName: 'spdy.response',
29
+ methodName: 'push',
30
+ isModule: false
31
+ });
32
+ agentEmitter.on(
33
+ HTTP_RESPONSE_HOOKED_METHOD_KEYS.PUSH,
34
+ this.checkResult.bind(this)
35
+ );
36
+ }
37
+
38
+ /**
39
+ * checks if an assess rule is enabled in policy
40
+ */
41
+ get enabled() {
42
+ return (
43
+ this.rules &&
44
+ this.rules['reflected-xss'] &&
45
+ this.rules['reflected-xss'].enabled
46
+ );
47
+ }
48
+
49
+ checkResult(body) {
50
+ if (!this.enabled) {
51
+ return;
52
+ }
53
+
54
+ const { ruleId, signature } = this;
55
+
56
+ const {
57
+ isVulnerable,
58
+ xss: { disallowedTags },
59
+ requiredTags,
60
+ report
61
+ } = this.common;
62
+
63
+ if (
64
+ isVulnerable({
65
+ input: body,
66
+ disallowedTags,
67
+ requiredTags,
68
+ ruleId
69
+ })
70
+ ) {
71
+ const ctxt = new CallContext({
72
+ obj: body,
73
+ args: [body],
74
+ result: body,
75
+ stackOpts: {
76
+ constructorOpt: agentEmitter.emit
77
+ }
78
+ });
79
+ report({ ruleId, signature, input: body, ctxt });
80
+ }
81
+ }
82
+ }
83
+
84
+ module.exports = SpdyXss;
@@ -31,7 +31,8 @@ const technologies = {
31
31
  'fastify',
32
32
  'restify',
33
33
  'loopback',
34
- 'kraken-js'
34
+ 'kraken-js',
35
+ 'sails'
35
36
  ],
36
37
  templating: ['jade', 'ejs', 'nunjucks', 'mustache', 'dust', 'handlebars'],
37
38
  loggers: ['winston', 'debug'],
package/lib/constants.js CHANGED
@@ -644,7 +644,8 @@ const REQUIRED_SIGNATURE_KEYS = [
644
644
 
645
645
  const HTTP_RESPONSE_HOOKED_METHOD_KEYS = {
646
646
  WRITE_HEAD: Symbol('writeHead'),
647
- END: Symbol('end')
647
+ END: Symbol('end'),
648
+ PUSH: Symbol('push')
648
649
  };
649
650
 
650
651
  const PATCH_TYPES = {
package/lib/contrast.js CHANGED
@@ -178,7 +178,7 @@ contrastAgent.configureGlobalLogger = function(config, args, target = global) {
178
178
 
179
179
  function getAgentArgs(options) {
180
180
  const agentArgs = {};
181
- options.options.forEach((opt) => {
181
+ program.options.forEach((opt) => {
182
182
  if (opt.name() !== 'application.args' && options[opt.name()]) {
183
183
  agentArgs[opt.name()] = options[opt.name()];
184
184
  }
@@ -243,8 +243,8 @@ contrastAgent.prepare = function(...args) {
243
243
 
244
244
  logger.info('Using config file at %s', config.configFile);
245
245
  // log the argv before and after modification.
246
- logger.info(`Original argv: ${options.rawArgs.join(', ')}`);
247
- logger.info(`Modified argv: ${options.args.join(', ')}`);
246
+ logger.info(`Original argv: ${program.rawArgs.join(', ')}`);
247
+ logger.info(`Modified argv: ${program.args.join(', ')}`);
248
248
 
249
249
  agent.config = config;
250
250
  agent.tsFeatureSet.config = config;
@@ -335,12 +335,12 @@ contrastAgent.init = async function(args, isCli = false) {
335
335
  // source: args passed to cli, destination: args after cli parsed it
336
336
  .action(async function callPrepare(options, commanderArgs = []) {
337
337
  // the user app main differs if a runner vs preload
338
- script = isCli ? options.args[0] : options.rawArgs[1];
338
+ script = isCli ? program.args[0] : program.rawArgs[1];
339
339
  options.script = script;
340
340
  // need to slice off app main in runner mode
341
341
  options['application.args'] = isCli
342
- ? options.args.slice(1)
343
- : options.args;
342
+ ? program.args.slice(1)
343
+ : program.args;
344
344
 
345
345
  try {
346
346
  enabled = await contrastAgent.prepare(options, commanderArgs, isCli);
@@ -18,3 +18,4 @@ require('./sqlite3');
18
18
  require('./postgres');
19
19
  require('./dynamodb');
20
20
  require('./dynamodbv3');
21
+ require('./rethinkdb');
@@ -28,25 +28,29 @@ ModuleHook.resolve(
28
28
  patchType: PATCH_TYPES.ARCH_COMPONENT,
29
29
  alwaysRun: true,
30
30
  post(ctx) {
31
- try {
32
- const { servers = [] } = this.s.options;
33
- if (servers.length === 0) {
34
- logger.warn('Unable to find any MongoDB servers\n');
35
- }
36
- for (const server of servers) {
37
- agentEmitter.emit('architectureComponent', {
38
- vendor: 'MongoDB',
39
- url: `mongodb://${server.host}`,
40
- remoteHost: '',
41
- remotePort: server.port
42
- });
43
- }
44
- } catch (err) {
45
- logger.warn(
46
- 'unable to report MongoDB architecture component\n%o',
47
- err
48
- );
31
+ if (!ctx.result || !ctx.result.then) {
32
+ return;
49
33
  }
34
+
35
+ // We should report only when connection is successful
36
+ ctx.result.then(function(client) {
37
+ try {
38
+ const { servers = [] } = ctx.obj.s && ctx.obj.s.options;
39
+ for (const server of servers) {
40
+ agentEmitter.emit('architectureComponent', {
41
+ vendor: 'MongoDB',
42
+ url: `mongodb://${server.host}:${server.port}`,
43
+ remoteHost: '',
44
+ remotePort: server.port
45
+ });
46
+ }
47
+ } catch (err) {
48
+ logger.warn(
49
+ 'unable to report MongoDB architecture component\n%o',
50
+ err
51
+ );
52
+ }
53
+ });
50
54
  }
51
55
  });
52
56
  }
@@ -19,6 +19,7 @@ const ModuleHook = require('../../hooks/require');
19
19
  const agentEmitter = require('../../agent-emitter');
20
20
  const { PATCH_TYPES } = require('../../constants');
21
21
  const logger = require('../logger')('contrast:arch-component');
22
+ const waitToConnect = require('./util');
22
23
 
23
24
  ModuleHook.resolve(
24
25
  { name: 'mysql', file: 'lib/Connection.js' },
@@ -28,22 +29,31 @@ ModuleHook.resolve(
28
29
  patchType: PATCH_TYPES.ARCH_COMPONENT,
29
30
  alwaysRun: true,
30
31
  post(wrapCtx) {
31
- try {
32
- let url = 'mysql://';
33
- if (this.config.socketPath) {
34
- url += this.config.socketPath;
35
- } else {
36
- url += `${this.config.host}`;
37
- }
38
- agentEmitter.emit('architectureComponent', {
39
- vendor: 'MySQL',
40
- url: new URL(url).toString(),
41
- remoteHost: '',
42
- remotePort: !this.config.socketPath ? this.config.port : -1
32
+ waitToConnect(wrapCtx)
33
+ .then(() => {
34
+ try {
35
+ let url = 'mysql://';
36
+ if (this.config.socketPath) {
37
+ url += this.config.socketPath;
38
+ } else {
39
+ url += `${this.config.host}`;
40
+ }
41
+ agentEmitter.emit('architectureComponent', {
42
+ vendor: 'MySQL',
43
+ url: new URL(url).toString(),
44
+ remoteHost: '',
45
+ remotePort: !this.config.socketPath ? this.config.port : -1
46
+ });
47
+ } catch (err) {
48
+ logger.warn(
49
+ 'unable to report MySQL architecture component\n',
50
+ err
51
+ );
52
+ }
53
+ })
54
+ .catch((err) => {
55
+ logger.warn('unable to report MySQL architecture component\n', err);
43
56
  });
44
- } catch (err) {
45
- logger.warn('unable to report MySQL architecture component\n', err);
46
- }
47
57
  }
48
58
  });
49
59
  }
@@ -18,6 +18,7 @@ const ModuleHook = require('../../hooks/require');
18
18
  const agentEmitter = require('../../agent-emitter');
19
19
  const { PATCH_TYPES } = require('../../constants');
20
20
  const logger = require('../logger')('contrast:arch-component');
21
+ const waitToConnect = require('./util');
21
22
 
22
23
  ModuleHook.resolve({ name: 'pg', file: 'lib/client.js' }, (pgClient) =>
23
24
  patcher.patch(pgClient, {
@@ -25,19 +26,46 @@ ModuleHook.resolve({ name: 'pg', file: 'lib/client.js' }, (pgClient) =>
25
26
  patchType: PATCH_TYPES.ARCH_COMPONENT,
26
27
  alwaysRun: true,
27
28
  post(wrapCtx) {
28
- try {
29
- const { host, port } = wrapCtx.result;
30
- agentEmitter.emit('architectureComponent', {
31
- vendor: 'PostgreSQL',
32
- remotePort: port || 0,
33
- url: new URL(host).toString()
29
+ waitToConnect(wrapCtx)
30
+ .then(() => {
31
+ try {
32
+ const {
33
+ host = process.env.PGHOST,
34
+ port = process.env.PGPORT
35
+ } = wrapCtx.result;
36
+
37
+ if (!host) {
38
+ return;
39
+ }
40
+
41
+ let url = host;
42
+
43
+ // build protocol and port into url prior to parsing
44
+ if (url.indexOf('://') === -1) {
45
+ url = `postgresql://${url}`;
46
+ }
47
+ if (port !== undefined) {
48
+ url = `${url}:${port}`;
49
+ }
50
+
51
+ agentEmitter.emit('architectureComponent', {
52
+ vendor: 'PostgreSQL',
53
+ remotePort: port || 0,
54
+ url: new URL(url).toString()
55
+ });
56
+ } catch (err) {
57
+ logger.warn(
58
+ 'unable to report PostgreSQL architecture component\n%o',
59
+ err
60
+ );
61
+ }
62
+ })
63
+ .catch((err) => {
64
+ logger.warn(
65
+ 'unable to report PostgreSQL architecture component\n%o',
66
+ err
67
+ );
34
68
  });
35
- } catch (err) {
36
- logger.warn(
37
- 'unable to report PostgreSQL architecture component\n',
38
- err
39
- );
40
- }
41
69
  }
42
70
  })
43
71
  );
@@ -13,6 +13,7 @@ Copyright: 2022 Contrast Security, Inc
13
13
  way not consistent with the End User License Agreement.
14
14
  */
15
15
  'use strict';
16
+
16
17
  const patcher = require('../../hooks/patcher');
17
18
  const ModuleHook = require('../../hooks/require');
18
19
  const agentEmitter = require('../../agent-emitter');
@@ -26,17 +27,14 @@ ModuleHook.resolve({ name: 'sqlite3' }, (sqlite3) => {
26
27
  alwaysRun: true,
27
28
  post(wrapCtx) {
28
29
  try {
29
- // can either be a path to a file or `:memory:'.
30
- const url = new URL(wrapCtx.args[0]).toString();
31
-
32
30
  agentEmitter.emit('architectureComponent', {
33
31
  vendor: 'SQLite3',
34
- url,
32
+ url: wrapCtx.args[0],
35
33
  remoteHost: '',
36
34
  remotePort: 0
37
35
  });
38
36
  } catch (err) {
39
- logger.warn('unable to report SQLite3 architecture component\n', err);
37
+ logger.warn('unable to report SQLite3 architecture component\n%o', err);
40
38
  }
41
39
  }
42
40
  });
@@ -0,0 +1,49 @@
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
+ const MYSQL = 'mysql.connect.arch_component';
16
+ const POSTGRES = 'pg.Client.arch_component';
17
+
18
+ module.exports = function waitToConnect(ctx, count = 0) {
19
+ return new Promise((resolve, reject) => {
20
+ const maxAttempts = 10 * 60; // i.e., 1 min.
21
+ const checkConnection = setInterval(
22
+ function(ctx, resolve, reject) {
23
+ if (count >= maxAttempts) {
24
+ clearInterval(checkConnection);
25
+ reject();
26
+ }
27
+ switch (ctx.name) {
28
+ case MYSQL:
29
+ if (ctx.obj.state === 'authenticated') {
30
+ clearInterval(checkConnection);
31
+ resolve();
32
+ }
33
+ break;
34
+ case POSTGRES:
35
+ if (ctx.result._connected) {
36
+ clearInterval(checkConnection);
37
+ resolve();
38
+ }
39
+ break;
40
+ }
41
+ count++;
42
+ },
43
+ 100,
44
+ ctx,
45
+ resolve,
46
+ reject
47
+ );
48
+ });
49
+ };
@@ -33,7 +33,8 @@ Copyright: 2022 Contrast Security, Inc
33
33
  * @module lib/core/config/options
34
34
  */
35
35
  'use strict';
36
- const program = require('commander');
36
+ const { Command, Option } = require('commander');
37
+ const program = new Command();
37
38
  const os = require('os');
38
39
  const url = require('url');
39
40
  const path = require('path');
@@ -486,6 +487,18 @@ const agent = [
486
487
  desc:
487
488
  'set limit for stack trace size (larger limits will improve accuracy but increase memory usage)'
488
489
  },
490
+ {
491
+ name: 'agent.trust_custom_validators',
492
+ arg: '<trust-custom-validators>',
493
+ default: false,
494
+ desc: `trust incoming strings when they pass custom validators (Mongoose, Joi)`
495
+ },
496
+ {
497
+ name: 'agent.traverse_and_track',
498
+ arg: '<traverse-and-track>',
499
+ default: false,
500
+ desc: 'source membrane alternative'
501
+ },
489
502
  {
490
503
  name: 'agent.polling.app_activity_ms',
491
504
  arg: '<ms>',
@@ -967,6 +980,16 @@ if (process.env.CONTRAST_DEV) {
967
980
  }
968
981
  ];
969
982
  }
983
+ const sails = [
984
+ {
985
+ name: 'pathToSails',
986
+ arg: '<path>'
987
+ },
988
+ {
989
+ name: 'gdsrc',
990
+ arg: '<path>'
991
+ }
992
+ ];
970
993
 
971
994
  const options = [].concat(
972
995
  misc,
@@ -1008,6 +1031,19 @@ options.forEach((option) => {
1008
1031
  program.option(name, option.desc);
1009
1032
  });
1010
1033
 
1034
+ // In NODE-2059 it was discovered that a module was appending config options that the
1035
+ // agent didn't recognize and was causing the application to not load properly.
1036
+ // The agent doesn't need to do anything with these options. It just needs to not
1037
+ // throw an error when it encounters them but we also don't need them displayed on
1038
+ // the agent's config option list. The newest version of Commander lets us do exactly this.
1039
+ // This is structured so that if anything like this is discovered again, they can be
1040
+ // added in easily.
1041
+ const hiddenOptions = [].concat(sails);
1042
+
1043
+ hiddenOptions.forEach((option) => {
1044
+ program.addOption(new Option(`--${option.name} ${option.arg}`).hideHelp());
1045
+ });
1046
+
1011
1047
  function getDefault(optionName) {
1012
1048
  let option;
1013
1049
  options.forEach((entry) => {
@@ -31,12 +31,9 @@ class Exclusion {
31
31
  return this.assess && this.appliesToRule(id, this.assessmentRulesList);
32
32
  }
33
33
 
34
+ // When an exclusion applies to all rules, its rules list is empty
34
35
  appliesToRule(id, list) {
35
- // When an exclusion applies to all rules, its rules list is empty
36
- const appliesToAllRules = list.length === 0;
37
- const appliesToRuleId = list.includes(id);
38
-
39
- return appliesToAllRules || appliesToRuleId;
36
+ return list.length === 0 || list.includes(id);
40
37
  }
41
38
 
42
39
  appliesToAllAssessRules() {
@@ -120,6 +120,24 @@ class ExpressFramework {
120
120
  }
121
121
  }
122
122
  });
123
+
124
+ patcher.patch(express.response, 'push', {
125
+ name: 'express.response.push',
126
+ patchType: PATCH_TYPES.PROTECT_SINK,
127
+ pre(data) {
128
+ agentEmitter.emit(
129
+ EVENTS.REQUEST_SEND,
130
+ data.args[0],
131
+ SINK_TYPES.RESPONSE_BODY
132
+ );
133
+
134
+ const body = data.args[0];
135
+ if (isString(body)) {
136
+ emitSendEvent(body.valueOf());
137
+ }
138
+ }
139
+ });
140
+
123
141
  patcher.patch(express.response, 'end', {
124
142
  name: 'express.response.end',
125
143
  patchType: PATCH_TYPES.PROTECT_SINK,
@@ -310,7 +328,9 @@ class ExpressFramework {
310
328
  }, 'textParser');
311
329
 
312
330
  this.useAfter(function ContrastBodyParsed(req, res, next) {
313
- agentEmitter.emit(EVENTS.BODY_PARSED, req, res, INPUT_TYPES.BODY);
331
+ agentEmitter.emit(EVENTS.BODY_PARSED, req, res, {
332
+ type: INPUT_TYPES.BODY
333
+ });
314
334
  next();
315
335
  }, 'urlencodedParser');
316
336
 
@@ -356,7 +376,7 @@ class ExpressFramework {
356
376
  const self = this;
357
377
 
358
378
  // Hook the request handler so that we can access the top of each route.
359
- // This is the only place the "params" hash is avaible
379
+ // This is the only place the "params" hash is available
360
380
  const Layer = ExpressFramework.getStack(app)[0].constructor;
361
381
  const _handle = Layer.prototype.handle_request;
362
382
  if (_handle) {
@@ -377,6 +397,9 @@ class ExpressFramework {
377
397
  req[LAYER_STACK].pop();
378
398
  }
379
399
  });
400
+ if (req.query) {
401
+ decorateRequest({ query: req.query });
402
+ }
380
403
  const params = new Object(this.params);
381
404
  if (Object.keys(params).length) {
382
405
  agentEmitter.emit(
@@ -399,9 +422,12 @@ class ExpressFramework {
399
422
 
400
423
  Whatever the core issue is, it doesn't appear to have any effects
401
424
  elsewhere in any of our Express/Kraken framework support.
425
+
426
+ BODY_PARSED event is emitted to support Sails framework
402
427
  */
403
428
  if (req.body) {
404
429
  decorateRequest({ body: req.body });
430
+ agentEmitter.emit(EVENTS.BODY_PARSED, req, res, req.body);
405
431
  }
406
432
  }
407
433
  });
@@ -33,7 +33,7 @@ const {
33
33
 
34
34
  const EVENTS = {
35
35
  REQUEST_READY: 'Express.RequestReady',
36
- BODY_PARSED: 'Express.cookiesParsed',
36
+ BODY_PARSED: 'Express.bodyParsed',
37
37
  COOKIES_PARSED: 'Express.cookiesParsed',
38
38
  PARAMS_PARSED: 'Express.paramsParsed',
39
39
  REQUEST_SEND: 'Express.requestSend',
@@ -246,7 +246,9 @@ const captureSignature = (layer, signature) => {
246
246
  const normalizeSignatureArg = (arg) => {
247
247
  // we weave in middleware for express that is prefixed with Contrast
248
248
  // remove this from signature
249
- if (typeof arg === 'function' && arg.name.startsWith('Contrast')) {
249
+ if (arg === '') {
250
+ return null;
251
+ } else if (typeof arg === 'function' && arg.name.startsWith('Contrast')) {
250
252
  return null;
251
253
  } else if (typeof arg === 'function') {
252
254
  return getHandlerName(arg);
@@ -313,8 +315,11 @@ const updateRouterSignatures = (self, router, path) => {
313
315
  return;
314
316
  }
315
317
  const routePath = _.get(route, 'path', '');
316
- const routeHandle = _.get(route, 'stack[0].handle');
318
+ const routeHandle = _.get(route, 'stack[0].handle', '');
317
319
  const routeMethod = _.get(route, 'stack[0].method');
320
+ if (!routeMethod) {
321
+ return;
322
+ }
318
323
  const newSignature = createSignature(self, 'Router', routeMethod, [
319
324
  path,
320
325
  routePath,
@@ -154,7 +154,8 @@ class FastifyCore {
154
154
  agentEmitter.emit(constants.EVENTS.PRE_VALIDATION, ...data);
155
155
  decorateRequest({
156
156
  body: request.body,
157
- parameters: request.params
157
+ parameters: request.params,
158
+ query: request.query
158
159
  });
159
160
  done();
160
161
  }
@@ -173,7 +173,8 @@ class HapiCore {
173
173
  decorateRequest({
174
174
  normalizedUri: get(request, 'route.path'),
175
175
  body: request.payload,
176
- parameters: request.params
176
+ parameters: request.params,
177
+ query: request.query
177
178
  });
178
179
  return h.continue;
179
180
  });
@@ -216,13 +216,21 @@ class KoaCore {
216
216
  */
217
217
  registerRequestHandler(ctx) {
218
218
  ctx.req.request = ctx.request;
219
+ const { query } = ctx.request;
219
220
  ctx.request = new Proxy(ctx.request, {
220
221
  set(...args) {
221
222
  const [obj, prop] = args;
222
223
  const result = Reflect.set(...args);
224
+ if (query) {
225
+ decorateRequest({
226
+ query
227
+ });
228
+ }
223
229
  switch (prop) {
224
230
  case 'body':
225
- decorateRequest({ body: obj[prop] });
231
+ decorateRequest({
232
+ body: obj[prop]
233
+ });
226
234
  agentEmitter.emit(constants.EVENTS.CTX_REQUEST_BODY, {
227
235
  request: obj,
228
236
  ctx
@@ -67,6 +67,22 @@ const specs = [
67
67
  token: 'eval',
68
68
  modes: { assess: true, protect: true }
69
69
  },
70
+ // Import Declaration
71
+ {
72
+ name: '__importDefault',
73
+ type: 'ImportDefaultSpecifier',
74
+ modes: { assess: true, protect: true }
75
+ },
76
+ {
77
+ name: '__importNamespace',
78
+ type: 'ImportNamespaceSpecifier',
79
+ modes: { assess: true, protect: true }
80
+ },
81
+ {
82
+ name: '__import',
83
+ type: 'ImportSpecifier',
84
+ modes: { assess: true, protect: true }
85
+ },
70
86
  // Member Expression
71
87
  {
72
88
  name: '__forceCopy',