@contrast/agent 4.8.0 → 4.9.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.
@@ -26,15 +26,33 @@ ModuleHook.resolve({ name: 'pg', file: 'lib/client.js' }, (pgClient) =>
26
26
  alwaysRun: true,
27
27
  post(wrapCtx) {
28
28
  try {
29
- const { host, port } = wrapCtx.result;
29
+ const {
30
+ host = process.env.PGHOST,
31
+ port = process.env.PGPORT
32
+ } = wrapCtx.result;
33
+
34
+ if (!host) {
35
+ return;
36
+ }
37
+
38
+ let url = host;
39
+
40
+ // build protocol and port into url prior to parsing
41
+ if (url.indexOf('://') === -1) {
42
+ url = `postgresql://${url}`;
43
+ }
44
+ if (port !== undefined) {
45
+ url = `${url}:${port}`;
46
+ }
47
+
30
48
  agentEmitter.emit('architectureComponent', {
31
49
  vendor: 'PostgreSQL',
32
50
  remotePort: port || 0,
33
- url: new URL(host).toString()
51
+ url: new URL(url).toString()
34
52
  });
35
53
  } catch (err) {
36
54
  logger.warn(
37
- 'unable to report PostgreSQL architecture component\n',
55
+ 'unable to report PostgreSQL architecture component\n%o',
38
56
  err
39
57
  );
40
58
  }
@@ -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
  });
@@ -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,12 @@ 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.traverse_and_track',
492
+ arg: '<traverse-and-track>',
493
+ default: false,
494
+ desc: 'source membrane alternative'
495
+ },
489
496
  {
490
497
  name: 'agent.polling.app_activity_ms',
491
498
  arg: '<ms>',
@@ -967,6 +974,16 @@ if (process.env.CONTRAST_DEV) {
967
974
  }
968
975
  ];
969
976
  }
977
+ const sails = [
978
+ {
979
+ name: 'pathToSails',
980
+ arg: '<path>',
981
+ },
982
+ {
983
+ name: 'gdsrc',
984
+ arg: '<path>',
985
+ }
986
+ ];
970
987
 
971
988
  const options = [].concat(
972
989
  misc,
@@ -1008,6 +1025,23 @@ options.forEach((option) => {
1008
1025
  program.option(name, option.desc);
1009
1026
  });
1010
1027
 
1028
+ // In NODE-2059 it was discovered that a module was appending config options that the
1029
+ // agent didn't recognize and was causing the application to not load properly.
1030
+ // The agent doesn't need to do anything with these options. It just needs to not
1031
+ // throw an error when it encounters them but we also don't need them displayed on
1032
+ // the agent's config option list. The newest version of Commander lets us do exactly this.
1033
+ // This is structured so that if anything like this is discovered again, they can be
1034
+ // added in easily.
1035
+ const hiddenOptions = [].concat(
1036
+ sails
1037
+ )
1038
+
1039
+ hiddenOptions.forEach((option) => {
1040
+ program.addOption(
1041
+ new Option(`--${option.name} ${option.arg}`).hideHelp()
1042
+ )
1043
+ });
1044
+
1011
1045
  function getDefault(optionName) {
1012
1046
  let option;
1013
1047
  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) {
@@ -399,9 +419,12 @@ class ExpressFramework {
399
419
 
400
420
  Whatever the core issue is, it doesn't appear to have any effects
401
421
  elsewhere in any of our Express/Kraken framework support.
422
+
423
+ BODY_PARSED event is emitted to support Sails framework
402
424
  */
403
425
  if (req.body) {
404
426
  decorateRequest({ body: req.body });
427
+ agentEmitter.emit(EVENTS.BODY_PARSED, req, res, req.body);
405
428
  }
406
429
  }
407
430
  });
@@ -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,
@@ -16,6 +16,7 @@ Copyright: 2022 Contrast Security, Inc
16
16
 
17
17
  const Http = require('./http');
18
18
  const Http2 = require('./http2');
19
+ const Spdy = require('./spdy');
19
20
  const Hapi16 = require('./hapi16');
20
21
 
21
22
  module.exports = function(agent) {
@@ -23,5 +24,6 @@ module.exports = function(agent) {
23
24
  new Http(agent);
24
25
  new Http(agent, 'https');
25
26
  new Http2(agent);
27
+ new Spdy(agent);
26
28
  new Hapi16(agent);
27
29
  };
@@ -0,0 +1,87 @@
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 {
18
+ PATCH_TYPES,
19
+ HTTP_RESPONSE_HOOKED_METHOD_KEYS,
20
+ SINK_TYPES
21
+ } = require('../../constants');
22
+ const patcher = require('../patcher');
23
+ const HttpFramework = require('./http');
24
+ const { isString } = require('../../util/is-string');
25
+ const { emitSendEvent } = require('../../hooks/frameworks/common');
26
+ const agentEmitter = require('../../agent-emitter');
27
+
28
+ class SpdyFramework extends HttpFramework {
29
+ /**
30
+ * @param {Agent} agent
31
+ */
32
+ constructor(agent) {
33
+ super(agent, 'spdy');
34
+ }
35
+
36
+ /**
37
+ * @param {import('spdy')} spdy
38
+ * @returns {import('spdy')}
39
+ */
40
+ onRequire(spdy) {
41
+ patcher.patch(spdy, 'createServer', {
42
+ name: `${this.id}.createServer`,
43
+ patchType: PATCH_TYPES.FRAMEWORK,
44
+ alwaysRun: true,
45
+ post: ({ args, result }) => {
46
+ this.handleServerCreate(args, result);
47
+ }
48
+ });
49
+
50
+ patcher.patch(spdy.response, 'push', {
51
+ name: 'spdy.response.push',
52
+ patchType: PATCH_TYPES.FRAMEWORK,
53
+ pre(data) {
54
+ agentEmitter.emit(
55
+ HTTP_RESPONSE_HOOKED_METHOD_KEYS.PUSH,
56
+ data.args[0],
57
+ SINK_TYPES.RESPONSE_BODY
58
+ );
59
+
60
+ const body = data.args[0];
61
+ if (isString(body)) {
62
+ emitSendEvent(body.valueOf());
63
+ }
64
+ }
65
+ });
66
+
67
+ return spdy;
68
+ }
69
+
70
+ /**
71
+ * Emits a create event for the new Server instance.
72
+ * @param {any[]} args The arguments passed to the Server constructor
73
+ * @param {import('net').Server} server The http Server instance
74
+ */
75
+ handleServerCreate(args, server) {
76
+ super.handleServerCreate(args, server);
77
+
78
+ patcher.patch(server, 'listen', {
79
+ name: `${this.id}.SpdyFramework.listen`,
80
+ patchType: PATCH_TYPES.FRAMEWORK,
81
+ alwaysRun: true,
82
+ pre: ({ args, obj }) => this.handleServerListen(args, obj)
83
+ });
84
+ }
85
+ }
86
+
87
+ module.exports = SpdyFramework;
package/lib/hooks/http.js CHANGED
@@ -195,5 +195,16 @@ module.exports = (agent) => {
195
195
  }
196
196
  });
197
197
  });
198
+
199
+ moduleHook.resolve({ name: 'spdy' }, (spdy) => {
200
+ patcher.patch(spdy, 'createServer', {
201
+ name: `create-server-spdy-hooks`,
202
+ patchType: PATCH_TYPES.MISC,
203
+ alwaysRun: true,
204
+ post({ result }) {
205
+ hookServer(result, agent);
206
+ }
207
+ });
208
+ });
198
209
  };
199
210
  module.exports.hookServer = hookServer;
@@ -48,9 +48,9 @@ module.exports = function TraceEvent(event = {}) {
48
48
  16: ret, // 17 ret
49
49
  17: args, // 18 args
50
50
  18: stack, // 19 stack
51
- 19: eventSources, // 10 event_sources
52
- 20: event.source, // 11 source
53
- 21: event.target, // 12 target
54
- 22: taintRanges // 13 taint_ranges
51
+ 19: eventSources, // 20 event_sources
52
+ 20: event.source, // 21 source
53
+ 21: event.target, // 22 target
54
+ 22: taintRanges // 23 taint_ranges
55
55
  });
56
56
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/agent",
3
- "version": "4.8.0",
3
+ "version": "4.9.0",
4
4
  "description": "Node.js security instrumentation by Contrast Security",
5
5
  "keywords": [
6
6
  "security",
@@ -22,6 +22,7 @@
22
22
  "Michael Woytowitz"
23
23
  ],
24
24
  "scripts": {
25
+ "preinstall": "npx npm-force-resolutions",
25
26
  "docs": "jsdoc -c ../.jsdoc.json",
26
27
  "release": "scripts/make-release.js",
27
28
  "tag": "scripts/tag-release.js",
@@ -72,7 +73,7 @@
72
73
  "@babel/types": "^7.12.1",
73
74
  "@contrast/distringuish-prebuilt": "^2.2.0",
74
75
  "@contrast/flat": "^4.1.1",
75
- "@contrast/fn-inspect": "^2.4.2",
76
+ "@contrast/fn-inspect": "^2.4.3",
76
77
  "@contrast/heapdump": "^1.1.0",
77
78
  "@contrast/protobuf-api": "^3.2.0",
78
79
  "@contrast/require-hook": "^2.0.6",
@@ -83,7 +84,7 @@
83
84
  "bluebird": "^3.5.3",
84
85
  "builtin-modules": "^3.2.0",
85
86
  "cls-hooked": "^4.2.2",
86
- "commander": "^5.0.0",
87
+ "commander": "^8.3.0",
87
88
  "content-security-policy-parser": "^0.2.0",
88
89
  "cookie": "^0.3.1",
89
90
  "crc-32": "^1.0.0",
@@ -125,8 +126,8 @@
125
126
  "codecov": "^3.7.0",
126
127
  "config": "^3.3.3",
127
128
  "csv-writer": "^1.2.0",
128
- "deasync": "^0.1.20",
129
- "dustjs-linkedin": "^3.0.0",
129
+ "deasync": "^0.1.24",
130
+ "dustjs-linkedin": "^3.0.1",
130
131
  "ejs": "^3.1.6",
131
132
  "escape-html": "^1.0.3",
132
133
  "eslint": "^8.2.0",
@@ -190,5 +191,9 @@
190
191
  "winston",
191
192
  "winston-syslog",
192
193
  "winston-daily-rotate-file"
193
- ]
194
+ ],
195
+ "resolutions": {
196
+ "markdown-it": ">=12.3.2",
197
+ "marked": ">=4.0.10"
198
+ }
194
199
  }