@contrast/agent 4.25.6-beta.0 → 4.27.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/bootstrap.js CHANGED
@@ -41,6 +41,13 @@ Module.runMain = async function (...args) {
41
41
  await loader.bootstrap(process.argv);
42
42
  }
43
43
  await loader.resetArgs(process.argv[0], process.argv[1]);
44
+
45
+ const diagnostics = process.env['CONTRAST__AGENT__DIAGNOSTICS__ENABLE'];
46
+ if (diagnostics === 'true') {
47
+ // eslint-disable-next-line no-process-exit
48
+ process.exit(0);
49
+ }
50
+
44
51
  loader.logTime(startTime, 'agent');
45
52
  const appStartTime = process.hrtime();
46
53
  orig.apply(this, args);
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ Copyright: 2022 Contrast Security, Inc
4
+ Contact: support@contrastsecurity.com
5
+ License: Commercial
6
+
7
+ NOTICE: This Software and the patented inventions embodied within may only be
8
+ used as part of Contrast Security’s commercial offerings. Even though it is
9
+ made available through public repositories, use of this Software is subject to
10
+ the applicable End User Licensing Agreement found at
11
+ https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
12
+ between Contrast Security and the End User. The Software may not be reverse
13
+ engineered, modified, repackaged, sold, redistributed or otherwise used in a
14
+ way not consistent with the End User License Agreement.
15
+ */
16
+ 'use strict';
17
+
18
+ const { exec } = require('child_process');
19
+
20
+ function handleDependentParameter(args, currParameter, nextParameter) {
21
+ switch (currParameter) {
22
+ case '--output':
23
+ case '-o':
24
+ args.output = nextParameter;
25
+ break;
26
+ default:
27
+ throw new Error(`Invalid parameter ${currParameter}`);
28
+ }
29
+ }
30
+
31
+ function handleIndependentParameter(args, parameter) {
32
+ switch (parameter) {
33
+ case '--help':
34
+ args.help = true;
35
+ break;
36
+ case '--quiet':
37
+ case '-q':
38
+ args.quiet = true;
39
+ break;
40
+ default:
41
+ throw new Error(`Invalid parameter ${parameter}`);
42
+ }
43
+ }
44
+
45
+ function printHelpMessage() {
46
+ console.log(
47
+ '\nDescription:\n' +
48
+ 'The config-diagnostics utility returns the current effective node agent configuration\n' +
49
+ '\nUsage:\n' +
50
+ 'npx -p @contrast/agent config-diagnostics <path/to/entry/script> [options]\n' +
51
+ 'npx -p @contrast/agent config-diagnostics --help\n' +
52
+ '\nOptions:\n' +
53
+ '--quiet -q Prevents output of the report to the console.\n' +
54
+ '--output -o The directory to write the report in. Defaults to the current directory.\n'
55
+ );
56
+ }
57
+
58
+ function parseArgv() {
59
+ const argv = process.argv.slice(2);
60
+ const args = {
61
+ output: 'contrast_effective_config.json',
62
+ quiet: false,
63
+ help: false,
64
+ };
65
+ let currParameter = null;
66
+
67
+ for (let i = 0; i < argv.length; i++) {
68
+ if (!currParameter) {
69
+ currParameter = argv[i];
70
+ }
71
+ if (!(currParameter.startsWith('--') || currParameter.startsWith('-'))) {
72
+ currParameter = null;
73
+ continue;
74
+ }
75
+
76
+ if (i + 1 == argv.length || argv[i + 1].startsWith('--') || argv[i + 1].startsWith('-')) {
77
+ handleIndependentParameter(args, currParameter);
78
+ currParameter = null;
79
+ } else {
80
+ handleDependentParameter(args, currParameter, argv[i + 1]);
81
+ currParameter = null;
82
+ }
83
+ }
84
+
85
+ return args;
86
+ }
87
+
88
+ function executeNodeAgent(args) {
89
+ let agentEnvArgs = 'CONTRAST__SHOW__BANNER=false CONTRAST__AGENT__DIAGNOSTICS__ENABLE=true';
90
+ const entry = process.argv.slice(2).shift();
91
+
92
+ if (!args.quiet) {
93
+ agentEnvArgs = agentEnvArgs.concat(' CONTRAST__AGENT__DIAGNOSTICS__QUIET=false');
94
+ }
95
+ if (args.output) {
96
+ agentEnvArgs = agentEnvArgs.concat(` CONTRAST__AGENT__DIAGNOSTICS__REPORT_PATH=${args.output}`);
97
+ }
98
+
99
+ exec(`${agentEnvArgs} node -r ./node_modules/@contrast/agent/bootstrap.js ${entry}`,
100
+ (error, stdout, stderr) => {
101
+ if (error && !args.quiet) {
102
+ console.log(`error: ${error.message}`);
103
+ return;
104
+ }
105
+
106
+ // by default errors still won't be visible since stdout == sterr
107
+ if (stderr) {
108
+ console.log(`stderr: ${stderr}`);
109
+ return;
110
+ }
111
+
112
+ if (!args.quiet) {
113
+ console.log(stdout);
114
+ }
115
+ }
116
+ );
117
+ }
118
+
119
+ function fetchAgentConfig(args) {
120
+ if (process.argv.slice(2).length == 1 && args.help) {
121
+ printHelpMessage();
122
+ } else {
123
+ executeNodeAgent(args);
124
+ }
125
+ }
126
+
127
+
128
+ fetchAgentConfig(parseArgv());
129
+ // exporting this stuff for testing purposes only
130
+ module.exports = { printHelpMessage, parseArgv, executeNodeAgent, fetchAgentConfig };
@@ -21,6 +21,7 @@ const stackFactory = require('../../core/stacktrace').singleton;
21
21
  const { PROXY_TARGET } = require('../../../lib/constants');
22
22
  const TagRange = require('../models/tag-range');
23
23
  const distringuish = require('@contrast/distringuish');
24
+ const agent = require('../../agent');
24
25
 
25
26
  /**
26
27
  * Holds information about the call context of a function
@@ -153,13 +154,36 @@ function valueString(value) {
153
154
  return type;
154
155
  }
155
156
 
156
- function create(params) {
157
+ const appendStack = agent.config.assess.enable_lazy_stacktraces ?
158
+ (instance, snapshot) => {
159
+ let stack;
160
+ // This enables the lazy generation of a stacktrace which is a performance
161
+ // improvement, but the snapshot method call is in a closure which creates
162
+ // a memory leak in longer running tests
163
+ Object.defineProperty(instance, 'stack', {
164
+ enumerable: true,
165
+ configurable: true,
166
+ get() {
167
+ if (!stack) {
168
+ stack = snapshot();
169
+ }
170
+ return stack;
171
+ },
172
+ set(value) {
173
+ stack = value;
174
+ }
175
+ });
176
+ } : (instance, snapshot) => {
177
+ instance.stack = snapshot();
178
+ };
179
+
180
+ function create (params) {
157
181
  const instance = new CallContext(params);
158
182
  const snapshot = params.stacktrace || stackFactory.createSnapshot({
159
183
  constructorOpt: params.hooked
160
184
  });
161
185
 
162
- instance.stack = typeof snapshot === 'function' ? snapshot() : snapshot;
186
+ appendStack(instance, snapshot);
163
187
 
164
188
  return instance;
165
189
  }
@@ -12,6 +12,8 @@ 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
+
15
17
  const CallContext = require('./call-context');
16
18
  const Signature = require('./signature');
17
19
  const BaseEvent = require('./base-event');
package/lib/contrast.js CHANGED
@@ -14,7 +14,7 @@ Copyright: 2022 Contrast Security, Inc
14
14
  */
15
15
  'use strict';
16
16
 
17
- const { program } = require('./core/config/options');
17
+ const { program, options } = require('./core/config/options');
18
18
  const path = require('path');
19
19
  const os = require('os');
20
20
  const semver = require('semver');
@@ -25,6 +25,7 @@ const Module = require('module');
25
25
  const sourceMapUtility = require('./util/source-map');
26
26
  const loggerFactory = require('./core/logger');
27
27
  const logger = loggerFactory('contrast:contrast-init');
28
+ const { outputAgentConfigFile } = require('./util/config-diagnostics-utils');
28
29
 
29
30
  function getAgentSnippet() {
30
31
  // getting reference to name every time for testing purposes
@@ -59,9 +60,12 @@ contrastAgent.showBanner = function showBanner() {
59
60
  console.log(colors.red(cat));
60
61
  }
61
62
 
62
- logger.console();
63
- logger.console('%s %s', NAME, VERSION);
64
- logger.console(colors.green('--------------------------------------'));
63
+ const showAgentBanner = (process.env['CONTRAST__SHOW__BANNER'] === 'false') ? false : true;
64
+ if (showAgentBanner) {
65
+ logger.console();
66
+ logger.console('%s %s', NAME, VERSION);
67
+ logger.console(colors.green('--------------------------------------'));
68
+ }
65
69
  };
66
70
 
67
71
  /**
@@ -284,18 +288,16 @@ contrastAgent.prepare = function(...args) {
284
288
 
285
289
  /**
286
290
  * Sends startup message to TeamServer then instruments user code
287
- *
288
291
  * @param {Array} args process.argv
289
292
  */
290
293
  contrastAgent.bootstrap = function(args) {
291
294
  const reporter = new TSReporter(agent);
295
+
292
296
  // returning promise for testing purposes only
293
297
  return reporter
294
298
  .onboard(args)
295
299
  .catch((err) => {
296
- logger.error(
297
- 'Reporter onboarding failed. Continuing without instrumentation.'
298
- );
300
+ logger.error('Reporter onboarding failed. Continuing without instrumentation.');
299
301
  logger.error(err);
300
302
  agent.clearIntervals();
301
303
  return;
@@ -314,6 +316,25 @@ contrastAgent.bootstrap = function(args) {
314
316
  'Unexpected error while trying to start contrast. Continuing without instrumentation. Error: %o',
315
317
  err
316
318
  );
319
+ })
320
+ .finally(async () => {
321
+ let destination = process.env['CONTRAST__AGENT__DIAGNOSTICS__REPORT_PATH'];
322
+ if (destination == null) {
323
+ destination = agent.config._flat['agent.logger.path'].split('/');
324
+ destination.pop() && destination.push('contrast_effective_config.json');
325
+ destination = destination.join('/');
326
+ }
327
+
328
+ const args = {
329
+ quiet: (process.env['CONTRAST__AGENT__DIAGNOSTICS__QUIET'] === 'false') ? false : true,
330
+ output: destination,
331
+ };
332
+
333
+ try {
334
+ outputAgentConfigFile(agent, options, args, null);
335
+ } catch (err) {
336
+ outputAgentConfigFile(agent, options, args, err);
337
+ }
317
338
  });
318
339
  };
319
340
 
@@ -761,6 +761,13 @@ const assess = [
761
761
  fn: castBoolean,
762
762
  desc: 'When set to `false` won\'t track the source events lazily but will track the first up to 250 source events',
763
763
  },
764
+ {
765
+ name: 'assess.enable_lazy_stacktraces',
766
+ arg: '[false]',
767
+ default: false,
768
+ fn: castBoolean,
769
+ desc: 'When set to `true` it will evaluate stacktraces lazily, this improves performance, but causes a memory leak if used in a longer running application',
770
+ },
764
771
  ];
765
772
 
766
773
  const protect = [
@@ -0,0 +1,221 @@
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 path = require('path');
18
+ const fs = require('fs');
19
+
20
+ // eslint-disable-next-line complexity
21
+ function getLoggerValues(option, config, tsData) {
22
+ let tsValue, effectiveValue = config._flat[option.name];
23
+
24
+ switch (option.name) {
25
+ case 'agent.logger.level':
26
+ tsValue = tsData.logLevel;
27
+ break;
28
+ case 'agent.logger.path':
29
+ tsValue = tsData.logFile;
30
+ break;
31
+ case 'agent.security_logger.syslog.enable':
32
+ tsValue = tsData.defend.syslog.enabled;
33
+ break;
34
+ case 'agent.security_logger.syslog.ip':
35
+ tsValue = tsData.defend.syslog.ipAddress;
36
+ break;
37
+ case 'agent.security_logger.syslog.port':
38
+ tsValue = tsData.defend.syslog.port;
39
+ break;
40
+ case 'agent.security_logger.syslog.fascility':
41
+ tsValue = tsData.defend.syslog.fascilityCode;
42
+ break;
43
+ case 'agent.security_logger.syslog.severity_exploited':
44
+ tsValue = tsData.defend.syslog.severityExploited;
45
+ break;
46
+ case 'agent.security_logger.syslog.severity_blocked':
47
+ tsValue = tsData.defend.syslog.severityBlocked;
48
+ break;
49
+ case 'agent.security_logger.syslog.severity_probed':
50
+ tsValue = tsData.defend.syslog.severityProbed;
51
+ break;
52
+ default:
53
+ break;
54
+ }
55
+
56
+ if (config._sources[option.name] == 'DEFAULT' && tsValue != null) {
57
+ effectiveValue = tsValue;
58
+ config._sources[option.name] = 'ContrastUI';
59
+ }
60
+
61
+ return {
62
+ CanonicalName: option.name,
63
+ Name: option.name,
64
+ Value: effectiveValue,
65
+ Source: config._sources[option.name],
66
+ };
67
+ }
68
+
69
+ function getAssessValues(option, config, tsData) {
70
+ let tsValue, effectiveValue = config._flat[option.name];
71
+
72
+ switch (option.name) {
73
+ case 'assess.enable':
74
+ tsValue = tsData.assess.enabled;
75
+ break;
76
+ case 'assess.enable_propagators':
77
+ tsValue = tsData.assess.propagators;
78
+ break;
79
+ case 'assess.sampling.enable':
80
+ tsValue = tsData.assess.sampling;
81
+ break;
82
+ default:
83
+ break;
84
+ }
85
+
86
+ if (config._sources[option.name] == 'DEFAULT' && tsValue != null) {
87
+ effectiveValue = tsValue;
88
+ config._sources[option.name] = 'ContrastUI';
89
+ }
90
+
91
+ return {
92
+ CanonicalName: option.name,
93
+ Name: option.name,
94
+ Value: effectiveValue,
95
+ Source: config._sources[option.name],
96
+ };
97
+ }
98
+
99
+ function getProtectValues(option, config, tsData) {
100
+ let value = {};
101
+
102
+ if (option.name.includes('ip-denylist')) {
103
+ value = {
104
+ CanonicalName: option.name.split('.')[2],
105
+ Name: option.name,
106
+ Value: tsData.ipDenylistsList,
107
+ Source: 'ContrastUI',
108
+ };
109
+ } else if (option.name.includes('virtual-patch')) {
110
+ value = {
111
+ CanonicalName: option.name.split('.')[2],
112
+ Name: option.name,
113
+ Value: tsData.virtualPatchesList,
114
+ Source: 'ContrastUI',
115
+ };
116
+ } else if (option.name.includes('protect.rules') && option.name.includes('mode')) {
117
+ const apiRule = tsData.protectionRulesList.find(r => r.id == option.name.split('.')[2]);
118
+
119
+ const apiMode = apiRule ? apiRule.mode : undefined;
120
+ const ymlMode = config._flat[option.name];
121
+ let effectiveMode;
122
+
123
+ if (!ymlMode && apiMode) {
124
+ config._sources[option.name] = 'ContrastUI';
125
+ effectiveMode = apiMode;
126
+ } else if (ymlMode) {
127
+ effectiveMode = ymlMode;
128
+ }
129
+
130
+ value = {
131
+ CanonicalName: option.name.split('.')[2],
132
+ Name: option.name,
133
+ Value: effectiveMode,
134
+ Source: config._sources[option.name],
135
+ };
136
+ }
137
+
138
+ return value;
139
+ }
140
+
141
+ function getEffectiveConfigValues(agent, options, err) {
142
+ const tsData = { ...agent.tsFeatureSet.serverFeatures, ...agent.tsFeatureSet.applicationSettings };
143
+ const values = [];
144
+
145
+ for (const option of options) {
146
+ if (!err && option.name.includes('protect.rules') && option.name.includes('mode')) {
147
+ // get all protect rules
148
+ values.push(getProtectValues(option, agent.config, tsData));
149
+ } else if (!err && option.name.includes('logger')) {
150
+ // fetch agent/service/sys logger settings
151
+ values.push(getLoggerValues(option, agent.config, tsData));
152
+ } else if (!err && option.name.includes('assess')) {
153
+ // fetch assess settings
154
+ values.push(getAssessValues(option, agent.config, tsData));
155
+ } else if (agent.config._flat[option.name] != null) {
156
+ // fetch everything else
157
+ values.push({
158
+ CanonicalName: option.name,
159
+ Name: option.name,
160
+ Value: agent.config._flat[option.name],
161
+ Source: agent.config._sources[option.name],
162
+ });
163
+ }
164
+ }
165
+ return values;
166
+ }
167
+
168
+ function getEffectiveConfig(agent, options, err) {
169
+ const effectiveConfigValues = getEffectiveConfigValues(agent, options, err);
170
+ const envValues = effectiveConfigValues.filter(v => v.Source == 'ENV');
171
+ const cliValues = effectiveConfigValues.filter(v => v.Source == 'CLI');
172
+ const ymlValues = effectiveConfigValues.filter(v => v.Source == 'YAML');
173
+ const apiValues = effectiveConfigValues.filter(v => v.Source == 'ContrastUI');
174
+
175
+ const effectiveConfig = {
176
+ ReportCreate: new Date(Date.now()),
177
+ Config: {
178
+ EffectiveConfig: {
179
+ Status: undefined,
180
+ Values: effectiveConfigValues
181
+ },
182
+ Environment: envValues,
183
+ CommandLine: cliValues,
184
+ ContrastUI: apiValues,
185
+ File: ymlValues,
186
+ }
187
+ };
188
+
189
+ return effectiveConfig;
190
+ }
191
+
192
+ function outputAgentConfigFile(agent, options, args, err) {
193
+ const effectiveConfig = getEffectiveConfig(agent, options, err);
194
+
195
+ if (err) {
196
+ effectiveConfig.Config.Status = 'Failure! ' +
197
+ `Could not connect to TeamServer, so no TeamServer data will be included. Error: ${err}`;
198
+ } else {
199
+ effectiveConfig.Config.Status = 'Success';
200
+ }
201
+
202
+ try {
203
+ fs.accessSync(path.join(args.output, '..'), fs.constants.RDWD_OK);
204
+ fs.writeFileSync(args.output, JSON.stringify(effectiveConfig, null, 2), 'utf-8');
205
+ } catch (err) {
206
+ console.log(`Couldn't create effective config file: ${err}`);
207
+ }
208
+
209
+ if (!args.quiet) {
210
+ console.log(JSON.stringify(effectiveConfig, null, 2));
211
+ }
212
+ }
213
+
214
+ module.exports = {
215
+ getEffectiveConfigValues,
216
+ getEffectiveConfig,
217
+ outputAgentConfigFile,
218
+ getProtectValues,
219
+ getAssessValues,
220
+ getLoggerValues
221
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/agent",
3
- "version": "4.25.6-beta.0",
3
+ "version": "4.27.0",
4
4
  "description": "Node.js security instrumentation by Contrast Security",
5
5
  "keywords": [
6
6
  "security",
@@ -54,7 +54,8 @@
54
54
  "bin": {
55
55
  "node-contrast": "cli.js",
56
56
  "perf-logs": "perf-logs.js",
57
- "contrast-transpile": "cli-rewriter.js"
57
+ "contrast-transpile": "cli-rewriter.js",
58
+ "read-contrast-config": "config-diagnostics.js"
58
59
  },
59
60
  "files": [
60
61
  "bin/**",
@@ -64,7 +65,8 @@
64
65
  "cli.js",
65
66
  "esm.mjs",
66
67
  "perf-logs.js",
67
- "cli-rewriter.js"
68
+ "cli-rewriter.js",
69
+ "config-diagnostics.js"
68
70
  ],
69
71
  "repository": {
70
72
  "type": "git"
@@ -138,7 +140,7 @@
138
140
  "dustjs-linkedin": "^3.0.1",
139
141
  "ejs": "^3.1.7",
140
142
  "escape-html": "^1.0.3",
141
- "eslint": "^8.9.0",
143
+ "eslint": "^8.26.0",
142
144
  "eslint-plugin-mocha": "^10.0.3",
143
145
  "eslint-plugin-node": "^11.1.0",
144
146
  "express": "file:test/mock/express",
@@ -158,7 +160,7 @@
158
160
  "lint-staged": "^12.0.2",
159
161
  "madge": "^4.0.1",
160
162
  "marsdb": "file:test/mock/marsdb",
161
- "mocha": "^9.2.0",
163
+ "mocha": "^9.2.2",
162
164
  "mochawesome": "^7.0.1",
163
165
  "mock-fs": "^5.1.2",
164
166
  "mongodb": "file:test/mock/mongodb",