@contrast/config 1.49.2 → 1.51.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/common.js +1 -1
- package/lib/config.js +49 -27
- package/lib/index.d.ts +2 -2
- package/lib/options.js +33 -8
- package/package.json +5 -4
package/lib/common.js
CHANGED
|
@@ -45,7 +45,7 @@ const {
|
|
|
45
45
|
} = require('@contrast/common');
|
|
46
46
|
|
|
47
47
|
function coerceLowerCase(path) {
|
|
48
|
-
return function(remoteData) {
|
|
48
|
+
return function (remoteData) {
|
|
49
49
|
const value = get(remoteData, path);
|
|
50
50
|
if (value && isString(value)) return StringPrototypeToLowerCase.call(value);
|
|
51
51
|
};
|
package/lib/config.js
CHANGED
|
@@ -12,13 +12,14 @@
|
|
|
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
|
+
// @ts-check
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const process = require('process');
|
|
19
19
|
const path = require('path');
|
|
20
20
|
const fs = require('fs');
|
|
21
21
|
const os = require('os');
|
|
22
|
+
const merge = require('deepmerge');
|
|
22
23
|
const yaml = require('yaml');
|
|
23
24
|
const { Event, get, set, primordials: { ArrayPrototypeJoin, StringPrototypeToUpperCase, JSONParse } } = require('@contrast/common');
|
|
24
25
|
const options = require('./options');
|
|
@@ -42,12 +43,14 @@ const OS_CONFIG_DIR = os.platform() === 'win32'
|
|
|
42
43
|
const REDACTED_KEYS = ['api.api_key', 'api.service_key', 'api.token'];
|
|
43
44
|
const OVERRIDABLE_SOURCES = [DEFAULT_VALUE, CONTRAST_UI];
|
|
44
45
|
|
|
46
|
+
// Overwrites the existing array values completely rather than concatenating them.
|
|
47
|
+
const arrayMerge = (target, source, options) => source;
|
|
45
48
|
const isValid = (opt) => opt !== undefined && opt !== null && opt !== '';
|
|
46
49
|
|
|
47
50
|
module.exports = class Config {
|
|
48
51
|
constructor(core) {
|
|
49
52
|
// internals
|
|
50
|
-
this.
|
|
53
|
+
this._filepaths = [];
|
|
51
54
|
this._errors = [];
|
|
52
55
|
this._effectiveMap = new Map();
|
|
53
56
|
this._status = '';
|
|
@@ -74,6 +77,7 @@ module.exports = class Config {
|
|
|
74
77
|
disabled_rules: ''
|
|
75
78
|
};
|
|
76
79
|
this.server = {};
|
|
80
|
+
this.preinstrument = false;
|
|
77
81
|
|
|
78
82
|
// initialize
|
|
79
83
|
this._build();
|
|
@@ -138,53 +142,65 @@ module.exports = class Config {
|
|
|
138
142
|
}
|
|
139
143
|
|
|
140
144
|
/**
|
|
141
|
-
* Returns the locations to search for configuration files
|
|
142
|
-
*
|
|
145
|
+
* Returns the locations to search for configuration files as an array of
|
|
146
|
+
* arrays where each inner array contains a set of files to be merged in order of precedence.
|
|
147
|
+
* Being a function allows us to stub these locations within tests.
|
|
143
148
|
*/
|
|
144
149
|
_configDirs() {
|
|
145
|
-
return [
|
|
146
|
-
process.cwd()
|
|
150
|
+
return [[
|
|
151
|
+
process.cwd()
|
|
152
|
+
], [
|
|
147
153
|
path.resolve(OS_CONFIG_DIR, 'node'),
|
|
148
154
|
OS_CONFIG_DIR,
|
|
155
|
+
], [
|
|
149
156
|
path.resolve(HOME_CONFIG_DIR, 'node'),
|
|
150
157
|
HOME_CONFIG_DIR,
|
|
151
|
-
];
|
|
158
|
+
]];
|
|
152
159
|
}
|
|
153
160
|
|
|
154
161
|
_initFile() {
|
|
155
162
|
let fileConfig = {};
|
|
156
163
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
164
|
+
if (process.env[CONTRAST_CONFIG_PATH]) {
|
|
165
|
+
// deliberately ignore /dev/null (linux) and \\.\\nul (windows)
|
|
166
|
+
if (process.env[CONTRAST_CONFIG_PATH] === os.devNull) return fileConfig;
|
|
167
|
+
|
|
168
|
+
this._filepaths = [process.env[CONTRAST_CONFIG_PATH]];
|
|
169
|
+
} else {
|
|
170
|
+
for (const dirs of this._configDirs()) {
|
|
171
|
+
for (const dir of dirs) {
|
|
172
|
+
const currentPath = path.resolve(dir, 'contrast_security.yaml');
|
|
173
|
+
if (fs.existsSync(currentPath)) {
|
|
174
|
+
this._filepaths.push(currentPath);
|
|
175
|
+
}
|
|
165
176
|
}
|
|
177
|
+
if (this._filepaths.length > 0) break;
|
|
166
178
|
}
|
|
167
179
|
}
|
|
168
180
|
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
// deliberately ignore /dev/null (linux) and \\.\\nul (windows)
|
|
172
|
-
if (_filepath && _filepath !== os.devNull) {
|
|
181
|
+
for (const filepath of this._filepaths) {
|
|
173
182
|
let fileContents;
|
|
174
183
|
|
|
175
184
|
try {
|
|
176
|
-
fileContents = fs.readFileSync(
|
|
185
|
+
fileContents = fs.readFileSync(filepath, 'utf-8');
|
|
177
186
|
} catch (e) {
|
|
178
|
-
const err = new Error(`Unable to read Contrast configuration file: '${
|
|
187
|
+
const err = new Error(`Unable to read Contrast configuration file: '${filepath}'`);
|
|
179
188
|
err.cause = e;
|
|
180
189
|
this._errors.push(err);
|
|
181
190
|
}
|
|
182
191
|
|
|
183
192
|
if (fileContents) {
|
|
184
193
|
try {
|
|
185
|
-
|
|
194
|
+
const yamlConfig = yaml.parse(fileContents, { prettyErrors: true });
|
|
195
|
+
|
|
196
|
+
if (yamlConfig.root) {
|
|
197
|
+
this._filepaths = [filepath];
|
|
198
|
+
return yamlConfig;
|
|
199
|
+
} else {
|
|
200
|
+
fileConfig = merge(yamlConfig, fileConfig, { arrayMerge });
|
|
201
|
+
}
|
|
186
202
|
} catch (e) {
|
|
187
|
-
const err = new Error(`Contrast configuration file is malformed: '${
|
|
203
|
+
const err = new Error(`Contrast configuration file is malformed: '${filepath}'`);
|
|
188
204
|
this._errors.push(err);
|
|
189
205
|
err.cause = e;
|
|
190
206
|
}
|
|
@@ -229,7 +245,11 @@ module.exports = class Config {
|
|
|
229
245
|
}
|
|
230
246
|
|
|
231
247
|
// this is not a common config value
|
|
232
|
-
this.setValue(
|
|
248
|
+
this.setValue(
|
|
249
|
+
'preinstrument',
|
|
250
|
+
!!process.env.CONTRAST_PREINSTRUMENT,
|
|
251
|
+
process.env.CONTRAST_PREINSTRUMENT ? ConfigSource.ENVIRONMENT_VARIABLE : ConfigSource.DEFAULT_VALUE,
|
|
252
|
+
);
|
|
233
253
|
}
|
|
234
254
|
|
|
235
255
|
_redact(name, value) {
|
|
@@ -247,7 +267,7 @@ module.exports = class Config {
|
|
|
247
267
|
}
|
|
248
268
|
}
|
|
249
269
|
|
|
250
|
-
getReport({ redact = true }) {
|
|
270
|
+
getReport({ redact = true, stringify = true } = {}) {
|
|
251
271
|
const report = {
|
|
252
272
|
report_create: new Date(),
|
|
253
273
|
config: {
|
|
@@ -257,10 +277,12 @@ module.exports = class Config {
|
|
|
257
277
|
const effective_config = [], environment_variable = [], contrast_ui = [];
|
|
258
278
|
|
|
259
279
|
Array.from(this._effectiveMap.values()).forEach((v) => {
|
|
260
|
-
let { value
|
|
280
|
+
let { value } = v;
|
|
261
281
|
if (value === null) return;
|
|
282
|
+
|
|
283
|
+
const { name, canonical_name, source } = v;
|
|
262
284
|
if (redact) value = this._redact(name, value);
|
|
263
|
-
value = String(value);
|
|
285
|
+
if (stringify) value = String(value);
|
|
264
286
|
|
|
265
287
|
effective_config.push({ value, name, canonical_name, source });
|
|
266
288
|
|
package/lib/index.d.ts
CHANGED
|
@@ -52,7 +52,7 @@ export interface ConfigOption<T> {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
export interface Config {
|
|
55
|
-
|
|
55
|
+
_filepaths: string[];
|
|
56
56
|
_effectiveMap: Map<string, EffectiveEntry<any>>;
|
|
57
57
|
_errors: Error[];
|
|
58
58
|
_status: string,
|
|
@@ -320,7 +320,7 @@ export interface Config {
|
|
|
320
320
|
};
|
|
321
321
|
getEffectiveSource(cannonicalName: string): string;
|
|
322
322
|
getEffectiveValue<T = any>(cannonicalName: string): T;
|
|
323
|
-
getReport({ redact
|
|
323
|
+
getReport(opts?: { redact?: boolean, stringify?: boolean }): any;
|
|
324
324
|
setValue<T = any>(name: string, value: T, source: string): void;
|
|
325
325
|
}
|
|
326
326
|
|
package/lib/options.js
CHANGED
|
@@ -23,6 +23,31 @@ const path = require('path');
|
|
|
23
23
|
const { Rule, primordials: { BufferFrom, BufferPrototypeToString, StringPrototypeReplace, StringPrototypeReplaceAll, StringPrototypeSplit, StringPrototypeToLowerCase, StringPrototypeToUpperCase, JSONParse } } = require('@contrast/common');
|
|
24
24
|
const { ConfigSource: { DEFAULT_VALUE } } = require('./common');
|
|
25
25
|
|
|
26
|
+
const CMD_IGNORE_LIST_DEFAULTS = [
|
|
27
|
+
'**/npm',
|
|
28
|
+
'**/npx',
|
|
29
|
+
'**/yarn',
|
|
30
|
+
'**/pnpm',
|
|
31
|
+
|
|
32
|
+
'**/.bin/next',
|
|
33
|
+
'**/node_modules/**/next/**',
|
|
34
|
+
'**/.bin/nuxi', // nuxt
|
|
35
|
+
'**/.bin/vite',
|
|
36
|
+
'**/.bin/astro',
|
|
37
|
+
'**/.bin/sv', // svelte
|
|
38
|
+
'**/.bin/webpack',
|
|
39
|
+
'**/node_modules/**/webpack-cli/**',
|
|
40
|
+
'**/.bin/rollup',
|
|
41
|
+
'**/.bin/parcel',
|
|
42
|
+
'**/@parcel/workers/**',
|
|
43
|
+
'**/.bin/babel',
|
|
44
|
+
'**/.bin/gulp',
|
|
45
|
+
'**/.bin/grunt',
|
|
46
|
+
|
|
47
|
+
'**/Visual Studio Code*/**',
|
|
48
|
+
'**/.vscode/**',
|
|
49
|
+
];
|
|
50
|
+
|
|
26
51
|
/**
|
|
27
52
|
* Takes strings "true"|"t" or "false"|"f" (case insensitive) and return the appropriate boolean.
|
|
28
53
|
* @param {boolean | string} value passed arg; never undefined or the function isn't called
|
|
@@ -160,7 +185,7 @@ const options = [
|
|
|
160
185
|
name: 'api.token',
|
|
161
186
|
arg: '<token>',
|
|
162
187
|
desc: 'base64 encoded JSON object containing the `url`, `api_key`, `service_key`, and `user_name` config options, allowing them all to be set in a single variable.',
|
|
163
|
-
fn(value, cfg, source) {
|
|
188
|
+
fn: (value, cfg, source) => {
|
|
164
189
|
try {
|
|
165
190
|
// parse the base64 encoded value
|
|
166
191
|
const parsed = JSONParse(BufferPrototypeToString.call(BufferFrom(value, 'base64'), 'utf8'));
|
|
@@ -194,25 +219,25 @@ const options = [
|
|
|
194
219
|
},
|
|
195
220
|
{
|
|
196
221
|
name: 'api.certificate.ca_file',
|
|
197
|
-
|
|
222
|
+
desc: 'Set the absolute or relative path to a CA for communication with the Contrast UI using a self-signed certificate.',
|
|
198
223
|
arg: '<path>',
|
|
199
224
|
fn: toAbsolutePath,
|
|
200
225
|
},
|
|
201
226
|
{
|
|
202
227
|
name: 'api.certificate.cert_file',
|
|
203
|
-
|
|
228
|
+
desc: 'Set the absolute or relative path to the Certificate PEM file for communication with the Contrast UI.',
|
|
204
229
|
arg: '<path>',
|
|
205
230
|
fn: toAbsolutePath,
|
|
206
231
|
},
|
|
207
232
|
{
|
|
208
233
|
name: 'api.certificate.key_file',
|
|
209
|
-
|
|
234
|
+
desc: 'Set the absolute or relative path to the Key PEM file for communication with the Contrast UI.',
|
|
210
235
|
arg: '<path>',
|
|
211
236
|
fn: toAbsolutePath,
|
|
212
237
|
},
|
|
213
238
|
{
|
|
214
239
|
name: 'api.certificate.ignore_cert_errors',
|
|
215
|
-
|
|
240
|
+
desc: 'When set to `true`, the agent ignores certificate verification errors when the agent communicates with the Contrast UI.',
|
|
216
241
|
arg: '[true]',
|
|
217
242
|
default: false,
|
|
218
243
|
},
|
|
@@ -470,14 +495,14 @@ Example - \`/opt/Contrast/contrast.log\` creates a log in the \`/opt/Contrast\`
|
|
|
470
495
|
name: 'agent.node.cmd_ignore_list',
|
|
471
496
|
arg: '<commands>',
|
|
472
497
|
default: '',
|
|
473
|
-
fn: (arg) => StringPrototypeSplit.call(arg,
|
|
474
|
-
desc:
|
|
498
|
+
fn: (arg) => [...CMD_IGNORE_LIST_DEFAULTS, ...StringPrototypeSplit.call(arg, /,(?![^{]*}|[^[]*\])/g).filter((v) => v)],
|
|
499
|
+
desc: "Comma-separated list of glob patterns that will prevent instrumentation when matched in node's arguments."
|
|
475
500
|
},
|
|
476
501
|
{
|
|
477
502
|
// SEE NODE-2886
|
|
478
503
|
name: 'agent.node.exclusive_entrypoint',
|
|
479
504
|
arg: '<entrypoint.js>',
|
|
480
|
-
desc: '
|
|
505
|
+
desc: 'An entrypoint for an application that, when specified, will force the agent to instrument and prevent the agent instrumenting anything else. Resolved relative to `app_root` if set or the current working directory otherwise.'
|
|
481
506
|
},
|
|
482
507
|
{
|
|
483
508
|
name: 'agent.node.rewrite.enable',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/config",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.51.0",
|
|
4
4
|
"description": "An API for discovering Contrast agent configuration data",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
|
|
@@ -14,14 +14,15 @@
|
|
|
14
14
|
"types": "lib/index.d.ts",
|
|
15
15
|
"engines": {
|
|
16
16
|
"npm": ">=6.13.7 <7 || >= 8.3.1",
|
|
17
|
-
"node": ">=
|
|
17
|
+
"node": ">= 18.7.0"
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
20
|
"test": "bash ../scripts/test.sh"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@contrast/common": "1.
|
|
24
|
-
"@contrast/core": "1.
|
|
23
|
+
"@contrast/common": "1.36.0",
|
|
24
|
+
"@contrast/core": "1.56.0",
|
|
25
|
+
"deepmerge": "^4.3.1",
|
|
25
26
|
"yaml": "^2.2.2"
|
|
26
27
|
}
|
|
27
28
|
}
|