@contrast/agent 5.2.1 → 5.3.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/README.md +31 -22
- package/lib/esm-loader.mjs +7 -142
- package/lib/index.js +4 -69
- package/lib/start-agent.js +146 -0
- package/package.json +8 -8
- package/lib/check-flag-vs-node-version.mjs +0 -61
- package/lib/esm-hooks.mjs +0 -266
- package/lib/initialize.mjs +0 -133
package/README.md
CHANGED
|
@@ -47,40 +47,49 @@ $ npm install @contrast/agent
|
|
|
47
47
|
|
|
48
48
|
## Usage
|
|
49
49
|
|
|
50
|
-
###
|
|
50
|
+
### With LTS (Long Term Support) Node.js Versions
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
method to start the application.
|
|
52
|
+
[Node.js policy](https://nodejs.org/en/about/previous-releases/) is that production applications
|
|
53
|
+
should use only Active LTS or Maintenance LTS releases. All current LTS versions of Node.js support
|
|
54
|
+
ECMAScript modules (ESM) and CommonJS modules (CJS) with the `--import` flag. To ensure that the
|
|
55
|
+
agent can instrument your application, use:
|
|
57
56
|
|
|
58
57
|
```sh
|
|
59
|
-
|
|
58
|
+
node --import @contrast/agent app-main [app arguments]
|
|
60
59
|
```
|
|
61
60
|
|
|
62
|
-
|
|
61
|
+
Notes:
|
|
62
|
+
- `--import` should be used for Node.js LTS (Active and Maintenance) versions `>=18.19.0`
|
|
63
|
+
- Node.js versions `>=20.0.0` and `<20.6.0` are not supported
|
|
64
|
+
|
|
65
|
+
### With end-of-life Node.js Versions
|
|
66
|
+
|
|
67
|
+
When using the agent with end-of-life Node.js versions, use either the `--loader` or
|
|
68
|
+
`--require` flag, depending on the version of Node.js and the module system used.
|
|
63
69
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
Use the `--loader` flag for Node.js versions `>=16.17.0` and `<18.19.0`.
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
node --loader @contrast/agent app-main.mjs [app arguments]
|
|
74
|
+
```
|
|
67
75
|
|
|
68
|
-
|
|
69
|
-
following methods to start the application. This is the appropriate method for
|
|
70
|
-
instrumenting an application that uses CJS, ESM, or a combination of both.
|
|
76
|
+
Use the `--require` (`-r`) flag for Node.js versions `<16.17.0`.
|
|
71
77
|
|
|
72
|
-
|
|
78
|
+
```sh
|
|
79
|
+
node -r @contrast/agent app-main [app arguments]
|
|
80
|
+
```
|
|
73
81
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
```
|
|
82
|
+
Note:
|
|
83
|
+
- `-r` will still work for Node.js versions that have no ESM modules or dependencies.
|
|
77
84
|
|
|
78
|
-
|
|
85
|
+
### With @contrast/agent v4
|
|
79
86
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
```
|
|
87
|
+
The Contrast Node.js agent v4 is still available for use, but does not support ESM
|
|
88
|
+
modules. To use the v4 agent, use the `--require` (`-r`) flag.
|
|
83
89
|
|
|
90
|
+
```sh
|
|
91
|
+
node -r @contrast/agent app-main [app arguments]
|
|
92
|
+
```
|
|
84
93
|
|
|
85
94
|
### Configuration
|
|
86
95
|
|
package/lib/esm-loader.mjs
CHANGED
|
@@ -14,148 +14,13 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import Module from 'node:module';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
import * as hooks from './esm-hooks.mjs';
|
|
20
|
-
import checkImportVsLoaderVsNodeVersion from './check-flag-vs-node-version.mjs';
|
|
21
|
-
|
|
22
|
-
// might need to get exclude function here as opposed to deep within initialize
|
|
23
|
-
// execution.
|
|
24
|
-
// 1) core should be a singleton so it's always shared between modules
|
|
25
|
-
// 2) core should include the redirect map.
|
|
26
|
-
// 3) is the exclude function needed here as we move to async loading? if so,
|
|
27
|
-
// this will need to add the root directory(ies) to an exclude list.
|
|
28
|
-
|
|
29
|
-
const logLoad = 1;
|
|
30
|
-
// eslint-disable-next-line no-unused-vars
|
|
31
|
-
const logResolve = 2;
|
|
32
|
-
const logRequireAll = 4;
|
|
33
|
-
let log = 0;
|
|
34
|
-
if (process.env.CSI_HOOKS_LOG) {
|
|
35
|
-
log = +process.env.CSI_HOOKS_LOG;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// verify that we're running with the correct flag for the version of node.
|
|
39
|
-
const { flag, msg } = checkImportVsLoaderVsNodeVersion();
|
|
40
|
-
|
|
41
|
-
if (msg) {
|
|
42
|
-
console.error(msg);
|
|
43
|
-
throw new Error(msg);
|
|
44
|
-
// belt and suspenders
|
|
45
|
-
// eslint-disable-next-line no-unreachable
|
|
46
|
-
process.exit(1); // eslint-disable-line no-process-exit
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
//
|
|
50
|
-
// if CJS hooks are not already setup, setup CJS hooks. each thread must patch
|
|
51
|
-
// Module. I don't think this will be called more than once, but the flag is
|
|
52
|
-
// in place just in case.
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
const core = (await import('./initialize.mjs')).default;
|
|
17
|
+
const require = Module.createRequire(import.meta.url);
|
|
18
|
+
const { startAgent } = require('./start-agent');
|
|
56
19
|
let load, resolve;
|
|
57
20
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const originalRequire = Module.prototype.require;
|
|
63
|
-
const originalCompile = Module.prototype._compile;
|
|
64
|
-
const originalExtensions = Module._extensions['.js'];
|
|
65
|
-
const originalLoad = Module._load;
|
|
66
|
-
if (originalLoad?.name !== '__loadOverride') {
|
|
67
|
-
// debugger; // this is a good place to make a quick check if things aren't working
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
Module.prototype.require = function(moduleId) {
|
|
71
|
-
(log & logRequireAll) && console.log('CJS -> require() called for', moduleId);
|
|
72
|
-
return originalRequire.call(this, moduleId);
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
Module.prototype._compile = function(code, filename) {
|
|
76
|
-
(log & logRequireAll) && console.log('CJS -> _compile() called for', filename);
|
|
77
|
-
return originalCompile.call(this, code, filename);
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
Module._extensions['.js'] = function(module, filename) {
|
|
81
|
-
(log & logRequireAll) && console.log('CJS -> _extensions[".js"] called for', filename);
|
|
82
|
-
return originalExtensions.call(this, module, filename);
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
Module._load = function(request, parent, isMain) {
|
|
86
|
-
(log & logLoad) && console.log(`CJS(${W.threadId}) -> _load() ${request}`);
|
|
87
|
-
return originalLoad.call(this, request, parent, isMain);
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
core.threadInfo.syncEmitter = new EventEmitter();
|
|
91
|
-
// abstract how notifications are posted so that the non-loader
|
|
92
|
-
// code is not coupled to the implementation. the loader-thread
|
|
93
|
-
// complement of this is in esm-hooks.
|
|
94
|
-
//
|
|
95
|
-
// specifically, this is the main thread and post() directly emits
|
|
96
|
-
// the event while in the loader thread post() uses post.postMessage()
|
|
97
|
-
// to communicate back to this (the main) thread.
|
|
98
|
-
core.threadInfo.post = (type, data) => {
|
|
99
|
-
core.threadInfo.syncEmitter.emit(type, data);
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
//
|
|
103
|
-
// setup ESM hooks
|
|
104
|
-
//
|
|
105
|
-
// if register exists this is 20.6.0 or later. we do not support
|
|
106
|
-
// node 20 prior to 20.6.0 (or 20.9.0 when 20 became LTS).
|
|
107
|
-
// news flash: backported register to node 18.19.0, so checking register
|
|
108
|
-
// is no longer enough.
|
|
109
|
-
//
|
|
110
|
-
if (Module.register && flag === '--import') {
|
|
111
|
-
// this file should never be executed in the loader thread; this file creates
|
|
112
|
-
// the loader thread by calling register(). the loader thread must create its
|
|
113
|
-
// own copy of threadInfo and insert it into core.
|
|
114
|
-
if (!core.threadInfo.isMainThread) {
|
|
115
|
-
// only get an error in CI on node v18.
|
|
116
|
-
throw new Error('esm-loader.mjs should not be executed in the loader thread');
|
|
117
|
-
}
|
|
118
|
-
const { MessageChannel } = await import('node:worker_threads');
|
|
119
|
-
const { port1, port2 } = new MessageChannel();
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
core.threadInfo.port = port1;
|
|
123
|
-
// messages received on the port are re-emitted as standard node events. the
|
|
124
|
-
// body must contain the type; data can be undefined.
|
|
125
|
-
core.threadInfo.missingTypeCount = 0;
|
|
126
|
-
core.threadInfo.port.on('message', (body) => {
|
|
127
|
-
const { type, data } = body;
|
|
128
|
-
if (type) {
|
|
129
|
-
core.threadInfo.syncEmitter.emit(type, data);
|
|
130
|
-
} else {
|
|
131
|
-
core.threadInfo.missingTypeCount += 1;
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
core.threadInfo.port.unref();
|
|
135
|
-
|
|
136
|
-
// record the URL of the entry point.
|
|
137
|
-
core.threadInfo.url = import.meta.url;
|
|
138
|
-
|
|
139
|
-
// get relative URL
|
|
140
|
-
const url = new URL('./esm-hooks.mjs', import.meta.url);
|
|
141
|
-
await Module.register(url.href, import.meta.url, { data: { port: port2 }, transferList: [port2] });
|
|
142
|
-
|
|
143
|
-
// we only need to do this if there is a background thread.
|
|
144
|
-
// The esmHooks component of the main agent will send TS settings update to the loader agent via the port.
|
|
145
|
-
// To get the loader agent components to update we just need to forward the settings using `.messages` emitter.
|
|
146
|
-
core.messages.on(Event.SERVER_SETTINGS_UPDATE, (msg) => {
|
|
147
|
-
core.threadInfo.port.postMessage({
|
|
148
|
-
type: Event.SERVER_SETTINGS_UPDATE,
|
|
149
|
-
...msg,
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
// it's not possible to conditionally export, but exporting undefined
|
|
154
|
-
// values is close.
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const finalHooks = (Module.register && flag === '--import') ? {} : hooks;
|
|
158
|
-
({ load, resolve } = finalHooks);
|
|
159
|
-
}
|
|
21
|
+
try {
|
|
22
|
+
const core = await startAgent({ type: 'esm' });
|
|
23
|
+
({ load, resolve } = core.esmHooks.hooks); // remove when all LTS versions support register
|
|
24
|
+
} catch (err) { } // eslint-disable-line no-empty
|
|
160
25
|
|
|
161
|
-
export { load, resolve };
|
|
26
|
+
export { load, resolve }; // remove when all LTS versions support register
|
package/lib/index.js
CHANGED
|
@@ -15,73 +15,8 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const
|
|
19
|
-
const {
|
|
20
|
-
name: agentName,
|
|
21
|
-
version: agentVersion,
|
|
22
|
-
engines: {
|
|
23
|
-
node: nodeEngine,
|
|
24
|
-
npm: npmEngine
|
|
25
|
-
}
|
|
26
|
-
} = require('../package.json');
|
|
27
|
-
const agentify = require('@contrast/agentify')({ agentName, agentVersion });
|
|
18
|
+
const { startAgent } = require('./start-agent');
|
|
28
19
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
nodeEngine.split('||').forEach((range) => {
|
|
33
|
-
const minVersion = semver.minVersion(range).toString();
|
|
34
|
-
const maxVersion = range.split('<').pop()
|
|
35
|
-
.trim();
|
|
36
|
-
validRanges += `${minVersion} and ${maxVersion}, `;
|
|
37
|
-
});
|
|
38
|
-
throw new Error(`Contrast only officially supports Node LTS versions between ${validRanges}but detected ${process.version}.`);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
core.startupValidation = {
|
|
42
|
-
/**
|
|
43
|
-
* This will run after we onboard to the UI and pick up any remote settings.
|
|
44
|
-
*/
|
|
45
|
-
install() {
|
|
46
|
-
if (
|
|
47
|
-
!core.config.getEffectiveValue('assess.enable') &&
|
|
48
|
-
!core.config.getEffectiveValue('protect.enable')
|
|
49
|
-
) {
|
|
50
|
-
throw new Error('Neither Assess nor Protect are enabled. Check local configuration and UI settings');
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
core.npmVersionRange = npmEngine;
|
|
56
|
-
require('@contrast/telemetry')(core);
|
|
57
|
-
require('@contrast/assess')(core);
|
|
58
|
-
require('@contrast/architecture-components')(core);
|
|
59
|
-
require('@contrast/library-analysis')(core);
|
|
60
|
-
require('@contrast/route-coverage')(core);
|
|
61
|
-
require('@contrast/protect')(core); // protect loads lastly, being the only non-passive feature
|
|
62
|
-
|
|
63
|
-
if (process.env.CSI_EXPOSE_CORE) {
|
|
64
|
-
const coreKey = Symbol.for('contrast:core');
|
|
65
|
-
global[coreKey] = core;
|
|
66
|
-
}
|
|
67
|
-
}, {
|
|
68
|
-
installOrder: [
|
|
69
|
-
'reporter',
|
|
70
|
-
'startupValidation',
|
|
71
|
-
'telemetry',
|
|
72
|
-
'contrastMethods',
|
|
73
|
-
'deadzones',
|
|
74
|
-
'scopes',
|
|
75
|
-
'sources',
|
|
76
|
-
'architectureComponents',
|
|
77
|
-
'assess',
|
|
78
|
-
'protect',
|
|
79
|
-
'depHooks',
|
|
80
|
-
'routeCoverage',
|
|
81
|
-
'libraryAnalysis',
|
|
82
|
-
'heapSnapshots',
|
|
83
|
-
'rewriteHooks',
|
|
84
|
-
'functionHooks',
|
|
85
|
-
'metrics',
|
|
86
|
-
],
|
|
87
|
-
});
|
|
20
|
+
try {
|
|
21
|
+
startAgent({ type: 'cjs' });
|
|
22
|
+
} catch (err) {} // eslint-disable-line no-empty
|
|
@@ -0,0 +1,146 @@
|
|
|
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 process = require('process');
|
|
19
|
+
const semver = require('semver');
|
|
20
|
+
const _agentify = require('@contrast/agentify');
|
|
21
|
+
const {
|
|
22
|
+
name: agentName,
|
|
23
|
+
version: agentVersion,
|
|
24
|
+
engines: {
|
|
25
|
+
node: nodeEngine,
|
|
26
|
+
npm: npmEngine
|
|
27
|
+
}
|
|
28
|
+
} = require('../package.json');
|
|
29
|
+
|
|
30
|
+
function initCore() {
|
|
31
|
+
if (!semver.satisfies(process.version, nodeEngine)) {
|
|
32
|
+
let validRanges = '';
|
|
33
|
+
nodeEngine.split('||').forEach((range) => {
|
|
34
|
+
const minVersion = semver.minVersion(range).toString();
|
|
35
|
+
const maxVersion = range.split('<').pop()
|
|
36
|
+
.trim();
|
|
37
|
+
validRanges += `${minVersion} and ${maxVersion}, `;
|
|
38
|
+
});
|
|
39
|
+
throw new Error(`Contrast only officially supports Node LTS versions between ${validRanges}but detected ${process.version}.`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
checkImportVsLoaderVsNodeVersion();
|
|
43
|
+
|
|
44
|
+
const core = { agentName, agentVersion };
|
|
45
|
+
|
|
46
|
+
core.startupValidation = {
|
|
47
|
+
/**
|
|
48
|
+
* This will run after we onboard to the UI and pick up any remote settings.
|
|
49
|
+
*/
|
|
50
|
+
install() {
|
|
51
|
+
if (
|
|
52
|
+
!core.config.getEffectiveValue('assess.enable') &&
|
|
53
|
+
!core.config.getEffectiveValue('protect.enable')
|
|
54
|
+
) {
|
|
55
|
+
throw new Error('Neither Assess nor Protect are enabled. Check local configuration and UI settings');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
core.npmVersionRange = npmEngine;
|
|
61
|
+
|
|
62
|
+
if (process.env.CSI_EXPOSE_CORE) {
|
|
63
|
+
global[Symbol.for('contrast:core')] = core;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return core;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function loadFeatures(core) {
|
|
70
|
+
require('@contrast/assess')(core);
|
|
71
|
+
require('@contrast/architecture-components')(core);
|
|
72
|
+
require('@contrast/library-analysis')(core);
|
|
73
|
+
require('@contrast/route-coverage')(core);
|
|
74
|
+
require('@contrast/protect')(core);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* check to see if we're running with the correct flag (--loader or --import)
|
|
79
|
+
* for the right version of node. we do not function correctly when --loader
|
|
80
|
+
* is used for node >= 18 or when --import is used for node < 18.
|
|
81
|
+
*/
|
|
82
|
+
function checkImportVsLoaderVsNodeVersion() {
|
|
83
|
+
// allow testing to ignore these restrictions
|
|
84
|
+
const noValidate = process.env.CSI_EXPOSE_CORE === 'no-validate';
|
|
85
|
+
let msg;
|
|
86
|
+
|
|
87
|
+
const { execArgv, version } = process;
|
|
88
|
+
// eslint-disable-next-line newline-per-chained-call
|
|
89
|
+
const [major, minor] = version.slice(1).split('.').map(Number);
|
|
90
|
+
for (let i = 0; i < execArgv.length; i++) {
|
|
91
|
+
const loader = execArgv[i];
|
|
92
|
+
const agent = execArgv[i + 1];
|
|
93
|
+
if (['--import', '--loader', '--experimental-loader'].includes(loader) && agent?.startsWith('@contrast/agent')) {
|
|
94
|
+
if (noValidate) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (loader === '--import') {
|
|
98
|
+
// loader is --import
|
|
99
|
+
if (major === 18 && minor >= 19 || major >= 20) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (major <= 18 && minor < 19) {
|
|
103
|
+
msg = 'Contrast requires node versions less than 18.19.0 use the --loader flag for ESM support';
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
// loader is either --loader or --experimental-loader (same thing)
|
|
107
|
+
if (major >= 20 || major === 18 && minor >= 19) {
|
|
108
|
+
msg = 'Contrast requires node versions >= 18.19.0 use the --import flag for ESM support';
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (msg) throw new Error(msg);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function startAgent({ type = 'cjs' } = {}) {
|
|
119
|
+
const core = initCore();
|
|
120
|
+
const agentify = _agentify(core);
|
|
121
|
+
|
|
122
|
+
return agentify(loadFeatures, {
|
|
123
|
+
installOrder: [
|
|
124
|
+
'reporter',
|
|
125
|
+
'startupValidation',
|
|
126
|
+
'contrastMethods',
|
|
127
|
+
'deadzones',
|
|
128
|
+
'scopes',
|
|
129
|
+
'sources',
|
|
130
|
+
'architectureComponents',
|
|
131
|
+
'assess',
|
|
132
|
+
'protect',
|
|
133
|
+
'depHooks',
|
|
134
|
+
'routeCoverage',
|
|
135
|
+
'libraryAnalysis',
|
|
136
|
+
'heapSnapshots',
|
|
137
|
+
'metrics',
|
|
138
|
+
'rewriteHooks',
|
|
139
|
+
'functionHooks',
|
|
140
|
+
'esmHooks',
|
|
141
|
+
],
|
|
142
|
+
type
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = { startAgent };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/agent",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.0",
|
|
4
4
|
"description": "Assess and Protect agents for Node.js",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
|
|
@@ -21,13 +21,13 @@
|
|
|
21
21
|
"test": "../scripts/test.sh"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@contrast/agentify": "1.
|
|
25
|
-
"@contrast/architecture-components": "1.
|
|
26
|
-
"@contrast/assess": "1.
|
|
27
|
-
"@contrast/library-analysis": "1.
|
|
28
|
-
"@contrast/protect": "1.
|
|
29
|
-
"@contrast/route-coverage": "1.
|
|
30
|
-
"@contrast/telemetry": "1.
|
|
24
|
+
"@contrast/agentify": "1.21.0",
|
|
25
|
+
"@contrast/architecture-components": "1.17.0",
|
|
26
|
+
"@contrast/assess": "1.25.0",
|
|
27
|
+
"@contrast/library-analysis": "1.18.0",
|
|
28
|
+
"@contrast/protect": "1.33.0",
|
|
29
|
+
"@contrast/route-coverage": "1.17.0",
|
|
30
|
+
"@contrast/telemetry": "1.5.0",
|
|
31
31
|
"semver": "^7.3.7"
|
|
32
32
|
}
|
|
33
33
|
}
|
|
@@ -1,61 +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
|
-
// check to see if we're running with the correct flag (--loader or --import)
|
|
17
|
-
// for the right version of node. we do not function correctly when --loader
|
|
18
|
-
// is used for node >= 20 or when --import is used for node < 20.
|
|
19
|
-
//
|
|
20
|
-
export default function checkImportVsLoaderVsNodeVersion() {
|
|
21
|
-
// allow testing to ignore these restrictions
|
|
22
|
-
const noValidate = process.env.CSI_EXPOSE_CORE === 'no-validate';
|
|
23
|
-
let flag;
|
|
24
|
-
const { execArgv, version, env: { NODE_OPTIONS } } = process;
|
|
25
|
-
|
|
26
|
-
// eslint-disable-next-line newline-per-chained-call
|
|
27
|
-
const [major, minor] = version.slice(1).split('.').map(Number);
|
|
28
|
-
|
|
29
|
-
const _execArgs = [
|
|
30
|
-
...(NODE_OPTIONS?.split?.(/\s+/).map((v) => v.trim()) || []),
|
|
31
|
-
...execArgv,
|
|
32
|
-
]
|
|
33
|
-
|
|
34
|
-
for (let i = 0; i < _execArgs.length; i++) {
|
|
35
|
-
const loader = _execArgs[i];
|
|
36
|
-
const agent = _execArgs[i + 1];
|
|
37
|
-
if (['--import', '--loader', '--experimental-loader'].includes(loader) && agent?.startsWith('@contrast/agent')) {
|
|
38
|
-
flag = loader;
|
|
39
|
-
if (noValidate) {
|
|
40
|
-
return { flag, msg: '' };
|
|
41
|
-
}
|
|
42
|
-
if (loader === '--import') {
|
|
43
|
-
// loader is --import
|
|
44
|
-
if (major === 18 && minor >= 19 || major >= 20) {
|
|
45
|
-
return { flag, msg: '' };
|
|
46
|
-
}
|
|
47
|
-
if (major <= 18 && minor < 19) {
|
|
48
|
-
return { flag, msg: 'Contrast requires node versions less than 18.19.0 use the --loader flag for ESM support' };
|
|
49
|
-
}
|
|
50
|
-
} else {
|
|
51
|
-
// loader is either --loader or --experimental-loader (same thing)
|
|
52
|
-
if (major >= 20 || major === 18 && minor >= 19) {
|
|
53
|
-
return { flag, msg: 'Contrast requires node versions >= 18.19.0 use the --import flag for ESM support' };
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
break;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
// i think we only get here if flag === '--loader' and node < 20
|
|
60
|
-
return { flag, msg: '' };
|
|
61
|
-
}
|
package/lib/esm-hooks.mjs
DELETED
|
@@ -1,266 +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
|
-
import Module from 'node:module';
|
|
16
|
-
import { readFile as rf } from 'node:fs/promises';
|
|
17
|
-
import W from 'node:worker_threads';
|
|
18
|
-
|
|
19
|
-
const logLoad = 1;
|
|
20
|
-
const logResolve = 2;
|
|
21
|
-
const logRequireAll = 4;
|
|
22
|
-
const logApplication = 8; // eslint-disable-line no-unused-vars
|
|
23
|
-
let log = 0;
|
|
24
|
-
if (process.env.CSI_HOOKS_LOG) {
|
|
25
|
-
log = +process.env.CSI_HOOKS_LOG;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const [major, minor] = process.versions.node.split('.').map(it => +it);
|
|
29
|
-
const isLT16_12 = major < 16 || (major === 16 && minor < 12);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
// from esmock: git@github.com:iambumblehead/esmock.git
|
|
33
|
-
//
|
|
34
|
-
// new versions of node: when multiple loaders are used and context
|
|
35
|
-
// is passed to nextResolve, the process crashes in a recursive call
|
|
36
|
-
// see: /esmock/issues/#48
|
|
37
|
-
//
|
|
38
|
-
// old versions of node: if context.parentURL is defined, and context
|
|
39
|
-
// is not passed to nextResolve, the tests fail
|
|
40
|
-
//
|
|
41
|
-
// later versions of node v16 include 'node-addons'
|
|
42
|
-
async function protectedNextResolve(nextResolve, specifier, context) {
|
|
43
|
-
if (context.parentURL) {
|
|
44
|
-
if (context.conditions.at(-1) === 'node-addons' || context.importAssertions || isLT16_12) {
|
|
45
|
-
return nextResolve(specifier, context);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return nextResolve(specifier);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// import.meta.resolve('node-fetch') v20 became synchronous. not all that useful for now because
|
|
53
|
-
// of the significant break in behavior. but the function is great - it resolves a specifier to the
|
|
54
|
-
// file that would be loaded.
|
|
55
|
-
// file:///home/bruce/github/csi/rasp-v3/node_modules/node-fetch/src/index.js
|
|
56
|
-
|
|
57
|
-
const { default: core } = await import('./initialize.mjs');
|
|
58
|
-
let load, resolve, initialize;
|
|
59
|
-
|
|
60
|
-
if (core) {
|
|
61
|
-
const { esmHooks: { mappings, fixPath, getFileType } } = core;
|
|
62
|
-
initialize = Module.register && async function(data) {
|
|
63
|
-
if (data?.port) {
|
|
64
|
-
data.port.on('message', _msg => {
|
|
65
|
-
// we don't currently send messages to the loader thread but when we do,
|
|
66
|
-
// this is where they will show up.
|
|
67
|
-
//console.log('ESM HOOK -> INIT -> MESSAGE', _msg);
|
|
68
|
-
});
|
|
69
|
-
data.port.postMessage({ type: 'keep-alive', data: 'hello from esm-hooks' });
|
|
70
|
-
data.port.unref();
|
|
71
|
-
// this is running in the loader thread. save thread info because it can't
|
|
72
|
-
// be set from the main thread.
|
|
73
|
-
if (W.isMainThread) {
|
|
74
|
-
throw new Error('initialize() called from main thread.');
|
|
75
|
-
}
|
|
76
|
-
core.threadInfo.isMainThread = false;
|
|
77
|
-
core.threadInfo.threadId = W.threadId;
|
|
78
|
-
core.threadInfo.port = data.port;
|
|
79
|
-
// the loader thread's post() sends via the port.
|
|
80
|
-
core.threadInfo.post = (type, data) => {
|
|
81
|
-
core.threadInfo.port.postMessage({ type, data });
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
log && console.log('ESM HOOK -> INIT');
|
|
86
|
-
// it's not clear to me why Module is present when initialize() is being
|
|
87
|
-
// executed in the loader thread. but it is. (code here originally loaded
|
|
88
|
-
// Module via createRequire(), but that is not necessary.)
|
|
89
|
-
const originalRequire = Module.prototype.require;
|
|
90
|
-
const originalCompile = Module.prototype._compile;
|
|
91
|
-
const originalExtensions = Module._extensions['.js'];
|
|
92
|
-
const originalLoad = Module._load;
|
|
93
|
-
|
|
94
|
-
// Module needs to be patched in the loader thread too. initialize() runs in
|
|
95
|
-
// that context.
|
|
96
|
-
Module.prototype.require = function(moduleId) {
|
|
97
|
-
(log & logRequireAll) && console.log(`CJS(init ${W.threadId}) -> require(${moduleId})`);
|
|
98
|
-
return originalRequire.call(this, moduleId);
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
Module.prototype._compile = function(code, filename) {
|
|
102
|
-
(log & logRequireAll) && console.log(`CJS(init ${W.threadId}) -> _compile(${filename})`);
|
|
103
|
-
return originalCompile.call(this, code, filename);
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
Module._extensions['.js'] = function(module, filename) {
|
|
107
|
-
(log & logRequireAll) && console.log(`CJS(init ${W.threadId}) -> _extensions[".js"]: ${filename}`);
|
|
108
|
-
return originalExtensions.call(this, module, filename);
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
Module._load = function(request, parent, isMain) {
|
|
112
|
-
(log & logLoad) && console.log(`CJS(init ${W.threadId}) -> _load(${request})`);
|
|
113
|
-
return originalLoad.call(this, request, parent, isMain);
|
|
114
|
-
};
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
resolve = async function(specifier, context, nextResolve) {
|
|
118
|
-
(log & logResolve) && console.log(`ESM HOOK(${W.threadId}) -> RESOLVE -> ${specifier}`);
|
|
119
|
-
let isFlaggedToPatch = false;
|
|
120
|
-
if (context.parentURL) {
|
|
121
|
-
isFlaggedToPatch = context.parentURL.endsWith('csi-flag=', context.parentURL.length - 1);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// this needs to be generalized so it uses the execArgv value. the idea is to
|
|
125
|
-
// capture when the application actually starts. is this needed? i don't think so
|
|
126
|
-
// because the loaders aren't instantiated until esm-loader.mjs returns/register()s
|
|
127
|
-
// the hooks. i am leaving the comment here as a breadcrumb in case we need to
|
|
128
|
-
// capture the application start time. but we could 1) look at process.argv, 2)
|
|
129
|
-
// find the first argument, and 3) notice when that's loaded and capture that
|
|
130
|
-
// "now we are in the user's application".
|
|
131
|
-
//
|
|
132
|
-
//if (false) debugger;
|
|
133
|
-
//if (log & logApplication) {
|
|
134
|
-
// const RE = /test-integration-servers\/express.m?js(\?.+)?$/;
|
|
135
|
-
// if (specifier.match(RE) || context.parentURL?.match(RE)) {
|
|
136
|
-
// console.log(`ESM HOOK(${W.threadId}) -> RESOLVE -> ${specifier} from ${context.parentURL}`);
|
|
137
|
-
// }
|
|
138
|
-
//}
|
|
139
|
-
|
|
140
|
-
// this works for most of what we need to patch because they are not esm-native
|
|
141
|
-
// modules. but for esm-native modules, e.g., node-fetch, this won't work. they
|
|
142
|
-
// will need to be patched in the loader thread.
|
|
143
|
-
//
|
|
144
|
-
// We could walk up the parent chain to see if we should interpret this specifier
|
|
145
|
-
// as a module or commonjs, but it seems more straightforward to just always
|
|
146
|
-
// redirect. walking up the parent chain would let us avoid redirecting commonjs
|
|
147
|
-
// files. we could also call nextResolve() and let node tell us type of the file
|
|
148
|
-
// is but that would potentially create problems when other hooks, e.g., datadog,
|
|
149
|
-
// are in place.
|
|
150
|
-
//
|
|
151
|
-
// also we could check to see if this module's already been patched and skip this.
|
|
152
|
-
// not sure of that though; it might get loaded as a module, not a commonjs file,
|
|
153
|
-
// and that would be a problem. so when we start patching esm-native modules, we'll
|
|
154
|
-
// need a second "already patched" weakmap.
|
|
155
|
-
if (!isFlaggedToPatch && specifier in mappings) {
|
|
156
|
-
// eslint-disable-next-line prefer-const
|
|
157
|
-
let { url, format, target } = mappings[specifier];
|
|
158
|
-
// set flag to module or commonjs. i'm not sure this is needed but am keeping it
|
|
159
|
-
// in place until we have to implement esm-native module rewrites/wrapping. this
|
|
160
|
-
// is the point at which resolve() communicates to load().
|
|
161
|
-
//
|
|
162
|
-
// builtin's are probably the most likely to be loaded, so they're first.
|
|
163
|
-
// some tweaks might be needed when we start to patch esm-native modules
|
|
164
|
-
// in esm-native-code:
|
|
165
|
-
// https://nodejs.org/docs/latest-v20.x/api/esm.html#builtin-modules
|
|
166
|
-
// https://nodejs.org/docs/latest-v20.x/api/module.html#modulesyncbuiltinesmexports
|
|
167
|
-
if (target === 'builtin') {
|
|
168
|
-
url = `${url.href}?csi-flag=m`;
|
|
169
|
-
format = 'module';
|
|
170
|
-
} else if (target === 'commonjs') {
|
|
171
|
-
url = `${url.href}?csi-flag=c`;
|
|
172
|
-
format = getFileType(url) || format || 'commonjs';
|
|
173
|
-
} else if (target === 'module') {
|
|
174
|
-
url = `${url.href}?csi-flag=m`;
|
|
175
|
-
format = getFileType(url) || format || 'module';
|
|
176
|
-
} else {
|
|
177
|
-
throw new Error(`unexpected target ${target} for ${specifier}`);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return {
|
|
181
|
-
url,
|
|
182
|
-
format,
|
|
183
|
-
shortCircuit: true,
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
return protectedNextResolve(nextResolve, specifier, context);
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
// readFile is a live binding. we need to capture it so it won't be
|
|
191
|
-
// altered by any patching later.
|
|
192
|
-
const readFile = rf;
|
|
193
|
-
|
|
194
|
-
load = async function(url, context, nextLoad) {
|
|
195
|
-
(log & logLoad) && console.log(`ESM HOOK(${W.threadId}) -> LOAD ${url}`);
|
|
196
|
-
|
|
197
|
-
const urlObject = new URL(url);
|
|
198
|
-
|
|
199
|
-
if (urlObject.searchParams.has('csi-flag')) {
|
|
200
|
-
// target type will, i think, be used to determine if this will require extra
|
|
201
|
-
// processing of some sort for esm-native modules.
|
|
202
|
-
// eslint-disable-next-line no-unused-vars
|
|
203
|
-
const targetType = urlObject.searchParams.get('csi-flag');
|
|
204
|
-
const { pathname } = urlObject;
|
|
205
|
-
|
|
206
|
-
(log & logLoad) && console.log(`ESM HOOK(${W.threadId}) -> LOAD -> CSI FLAG ${pathname}`);
|
|
207
|
-
|
|
208
|
-
const source = await readFile(fixPath(pathname), 'utf8');
|
|
209
|
-
return {
|
|
210
|
-
source,
|
|
211
|
-
format: 'module',
|
|
212
|
-
shortCircuit: true,
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (urlObject.pathname.endsWith('.node')) {
|
|
217
|
-
const metaUrl = JSON.stringify(url);
|
|
218
|
-
const addon = urlObject.pathname;
|
|
219
|
-
return {
|
|
220
|
-
source: `require("node:module").createRequire(${metaUrl})("${addon}")`,
|
|
221
|
-
format: 'commonjs',
|
|
222
|
-
shortCircuit: true,
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// if it's not a builtin, a .node addon, or a flagged file, it needs to be
|
|
227
|
-
// rewritten if it's a module.
|
|
228
|
-
if (urlObject.pathname.match(/(.js|.mjs|.cjs)$/)) {
|
|
229
|
-
// if it's not a module it will be rewritten by the require hooks.
|
|
230
|
-
const type = getFileType(urlObject);
|
|
231
|
-
if (type !== 'module') {
|
|
232
|
-
return nextLoad(url, context);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const filename = fixPath(urlObject.pathname);
|
|
236
|
-
const source = await readFile(filename, 'utf8');
|
|
237
|
-
|
|
238
|
-
const rewriteOptions = {
|
|
239
|
-
filename,
|
|
240
|
-
isModule: type === 'module',
|
|
241
|
-
inject: true,
|
|
242
|
-
wrap: type !== 'module', // cannot wrap modules
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
core.threadInfo.post('rewrite', rewriteOptions);
|
|
246
|
-
|
|
247
|
-
const result = core.rewriter.rewrite(source, rewriteOptions);
|
|
248
|
-
|
|
249
|
-
return {
|
|
250
|
-
source: result.code,
|
|
251
|
-
format: type || 'commonjs', // don't know what else to do here. log?
|
|
252
|
-
shortCircuit: true,
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
// this never gets called, so hmmm.
|
|
256
|
-
return nextLoad(url, context);
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
export {
|
|
261
|
-
load,
|
|
262
|
-
resolve,
|
|
263
|
-
initialize,
|
|
264
|
-
// TODO: add for testing/verification
|
|
265
|
-
//loaderIsVerified as default
|
|
266
|
-
};
|
package/lib/initialize.mjs
DELETED
|
@@ -1,133 +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
|
-
// this file is the bootstrap for starting the agent's loader. it needs to be executed
|
|
17
|
-
// in the main thread and the loader thread. it should not try to load all the modules
|
|
18
|
-
// required to fully instantiate the agent, but only those required to make the loader
|
|
19
|
-
// work.
|
|
20
|
-
|
|
21
|
-
import { default as semver } from 'semver';
|
|
22
|
-
import { default as Module } from 'node:module';
|
|
23
|
-
import { isMainThread, threadId } from 'node:worker_threads';
|
|
24
|
-
|
|
25
|
-
const require = Module.createRequire(import.meta.url);
|
|
26
|
-
|
|
27
|
-
const {
|
|
28
|
-
name: agentName,
|
|
29
|
-
version: agentVersion,
|
|
30
|
-
engines: {
|
|
31
|
-
node: nodeEngine,
|
|
32
|
-
npm: npmEngine
|
|
33
|
-
}
|
|
34
|
-
} = require('../package.json');
|
|
35
|
-
|
|
36
|
-
const installOrder = [
|
|
37
|
-
'reporter',
|
|
38
|
-
'startupValidation',
|
|
39
|
-
'contrastMethods',
|
|
40
|
-
'deadzones',
|
|
41
|
-
'scopes',
|
|
42
|
-
'sources',
|
|
43
|
-
'architectureComponents',
|
|
44
|
-
'assess',
|
|
45
|
-
'protect',
|
|
46
|
-
'depHooks',
|
|
47
|
-
'esmHooks',
|
|
48
|
-
'routeCoverage',
|
|
49
|
-
'libraryAnalysis',
|
|
50
|
-
'heapSnapshots',
|
|
51
|
-
'rewriteHooks',
|
|
52
|
-
'functionHooks',
|
|
53
|
-
'metrics',
|
|
54
|
-
];
|
|
55
|
-
|
|
56
|
-
//
|
|
57
|
-
// In order to avoid breaking every test and function in the agent, agentify
|
|
58
|
-
// isn't changed at all; initialize() replaces it with new behavior.
|
|
59
|
-
//
|
|
60
|
-
import { loadModules, startAgent } from '@contrast/agentify/lib/initialize.mjs';
|
|
61
|
-
|
|
62
|
-
let core;
|
|
63
|
-
|
|
64
|
-
if (!core) {
|
|
65
|
-
// self-identification
|
|
66
|
-
const me = import.meta.url;
|
|
67
|
-
// we can determine when the app is being run by examining this
|
|
68
|
-
const argv = process.argv.slice();
|
|
69
|
-
// we can tell how the agent was started by examining this. --loader/--import must
|
|
70
|
-
// be correct for the node version.
|
|
71
|
-
const execArgv = process.execArgv.slice();
|
|
72
|
-
|
|
73
|
-
core = { agentName, agentVersion, me, argv, execArgv };
|
|
74
|
-
|
|
75
|
-
core = await loadModules({ core, options: {} });
|
|
76
|
-
|
|
77
|
-
if (core) core = await startAgent({ core, options: { executor, installOrder } });
|
|
78
|
-
|
|
79
|
-
// self identification
|
|
80
|
-
// for communications between the main and loader thread (if present)
|
|
81
|
-
if (core) core.threadInfo = {
|
|
82
|
-
isMainThread,
|
|
83
|
-
threadId,
|
|
84
|
-
port: undefined, // filled in if there's a loader thread
|
|
85
|
-
post: () => {}, // replaced by appropriate function
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
if (process.env.CSI_EXPOSE_CORE) {
|
|
89
|
-
const coreKey = Symbol.for('contrast:core');
|
|
90
|
-
global[coreKey] = core;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export default core;
|
|
95
|
-
|
|
96
|
-
async function executor(core) {
|
|
97
|
-
if (!semver.satisfies(process.version, nodeEngine)) {
|
|
98
|
-
let validRanges = '';
|
|
99
|
-
nodeEngine.split('||').forEach((range) => {
|
|
100
|
-
const minVersion = semver.minVersion(range).toString();
|
|
101
|
-
const maxVersion = range.split('<').pop()
|
|
102
|
-
.trim();
|
|
103
|
-
validRanges += `${minVersion} and ${maxVersion}, `;
|
|
104
|
-
});
|
|
105
|
-
throw new Error(`Contrast supports Node LTS versions between ${validRanges}but detected ${process.version}.`);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
core.startupValidation = {
|
|
109
|
-
/**
|
|
110
|
-
* This will run after we onboard to the UI and pick up any remote settings.
|
|
111
|
-
*/
|
|
112
|
-
install() {
|
|
113
|
-
if (
|
|
114
|
-
isMainThread &&
|
|
115
|
-
!core.config.getEffectiveValue('assess.enable') &&
|
|
116
|
-
!core.config.getEffectiveValue('protect.enable')
|
|
117
|
-
) {
|
|
118
|
-
throw new Error('Neither Assess nor Protect are enabled. Check local configuration and UI settings');
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
core.npmVersionRange = npmEngine;
|
|
124
|
-
if (isMainThread) {
|
|
125
|
-
require('@contrast/assess')(core);
|
|
126
|
-
require('@contrast/architecture-components')(core);
|
|
127
|
-
require('@contrast/library-analysis')(core);
|
|
128
|
-
require('@contrast/route-coverage')(core);
|
|
129
|
-
require('@contrast/protect')(core); // protect loads last; the other features are all passive
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return core; // should this return core or just null/undefined?
|
|
133
|
-
}
|