@contrast/agentify 1.20.0 → 1.21.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.js +83 -61
- package/lib/rewrite-hooks.js +0 -2
- package/package.json +9 -9
- package/lib/initialize.mjs +0 -156
package/lib/index.js
CHANGED
|
@@ -19,6 +19,14 @@ const Module = require('module');
|
|
|
19
19
|
const { IntentionalError } = require('@contrast/common');
|
|
20
20
|
|
|
21
21
|
const ERROR_MESSAGE = 'An error prevented the Contrast agent from installing. The application will be run without instrumentation.';
|
|
22
|
+
/**
|
|
23
|
+
* Specific agents may want to add their startup validation to this list as needed.
|
|
24
|
+
* We have to install the reporter first since installation is contingent on TS onboarding responses.
|
|
25
|
+
* Otherwise, the order of installing side effects doesn't matter too much. Ideally, the installations
|
|
26
|
+
* that most affect the environment and "normal" execution of things should be done lastly e.g. modifying prototypes.
|
|
27
|
+
* A good rule might be to install in the order such that side-effects that are easier to undo should be done first.
|
|
28
|
+
* With that rule in mind, ESM support is the most complex (loader agent) so it should come last.
|
|
29
|
+
*/
|
|
22
30
|
const DEFAULT_INSTALL_ORDER = [
|
|
23
31
|
'reporter',
|
|
24
32
|
'contrastMethods',
|
|
@@ -32,16 +40,87 @@ const DEFAULT_INSTALL_ORDER = [
|
|
|
32
40
|
'routeCoverage',
|
|
33
41
|
'libraryAnalysis',
|
|
34
42
|
'heapSnapshots',
|
|
43
|
+
'metrics',
|
|
35
44
|
'rewriteHooks',
|
|
36
45
|
'functionHooks',
|
|
37
|
-
'
|
|
46
|
+
'esmHooks',
|
|
38
47
|
];
|
|
39
48
|
|
|
40
49
|
/**
|
|
50
|
+
* Utility for making agents: entrypoints indended to be run via --require, --loader, --import.
|
|
51
|
+
* Composes all needed dependencies during factory instantiation, and installs various components
|
|
52
|
+
* that alter an application's environment in order for composed features to operate e.g. request scopes,
|
|
53
|
+
* source code rewrite hooks etc.
|
|
41
54
|
* @param {any} core
|
|
42
55
|
* @returns {import('.').Agentify}
|
|
43
56
|
*/
|
|
44
57
|
module.exports = function init(core = {}) {
|
|
58
|
+
let _callback;
|
|
59
|
+
let _opts;
|
|
60
|
+
|
|
61
|
+
core.agentify = async function agentify(callback, opts = {}) {
|
|
62
|
+
_callback = callback;
|
|
63
|
+
_opts = opts;
|
|
64
|
+
_opts.type = _opts.type ?? 'cjs';
|
|
65
|
+
_opts.installOrder = _opts.installOrder ?? DEFAULT_INSTALL_ORDER;
|
|
66
|
+
|
|
67
|
+
// for 'cjs' we hook runMain and install prior to running it
|
|
68
|
+
if (_opts.type === 'cjs') {
|
|
69
|
+
if (typeof callback !== 'function') {
|
|
70
|
+
throw new Error('Invalid usage: first argument must be a function');
|
|
71
|
+
}
|
|
72
|
+
const { runMain } = Module;
|
|
73
|
+
/**
|
|
74
|
+
* This is one side effect that will always occur, even with `installOrder: []`.
|
|
75
|
+
* The act of calling `agentify()` is enough to cause this side effect, by design.
|
|
76
|
+
*/
|
|
77
|
+
Module.runMain = async function (...args) {
|
|
78
|
+
await install();
|
|
79
|
+
return runMain.apply(this, args);
|
|
80
|
+
};
|
|
81
|
+
} else {
|
|
82
|
+
// for 'esm' we load esm-hooks support, which will install lastly
|
|
83
|
+
const { default: esmHooks } = await import('@contrast/esm-hooks/lib/index.mjs');
|
|
84
|
+
esmHooks(core);
|
|
85
|
+
|
|
86
|
+
await install();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return core;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
async function install() {
|
|
93
|
+
const { config, logger } = core;
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
for (const err of config._errors) {
|
|
97
|
+
throw err; // move this into config itself since we now handle errors
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
logger.info('Starting %s v%s', core.agentName, core.agentVersion);
|
|
101
|
+
logger.info({ config }, 'Agent configuration');
|
|
102
|
+
|
|
103
|
+
const plugin = await _callback?.(core);
|
|
104
|
+
|
|
105
|
+
for (const svcName of _opts.installOrder ?? []) {
|
|
106
|
+
const svc = core[svcName];
|
|
107
|
+
if (svc?.install) {
|
|
108
|
+
logger.trace('installing service: %s', svcName);
|
|
109
|
+
await svc.install();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (plugin?.install) {
|
|
114
|
+
await plugin.install();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
core.logDiagnosticFiles(); // should this be moved into a separate install side-effect?
|
|
118
|
+
} catch (err) {
|
|
119
|
+
// TODO: Consider proper UNINSTALLATION and normal startup w/o agent
|
|
120
|
+
logger.error({ err }, ERROR_MESSAGE);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
45
124
|
try {
|
|
46
125
|
require('@contrast/core/lib/messages')(core);
|
|
47
126
|
require('@contrast/config')(core);
|
|
@@ -49,8 +128,6 @@ module.exports = function init(core = {}) {
|
|
|
49
128
|
throw core.config._errors[0];
|
|
50
129
|
}
|
|
51
130
|
require('@contrast/logger').default(core);
|
|
52
|
-
|
|
53
|
-
// @contrast/info ?
|
|
54
131
|
require('@contrast/core/lib/agent-info')(core);
|
|
55
132
|
require('@contrast/core/lib/system-info')(core);
|
|
56
133
|
require('@contrast/core/lib/app-info')(core);
|
|
@@ -62,77 +139,20 @@ module.exports = function init(core = {}) {
|
|
|
62
139
|
require('@contrast/dep-hooks')(core);
|
|
63
140
|
require('@contrast/patcher')(core);
|
|
64
141
|
require('@contrast/core/lib/capture-stacktrace')(core);
|
|
65
|
-
|
|
66
142
|
require('@contrast/rewriter')(core); // merge contrast-methods?
|
|
67
143
|
require('@contrast/core/lib/contrast-methods')(core); // can we remove dependency on patcher?
|
|
68
|
-
|
|
69
144
|
require('@contrast/scopes')(core);
|
|
70
145
|
require('@contrast/deadzones')(core);
|
|
71
146
|
require('@contrast/reporter').default(core);
|
|
72
147
|
require('@contrast/instrumentation')(core);
|
|
73
148
|
require('@contrast/metrics')(core);
|
|
74
149
|
|
|
75
|
-
// compose
|
|
150
|
+
// compose additional local services
|
|
76
151
|
require('./heap-snapshots')(core);
|
|
77
152
|
require('./sources')(core);
|
|
78
153
|
require('./function-hooks')(core);
|
|
79
154
|
require('./log-diagnostic-files')(core); // this doesn't really belong in agentify
|
|
80
155
|
require('./rewrite-hooks')(core);
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* The interface is a function, which when called, will hook runMain
|
|
84
|
-
* @type {import('.').Agentify}
|
|
85
|
-
*/
|
|
86
|
-
core.agentify = function agentify(
|
|
87
|
-
preRunMain,
|
|
88
|
-
{ installOrder = DEFAULT_INSTALL_ORDER } = {},
|
|
89
|
-
) {
|
|
90
|
-
if (typeof preRunMain !== 'function') {
|
|
91
|
-
throw new Error('Invalid usage: first argument must be a function');
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const { runMain } = Module;
|
|
95
|
-
const { config, logger } = core;
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* This is one side effect that will always occur, even with `installOrder: []`.
|
|
99
|
-
* The act of calling `agentify()` is enough to cause this side effect, by design.
|
|
100
|
-
*/
|
|
101
|
-
Module.runMain = async function (...args) {
|
|
102
|
-
try {
|
|
103
|
-
for (const err of config._errors) {
|
|
104
|
-
throw err; // move this into config itself since we now handle errors
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
logger.info('Starting %s v%s', core.agentName, core.agentVersion);
|
|
108
|
-
// pino serializers know how to log redacted values and omit certain props
|
|
109
|
-
logger.info({ config }, 'Agent configuration');
|
|
110
|
-
|
|
111
|
-
const plugin = await preRunMain(core);
|
|
112
|
-
|
|
113
|
-
for (const svcName of installOrder ?? []) {
|
|
114
|
-
const svc = core[svcName];
|
|
115
|
-
if (svc?.install) {
|
|
116
|
-
logger.trace('installing service: %s', svcName);
|
|
117
|
-
await svc.install();
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (plugin?.install) {
|
|
122
|
-
await plugin.install();
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
core.logDiagnosticFiles(); // should this be moved into a separate install side-effect?
|
|
126
|
-
} catch (err) {
|
|
127
|
-
// TODO: Consider proper UNINSTALLATION and normal startup w/o agent
|
|
128
|
-
logger.error({ err }, ERROR_MESSAGE);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return runMain.apply(this, args);
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
return core;
|
|
135
|
-
};
|
|
136
156
|
} catch (err) {
|
|
137
157
|
// TODO: Consider proper UNINSTALLATION and normal startup w/o agent
|
|
138
158
|
core.agentify = function agentify() {
|
|
@@ -151,3 +171,5 @@ module.exports = function init(core = {}) {
|
|
|
151
171
|
|
|
152
172
|
return core.agentify;
|
|
153
173
|
};
|
|
174
|
+
|
|
175
|
+
module.exports.DEFAULT_INSTALL_ORDER = DEFAULT_INSTALL_ORDER;
|
package/lib/rewrite-hooks.js
CHANGED
|
@@ -44,8 +44,6 @@ module.exports = function init(core) {
|
|
|
44
44
|
inject: true,
|
|
45
45
|
wrap: true,
|
|
46
46
|
};
|
|
47
|
-
// if threadInfo is present, this is running with --loader or --import
|
|
48
|
-
core.threadInfo?.post('rewrite', options);
|
|
49
47
|
|
|
50
48
|
const { code } = core.rewriter.rewrite(content, options);
|
|
51
49
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/agentify",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.21.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)",
|
|
@@ -17,17 +17,17 @@
|
|
|
17
17
|
"test": "../scripts/test.sh"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@contrast/common": "1.
|
|
21
|
-
"@contrast/config": "1.
|
|
22
|
-
"@contrast/core": "1.
|
|
20
|
+
"@contrast/common": "1.19.0",
|
|
21
|
+
"@contrast/config": "1.26.0",
|
|
22
|
+
"@contrast/core": "1.30.0",
|
|
23
23
|
"@contrast/deadzones": "1.1.2",
|
|
24
24
|
"@contrast/dep-hooks": "1.3.1",
|
|
25
|
-
"@contrast/esm-hooks": "2.
|
|
26
|
-
"@contrast/instrumentation": "1.
|
|
27
|
-
"@contrast/logger": "1.
|
|
28
|
-
"@contrast/metrics": "1.
|
|
25
|
+
"@contrast/esm-hooks": "2.3.0",
|
|
26
|
+
"@contrast/instrumentation": "1.6.0",
|
|
27
|
+
"@contrast/logger": "1.8.0",
|
|
28
|
+
"@contrast/metrics": "1.5.0",
|
|
29
29
|
"@contrast/patcher": "1.7.1",
|
|
30
|
-
"@contrast/reporter": "1.
|
|
30
|
+
"@contrast/reporter": "1.25.0",
|
|
31
31
|
"@contrast/rewriter": "1.4.2",
|
|
32
32
|
"@contrast/scopes": "1.4.0"
|
|
33
33
|
}
|
package/lib/initialize.mjs
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
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
|
-
import Module from 'node:module';
|
|
17
|
-
// want to only initialize some of the agent if main thread. not sure that's really
|
|
18
|
-
// possible. based on logging, it looks like all of the low-level hooks (rewrite-injection,
|
|
19
|
-
// assess-dataflow-propagators, assess-dataflow-sink ContrastMethods, and function-hooks)
|
|
20
|
-
// are added in both the main thread and the loader thread. But all other modules are loaded
|
|
21
|
-
// in the main thread. presumably, this is because the assess module was loaded in both
|
|
22
|
-
// threads, registered the appropriate patchers in each, so both convert 'import' (statement
|
|
23
|
-
// or function) to 'require'.
|
|
24
|
-
//
|
|
25
|
-
import { isMainThread, threadId } from 'node:worker_threads';
|
|
26
|
-
|
|
27
|
-
const ERROR_MESSAGE = 'An error prevented the Contrast agent from installing. The application will be run without instrumentation.';
|
|
28
|
-
const DEFAULT_INSTALL_ORDER = [
|
|
29
|
-
'reporter',
|
|
30
|
-
'contrastMethods',
|
|
31
|
-
'deadzones',
|
|
32
|
-
'scopes',
|
|
33
|
-
'sources',
|
|
34
|
-
'architectureComponents',
|
|
35
|
-
'assess',
|
|
36
|
-
'protect',
|
|
37
|
-
'depHooks',
|
|
38
|
-
'esmHooks',
|
|
39
|
-
'routeCoverage',
|
|
40
|
-
'libraryAnalysis',
|
|
41
|
-
'heapSnapshots',
|
|
42
|
-
'rewriteHooks',
|
|
43
|
-
'functionHooks',
|
|
44
|
-
'metrics',
|
|
45
|
-
];
|
|
46
|
-
|
|
47
|
-
const require = Module.createRequire(import.meta.url);
|
|
48
|
-
let startupError;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* @param {object} { core = {}, options = {} }
|
|
52
|
-
*/
|
|
53
|
-
async function loadModules({ core = {}, options = {} }) {
|
|
54
|
-
try {
|
|
55
|
-
require('@contrast/core/lib/messages')(core);
|
|
56
|
-
require('@contrast/config')(core);
|
|
57
|
-
if (core.config._errors?.length) {
|
|
58
|
-
throw core.config._errors[0];
|
|
59
|
-
}
|
|
60
|
-
require('@contrast/logger').default(core);
|
|
61
|
-
|
|
62
|
-
const thread = isMainThread ? 'main' : 'loader';
|
|
63
|
-
core.logger.trace({ tid: threadId }, 'initializing core modules in %s thread', thread);
|
|
64
|
-
|
|
65
|
-
if (isMainThread) {
|
|
66
|
-
// @contrast/info ?
|
|
67
|
-
require('@contrast/core/lib/agent-info')(core);
|
|
68
|
-
require('@contrast/core/lib/system-info')(core);
|
|
69
|
-
require('@contrast/core/lib/app-info')(core);
|
|
70
|
-
if (core.appInfo._errors?.length) {
|
|
71
|
-
throw core.appInfo._errors[0];
|
|
72
|
-
}
|
|
73
|
-
require('@contrast/core/lib/sensitive-data-masking')(core);
|
|
74
|
-
require('@contrast/core/lib/is-agent-path')(core);
|
|
75
|
-
require('@contrast/dep-hooks')(core);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const { default: install } = await import('@contrast/esm-hooks');
|
|
79
|
-
const esmHooks = await install(core);
|
|
80
|
-
core.esmHooks = esmHooks;
|
|
81
|
-
|
|
82
|
-
if (isMainThread) {
|
|
83
|
-
require('@contrast/patcher')(core);
|
|
84
|
-
require('@contrast/core/lib/capture-stacktrace')(core);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
require('@contrast/rewriter')(core); // merge contrast-methods?
|
|
88
|
-
|
|
89
|
-
if (isMainThread) {
|
|
90
|
-
require('@contrast/core/lib/contrast-methods')(core); // can we remove dependency on patcher?
|
|
91
|
-
|
|
92
|
-
require('@contrast/scopes')(core);
|
|
93
|
-
require('@contrast/deadzones')(core);
|
|
94
|
-
require('@contrast/reporter').default(core);
|
|
95
|
-
require('@contrast/instrumentation')(core);
|
|
96
|
-
require('@contrast/metrics')(core);
|
|
97
|
-
|
|
98
|
-
require('./heap-snapshots')(core);
|
|
99
|
-
require('./sources')(core);
|
|
100
|
-
require('./function-hooks')(core);
|
|
101
|
-
require('./log-diagnostic-files')(core); // this doesn't really belong in agentify
|
|
102
|
-
require('./rewrite-hooks')(core);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
} catch (err) {
|
|
106
|
-
// TODO: Consider proper UNINSTALLATION and normal startup w/o agent
|
|
107
|
-
if (core.logger) {
|
|
108
|
-
core.logger.error({ err, threadId }, ERROR_MESSAGE);
|
|
109
|
-
} else {
|
|
110
|
-
console.error(new Error(ERROR_MESSAGE, { cause: err }));
|
|
111
|
-
}
|
|
112
|
-
startupError = err;
|
|
113
|
-
}
|
|
114
|
-
return core;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async function startAgent({ core, options = {} }) {
|
|
118
|
-
if (startupError) return;
|
|
119
|
-
|
|
120
|
-
const { executor, installOrder = DEFAULT_INSTALL_ORDER } = options;
|
|
121
|
-
const { config, logger } = core;
|
|
122
|
-
|
|
123
|
-
// this should be moved into config because errors are handled here now.
|
|
124
|
-
for (const error of config._errors) {
|
|
125
|
-
throw error;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
logger.info({ tid: threadId }, 'Starting %s v%s', core.agentName, core.agentVersion);
|
|
129
|
-
logger.info({ config }, 'Agent configuration');
|
|
130
|
-
|
|
131
|
-
let plugin;
|
|
132
|
-
if (typeof executor === 'function') {
|
|
133
|
-
plugin = await executor(core);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
for (const svcName of installOrder ?? []) {
|
|
137
|
-
const svc = core[svcName];
|
|
138
|
-
if (svc?.install) {
|
|
139
|
-
logger.trace({ tid: threadId }, 'installing service: %s', svcName);
|
|
140
|
-
await svc.install();
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (plugin?.install) {
|
|
145
|
-
await plugin.install();
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// should this be moved into a separate install side-effect?
|
|
149
|
-
if (isMainThread) {
|
|
150
|
-
core.logDiagnosticFiles();
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return core;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export { loadModules, startAgent };
|