@contrast/agentify 1.25.1 → 1.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/lib/index.d.ts CHANGED
@@ -76,6 +76,6 @@ export interface Agentify {
76
76
  (preRunMain: PreRunMain, opts?: AgentifyOptions): Installable | void;
77
77
  }
78
78
 
79
- declare function init(core: Partial<Core>): Agentify;
79
+ declare function init(core?: Partial<Core>): Agentify;
80
80
 
81
81
  export = init;
package/lib/index.js CHANGED
@@ -12,14 +12,13 @@
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
-
15
+ /*eslint node/no-unsupported-features/es-syntax: ["error", {version: >=10.0.0}]*/
16
16
  'use strict';
17
17
 
18
18
  const Module = require('module');
19
19
  const {
20
20
  assertValidOpts,
21
21
  preStartupValidation,
22
- postConfigValidation,
23
22
  } = require('./utils');
24
23
  const { IntentionalError } = require('@contrast/common');
25
24
 
@@ -89,7 +88,8 @@ module.exports = function init(core = {}) {
89
88
  return runMain.apply(this, args);
90
89
  };
91
90
  } else {
92
- // for 'esm' we load esm-hooks support, which will install lastly
91
+ // for 'esm' we load esm-hooks support, which installs last
92
+ // eslint-disable-next-line node/no-unsupported-features/es-syntax
93
93
  const { default: esmHooks } = await import('@contrast/esm-hooks/lib/index.mjs');
94
94
  esmHooks(core);
95
95
 
@@ -135,45 +135,84 @@ module.exports = function init(core = {}) {
135
135
  // check supported Node version and correct preload usage before anything else
136
136
  preStartupValidation(core);
137
137
 
138
- require('@contrast/core/lib/messages')(core);
139
- require('@contrast/config')(core);
140
-
141
- require('@contrast/logger').default(core);
142
-
143
- // validate the config now that the logger has been installed
144
- postConfigValidation(core);
138
+ const modules = [
139
+ { name: 'messages', spec: '@contrast/core/lib/messages' },
140
+ { name: 'config', spec: '@contrast/config' },
141
+ { name: 'logger', spec: '@contrast/logger', default: true },
142
+ // call all configValidation functions here
143
+ { name: 'agent-info', spec: '@contrast/core/lib/agent-info' },
144
+ { name: 'system-info', spec: '@contrast/core/lib/system-info' },
145
+ { name: 'app-info', spec: '@contrast/core/lib/app-info' },
146
+ // was check for appInfo errors length and throw if found
147
+ { name: 'sensitive-data-masking', spec: '@contrast/core/lib/sensitive-data-masking' },
148
+ { name: 'is-agent-path', spec: '@contrast/core/lib/is-agent-path' },
149
+ { name: 'dep-hooks', spec: '@contrast/dep-hooks' },
150
+ { name: 'patcher', spec: '@contrast/patcher' },
151
+ { name: 'capture-stacktrace', spec: '@contrast/core/lib/capture-stacktrace' },
152
+ { name: 'rewriter', spec: '@contrast/rewriter' },
153
+ { name: 'contrast-methods', spec: '@contrast/core/lib/contrast-methods' },
154
+ { name: 'scopes', spec: '@contrast/scopes' },
155
+ { name: 'deadzones', spec: '@contrast/deadzones' },
156
+ { name: 'reporter', spec: '@contrast/reporter', default: true },
157
+ { name: 'instrumentation', spec: '@contrast/instrumentation' },
158
+ { name: 'metrics', spec: '@contrast/metrics' },
159
+
160
+ // compose additional local services
161
+ { name: 'heap-snapshots', spec: './heap-snapshots' },
162
+ { name: 'sources', spec: './sources' },
163
+ { name: 'function-hooks', spec: './function-hooks' },
164
+ { name: 'log-diagnostic-files', spec: './log-diagnostic-files' },
165
+ { name: 'rewrite-hooks', spec: './rewrite-hooks' },
166
+ ];
167
+
168
+ // collect validators for each module that has them.
169
+ //
170
+ // the pattern for supplying a validator for a module is to put a validators.js file
171
+ // in the same directory as the module's index.js file. the validators.js file should
172
+ // export an object with a property for each validator it implements.
173
+ //
174
+ // the module-loading for-loop defines the implemented validators.
175
+ // start with agentify, which is a special case because it's not in the list
176
+ // modules to be loaded.
177
+ const validators = { agentify: require('./validators') };
178
+ for (const { name, spec } of modules) {
179
+ try {
180
+ // if any module has files in a directory other than /lib then...
181
+ const v = require(`${spec}/lib/validators.js`);
182
+ validators[name] = v;
183
+ } catch (e) {
184
+ // if e.code !== 'MODULE_NOT_FOUND' consider logging.
185
+ // no validators for this module.
186
+ }
187
+ }
145
188
 
146
- require('@contrast/core/lib/agent-info')(core);
147
- require('@contrast/core/lib/system-info')(core);
148
- require('@contrast/core/lib/app-info')(core);
149
- if (core.appInfo._errors?.length) {
150
- throw core.appInfo._errors[0];
189
+ // now load each module and, if a validator is available, run it.
190
+ for (const { name, spec, default: isDefault } of modules) {
191
+ // typescript inserts a default export for esm modules.
192
+ let mod = require(spec);
193
+ if (isDefault) {
194
+ mod = mod.default;
195
+ }
196
+ mod(core);
197
+
198
+ // perform any validations that can take place now that this module is loaded.
199
+ if (name === 'logger') {
200
+ // note that this runs the "config" validation. it does this so that
201
+ // the logger is available for any validation errors.
202
+ for (const v in validators) {
203
+ validators[v]?.config && validators[v].config(core);
204
+ }
205
+ } // else if (name === 'other-validation-name') {...}
151
206
  }
152
- require('@contrast/core/lib/sensitive-data-masking')(core);
153
- require('@contrast/core/lib/is-agent-path')(core);
154
- require('@contrast/dep-hooks')(core);
155
- require('@contrast/patcher')(core);
156
- require('@contrast/core/lib/capture-stacktrace')(core);
157
- require('@contrast/rewriter')(core); // merge contrast-methods?
158
- require('@contrast/core/lib/contrast-methods')(core); // can we remove dependency on patcher?
159
- require('@contrast/scopes')(core);
160
- require('@contrast/deadzones')(core);
161
- require('@contrast/reporter').default(core);
162
- require('@contrast/instrumentation')(core);
163
- require('@contrast/metrics')(core);
164
-
165
- // compose additional local services
166
- require('./heap-snapshots')(core);
167
- require('./sources')(core);
168
- require('./function-hooks')(core);
169
- require('./log-diagnostic-files')(core); // this doesn't really belong in agentify
170
- require('./rewrite-hooks')(core);
207
+
171
208
  } catch (err) {
172
209
  // TODO: Consider proper UNINSTALLATION and normal startup w/o agent
173
210
  core.agentify = function agentify() {
174
211
  return core;
175
212
  };
176
213
 
214
+ // IntentionalError is used when the agent is disabled by config. We want
215
+ // to abort the installation but not issue any messages.
177
216
  if (!(err instanceof IntentionalError)) {
178
217
  if (core.logger) {
179
218
  core.logger.error({ err }, ERROR_MESSAGE);
@@ -45,7 +45,6 @@ module.exports = function init(core) {
45
45
  isModule: false,
46
46
  inject: true,
47
47
  wrap: true,
48
- trim: false,
49
48
  };
50
49
 
51
50
  if (rewriteIsDeadzoned(filename)) {
@@ -59,7 +58,7 @@ module.exports = function init(core) {
59
58
  const compiled = Reflect.apply(_compile, this, [result.code, filename]);
60
59
 
61
60
  if (core.config.agent.node.rewrite.cache.enable) {
62
- core.rewriter.cache.write(filename, result);
61
+ core.rewriter.cache.writeSync(filename, result);
63
62
  }
64
63
 
65
64
  return compiled;
package/lib/utils.js CHANGED
@@ -18,8 +18,7 @@
18
18
  const path = require('path');
19
19
  const process = require('process');
20
20
  const semver = require('semver');
21
- const { IntentionalError } = require('@contrast/common');
22
- const { findPackageJsonSync } = require('@contrast/find-package-json');
21
+
23
22
  const {
24
23
  engines: {
25
24
  node: nodeEngines,
@@ -89,6 +88,7 @@ function assertSupportedPreloadUsage() {
89
88
  // if absolute path is provided read the package name to check if it's our agent
90
89
  if (path.isAbsolute(preloadTarget)) {
91
90
  try {
91
+ const { findPackageJsonSync } = require('@contrast/find-package-json');
92
92
  const { name } = require(findPackageJsonSync({ cwd: path.dirname(preloadTarget) }));
93
93
  if (name !== '@contrast/agent') continue;
94
94
  preloadTarget = name;
@@ -129,79 +129,6 @@ function isLoader(arg) {
129
129
  return arg == '--import' || arg == '--loader' || arg == '--experimental-loader';
130
130
  }
131
131
 
132
- /**
133
- * Check that the config contains the minimum required settings. Issue appropriate
134
- * error messages.
135
- * @throws IntentionalError
136
- */
137
- function postConfigValidation(core) {
138
- // find out if any required settings are missing.
139
- const missingRequiredSettings = [];
140
- // these are only required if the API is enabled. if api.enable is false, it
141
- // had to be set by the user because it defaults to true.
142
- if (core.config.getEffectiveValue('api.enable')) {
143
- for (const setting of ['api.api_key', 'api.service_key', 'api.user_name']) {
144
- if (!core.config.getEffectiveValue(setting)) {
145
- missingRequiredSettings.push(setting);
146
- }
147
- }
148
- }
149
-
150
- // v4 accepted `-c` or `--configFile` option in argv, but v5 does not. so if
151
- // something that looks like a config flag is present on the command line, we
152
- // log it. the worst case is that it's a false positive; if that's a problem
153
- // we can make it an info level, but the goal here is for the user to see it
154
- // without having to contact customer support and rerun their test at a more
155
- // verbose log level.
156
- const configFlag = getConfigFlag(process.argv);
157
- if (configFlag) {
158
- const msg = `Command line config flag present: ${configFlag[0]} ${configFlag[1]}`;
159
-
160
- if (core.logger) {
161
- core.logger.warn(msg);
162
- } else {
163
- console.warn(msg);
164
- }
165
- }
166
-
167
- if (missingRequiredSettings.length) {
168
- const reason = `Missing required settings: ${missingRequiredSettings.join(', ')}`;
169
- const finalMsg = 'A configuration error prevents the Contrast agent from starting.';
170
- if (core.logger) {
171
- core.logger.error({ reason }, finalMsg);
172
- } else {
173
- console.error(reason, finalMsg);
174
- }
175
- throw new IntentionalError(finalMsg);
176
- }
177
-
178
- // now check for serious errors, like file not readable, file format is wrong, etc.
179
- // that were captured when building the config.
180
- if (core.config._errors?.length) {
181
- throw core.config._errors[0];
182
- }
183
- // finally, was it disabled?
184
- if (!core.config.enable) {
185
- const errorMessage = 'Contrast agent disabled by configuration (enable: false)';
186
- throw new IntentionalError(errorMessage);
187
- }
188
- }
189
-
190
- function getConfigFlag(argv) {
191
- for (let i = 0; i < argv.length - 1; i++) {
192
- if (argv[i] === '-c' || argv[i] === '--configFile') {
193
- // should this check to see if the name looks like a contrast config file?
194
- // let's not because that seems false-negative prone.
195
-
196
- // if the next arg is another flag, then this isn't a contrast config flag
197
- if (!argv[i + 1].startsWith('-') && argv[i + 1].toLowerCase().includes('contrast')) {
198
- return argv.slice(i, i + 2);
199
- }
200
- }
201
- }
202
- return undefined;
203
- }
204
-
205
132
  /**
206
133
  * Validates custom `installOrder` options. We currently need the reporter component
207
134
  * to install first in order to onboard and get effective settings and mode values.
@@ -224,6 +151,4 @@ module.exports = {
224
151
  assertSupportedNodeVersion,
225
152
  assertSupportedPreloadUsage,
226
153
  preStartupValidation,
227
- postConfigValidation,
228
- getConfigFlag,
229
154
  };
@@ -0,0 +1,62 @@
1
+ /*
2
+ * Copyright: 2024 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
+
16
+ 'use strict';
17
+
18
+ const { IntentionalError } = require('@contrast/common');
19
+
20
+ // v4 accepted `-c` or `--configFile` option in argv, but v5 does not. so if
21
+ // something that looks like a config flag is present on the command line, we
22
+ // log it. the worst case is that it's a false positive; if that's a problem
23
+ // we can make it an info level, but the goal here is for the user to see it
24
+ // without having to contact customer support and rerun their test at a more
25
+ // verbose log level.
26
+ function config(core) {
27
+ const configFlag = getConfigFlag(process.argv);
28
+ if (configFlag) {
29
+ const msg = `Command line config flag is not supported: ${configFlag[0]} ${configFlag[1]}`;
30
+
31
+ if (core.logger) {
32
+ core.logger.warn(msg);
33
+ } else {
34
+ console.warn(msg);
35
+ }
36
+ }
37
+
38
+ // finally, was it disabled?
39
+ if (!core.config.enable) {
40
+ const errorMessage = 'Contrast agent disabled by configuration (enable: false)';
41
+ throw new IntentionalError(errorMessage);
42
+ }
43
+ }
44
+
45
+ function getConfigFlag(argv) {
46
+ for (let i = 0; i < argv.length - 1; i++) {
47
+ if (argv[i] === '-c' || argv[i] === '--configFile') {
48
+ // should this check to see if the name looks like a contrast config file?
49
+ // let's not because that seems false-negative prone.
50
+
51
+ // if the next arg is another flag, then this isn't a contrast config flag
52
+ if (!argv[i + 1].startsWith('-') && argv[i + 1].toLowerCase().includes('contrast')) {
53
+ return argv.slice(i, i + 2);
54
+ }
55
+ }
56
+ }
57
+ return undefined;
58
+ }
59
+
60
+ module.exports = {
61
+ config,
62
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/agentify",
3
- "version": "1.25.1",
3
+ "version": "1.27.0",
4
4
  "description": "Configures Contrast agent services and instrumentation within an application",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
@@ -11,25 +11,25 @@
11
11
  "types": "lib/index.d.ts",
12
12
  "engines": {
13
13
  "npm": ">=6.13.7 <7 || >= 8.3.1",
14
- "node": ">= 14.18.0"
14
+ "node": ">= 16.9.1"
15
15
  },
16
16
  "scripts": {
17
17
  "test": "../scripts/test.sh"
18
18
  },
19
19
  "dependencies": {
20
- "@contrast/common": "1.21.0",
21
- "@contrast/config": "1.28.0",
22
- "@contrast/core": "1.32.0",
23
- "@contrast/deadzones": "1.2.0",
24
- "@contrast/dep-hooks": "1.3.2",
25
- "@contrast/esm-hooks": "2.6.0",
20
+ "@contrast/common": "1.21.2",
21
+ "@contrast/config": "1.28.2",
22
+ "@contrast/core": "1.32.2",
23
+ "@contrast/deadzones": "1.2.2",
24
+ "@contrast/dep-hooks": "1.3.3",
25
+ "@contrast/esm-hooks": "2.6.2",
26
26
  "@contrast/find-package-json": "^1.1.0",
27
- "@contrast/instrumentation": "1.8.0",
28
- "@contrast/logger": "1.8.2",
29
- "@contrast/metrics": "1.8.0",
30
- "@contrast/patcher": "1.7.2",
31
- "@contrast/reporter": "1.27.0",
32
- "@contrast/rewriter": "1.8.0",
27
+ "@contrast/instrumentation": "1.10.0",
28
+ "@contrast/logger": "1.8.4",
29
+ "@contrast/metrics": "1.8.2",
30
+ "@contrast/patcher": "1.7.3",
31
+ "@contrast/reporter": "1.27.2",
32
+ "@contrast/rewriter": "1.8.2",
33
33
  "@contrast/scopes": "1.4.1",
34
34
  "semver": "^7.6.0"
35
35
  }