@contrast/cli 1.54.0 → 1.56.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.
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * Copyright: 2025 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
+
17
+ import { program } from 'commander';
18
+ import path from 'node:path';
19
+ import { action, version } from '../lib/config-diagnostics.mjs';
20
+
21
+ program
22
+ .name('config-diagnostics')
23
+ .version(version)
24
+ .description('The config-diagnostics utility returns the current effective node agent configuration.')
25
+ .argument('<entrypoint>', 'The entrypoint JavaScript or ESM file for the application')
26
+ .option('-q, --quiet', 'suppress logging to stdout')
27
+ .option('-o, --output <string>', 'output directory for generated JSON file', path.join(process.cwd(), 'contrast_effective_config.json'))
28
+ .action(action)
29
+ .parse(process.argv);
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * Copyright: 2025 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
+
17
+ import { execFileSync } from 'node:child_process';
18
+ import { fileURLToPath } from 'node:url';
19
+
20
+ execFileSync(
21
+ process.argv[0],
22
+ [
23
+ '--experimental-import-meta-resolve',
24
+ fileURLToPath(new URL('../lib/rewrite/program.mjs', import.meta.url)),
25
+ ...process.argv.slice(2),
26
+ ],
27
+ { stdio: 'inherit' },
28
+ );
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * Copyright: 2025 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
+
17
+ import { program } from 'commander';
18
+ import path from 'node:path';
19
+ import { action, version } from '../lib/system-diagnostics.mjs';
20
+
21
+ program
22
+ .name('system-diagnostics')
23
+ .version(version)
24
+ .description('The system-diagnostics utility returns the system info for the server/container the agent is running on.')
25
+ .option('-q, --quiet', 'suppress logging to stdout')
26
+ .option('-o, --output <string>', 'output directory for generated JSON file', path.join(process.cwd(), 'contrast_system_info.json'))
27
+ .action(action)
28
+ .parse(process.argv);
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  /*
3
2
  * Copyright: 2025 Contrast Security, Inc
4
3
  * Contact: support@contrastsecurity.com
@@ -13,36 +12,34 @@
13
12
  * engineered, modified, repackaged, sold, redistributed or otherwise used in a
14
13
  * way not consistent with the End User License Agreement.
15
14
  */
16
- 'use strict';
17
15
 
18
- const commander = require('commander');
19
- const fs = require('fs');
20
- const { EOL } = require('os');
21
- const path = require('path');
22
- const { name: agentName, version: agentVersion } = require('../package.json');
23
- const { primordials: { JSONStringify } } = require('@contrast/common');
16
+ import { primordials } from '@contrast/common';
17
+ import configInit from '@contrast/config';
18
+ import appInfoInit from '@contrast/core/lib/app-info.js';
19
+ import coreMessagesInit from '@contrast/core/lib/messages.js';
20
+ import reporterInit from '@contrast/reporter';
21
+ import scopesInit from '@contrast/scopes';
22
+ import loggerInit from '@contrast/logger';
23
+ import Perf from '@contrast/perf';
24
+ import { readFileSync, writeFileSync } from 'node:fs';
25
+ import { EOL } from 'node:os';
24
26
 
25
- if (require.main === module) {
26
- commander.program
27
- .name('config-diagnostics')
28
- .description('The config-diagnostics utility returns the current effective node agent configuration.')
29
- .argument('<entrypoint>', 'The entrypoint JavaScript or ESM file for the application')
30
- .option('-q, --quiet', 'suppress logging to stdout')
31
- .option('-o, --output <string>', 'output directory for generated JSON file', path.join(process.cwd(), 'contrast_effective_config.json'))
32
- .action(action)
33
- .parse();
34
- }
27
+ const { JSONStringify } = primordials;
28
+ const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.meta.url)));
29
+ const { name: agentName, version: agentVersion } = packageJson;
30
+
31
+ export { agentVersion as version };
35
32
 
36
- async function action(entrypoint, options) {
37
- const core = { agentName, agentVersion, Perf: require('@contrast/perf') };
38
- require('@contrast/core/lib/messages')(core);
39
- require('@contrast/config')(core);
40
- require('@contrast/logger').default(core);
41
- require('@contrast/core/lib/app-info')(core);
33
+ export async function action(entrypoint, options) {
34
+ const core = { agentName, agentVersion, Perf };
35
+ coreMessagesInit(core);
36
+ configInit(core);
37
+ loggerInit.default(core);
38
+ appInfoInit(core);
42
39
  if (core.appInfo._errors?.length) throw core.appInfo._errors[0];
43
40
 
44
- require('@contrast/scopes')(core);
45
- require('@contrast/reporter').default(core, { reporters: ['ui'] });
41
+ scopesInit(core);
42
+ reporterInit.default(core, { reporters: ['ui'] });
46
43
 
47
44
  for (const err of core.config._errors) {
48
45
  throw err;
@@ -62,11 +59,9 @@ async function action(entrypoint, options) {
62
59
  const content = JSONStringify({ ...core.config.getReport({ redact: true }), Status }, null, 2) + EOL;
63
60
 
64
61
  if (!options.quiet) {
65
- fs.writeFileSync(1, content, 'utf8');
62
+ writeFileSync(process.stdout.fd, content, 'utf8');
66
63
  }
67
64
  if (options.output) {
68
- fs.writeFileSync(options.output, content, 'utf-8');
65
+ writeFileSync(options.output, content, 'utf-8');
69
66
  }
70
67
  }
71
-
72
- module.exports.action = action;
@@ -0,0 +1,187 @@
1
+ /*
2
+ * Copyright: 2025 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
+ // @ts-check
16
+ import { primordials } from '@contrast/common';
17
+ import configInit from '@contrast/config';
18
+ import appInfoInit from '@contrast/core/lib/app-info.js';
19
+ import coreMessagesInit from '@contrast/core/lib/messages.js';
20
+ import loggerInit from '@contrast/logger';
21
+ import Perf from '@contrast/perf';
22
+ import rewriterInit from '@contrast/rewriter';
23
+ import { rewriteIsDeadzoned } from '@contrast/rewriter/lib/rewrite-is-deadzoned.js';
24
+ import swc from '@swc/core';
25
+ import { Visitor } from '@swc/core/Visitor.js';
26
+ import { readFile } from 'node:fs/promises';
27
+ import { createRequire } from 'node:module';
28
+ import path from 'node:path';
29
+ import { fileURLToPath, pathToFileURL } from 'node:url';
30
+
31
+ const { RegExpPrototypeTest } = primordials;
32
+ const JS_FILE_REGEX = /\.[cm]?js$/;
33
+
34
+ const packageJson = JSON.parse((await readFile(new URL('../../package.json', import.meta.url))).toString());
35
+ const { name: agentName, version: agentVersion } = packageJson;
36
+
37
+ /** @type {any} */
38
+ const core = { agentName, agentVersion, Perf };
39
+ coreMessagesInit(core);
40
+ const config = configInit(core);
41
+ if (config._errors?.length) throw config._errors[0];
42
+ // @ts-expect-error ESM interop doesn't handle this `default` very well.
43
+ const logger = loggerInit.default(core, { name: 'contrast:rewriter:cli' });
44
+
45
+ if (!config.agent.node.rewrite.enable || !config.agent.node.rewrite.cache.enable) {
46
+ logger.warn({ 'config.agent.node.rewrite': config.agent.node.rewrite }, 'rewriter config');
47
+ throw new Error('Rewriting disabled.');
48
+ }
49
+
50
+ const appInfo = appInfoInit(core);
51
+ if (appInfo._errors?.length) throw appInfo._errors[0];
52
+
53
+ const rewriter = rewriterInit(core);
54
+
55
+ /**
56
+ * Keeps track of visited files so we don't bother rewriting multiple times.
57
+ * @type {Set<string>}
58
+ */
59
+ const visited = new Set();
60
+
61
+ class RewriteVisitor extends Visitor {
62
+ /** @param {string} filename */
63
+ constructor(filename) {
64
+ super();
65
+ visited.add(filename);
66
+ this.parentUrl = pathToFileURL(filename);
67
+ this.require = createRequire(filename);
68
+ }
69
+
70
+ /**
71
+ * Visit the argument of an import declaration or `import()` function to
72
+ * rewrite the arg if it's a valid (i.e., absolute) path.
73
+ * @param {string} source
74
+ */
75
+ visitImportSource(source) {
76
+ try {
77
+ const filename = fileURLToPath(import.meta.resolve(source, this.parentUrl));
78
+ if (path.isAbsolute(filename)) {
79
+ rewriteFile(filename);
80
+ }
81
+ } catch (err) {
82
+ logger.error({ err }, 'unable to resolve %s', source);
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Visit `import ... from 'source'`, recursively rewriting the resolved path
88
+ * of `source`.
89
+ * @param {swc.ImportDeclaration} n
90
+ */
91
+ visitImportDeclaration(n) {
92
+ this.visitImportSource(n.source.value);
93
+ return super.visitImportDeclaration(n);
94
+ }
95
+
96
+ /**
97
+ * Visit the argument of a `require()` call to rewrite the arg if it's a valid
98
+ * (i.e., absolute) path.
99
+ * @param {string} arg
100
+ */
101
+ visitRequireArg(arg) {
102
+ try {
103
+ const filename = this.require.resolve(arg);
104
+ if (path.isAbsolute(filename)) {
105
+ rewriteFile(filename);
106
+ }
107
+ } catch (err) {
108
+ logger.error({ err }, 'unable to resolve %s', arg);
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Visits `import(...)` or `require(...)` call expressions, recursively
114
+ * rewriting the resolved path of the first argument if it's a string literal.
115
+ * @param {swc.CallExpression} n
116
+ */
117
+ visitCallExpression(n) {
118
+ if (n.callee.type === 'Import') {
119
+ const { expression } = n.arguments[0];
120
+ if (expression.type === 'StringLiteral') {
121
+ this.visitImportSource(expression.value);
122
+ }
123
+ } else if (n.callee.type === 'Identifier' && n.callee.value === 'require') {
124
+ const { expression } = n.arguments[0];
125
+ if (expression.type === 'StringLiteral') {
126
+ this.visitRequireArg(expression.value);
127
+ }
128
+ }
129
+
130
+ return super.visitCallExpression(n);
131
+ }
132
+ }
133
+
134
+ /** @param {string} filename */
135
+ function shouldSkipFile(filename) {
136
+ return (
137
+ !RegExpPrototypeTest.call(JS_FILE_REGEX, filename) ||
138
+ visited.has(filename) ||
139
+ !!rewriteIsDeadzoned(filename)
140
+ );
141
+ }
142
+
143
+ /** @param {string} filename */
144
+ async function rewriteFile(filename) {
145
+ if (shouldSkipFile(filename)) return;
146
+
147
+ try {
148
+ const content = (await readFile(filename)).toString();
149
+
150
+ const result = await rewriter.rewrite(content, {
151
+ filename,
152
+ inject: true,
153
+ minify: true,
154
+ });
155
+ rewriter.cache.write(filename, result);
156
+
157
+ /** @type {swc.Module | swc.Script} */
158
+ const program = await swc.parse(content);
159
+ const visitor = new RewriteVisitor(filename);
160
+ visitor.visitProgram(program);
161
+ } catch (err) {
162
+ logger.error({ err }, 'unable to parse or rewrite %s', filename);
163
+ }
164
+ }
165
+
166
+ export { agentVersion as version };
167
+
168
+ /**
169
+ * @param {string} filename
170
+ * @param {object} opts
171
+ * @param {boolean=} opts.assess
172
+ * @param {boolean=} opts.protect
173
+ */
174
+ export async function action(filename, opts) {
175
+ if (!opts.protect && (config.assess.enable || opts.assess)) {
176
+ rewriter.install('assess');
177
+ } else {
178
+ rewriter.install('protect');
179
+ }
180
+
181
+ logger.info(
182
+ 'Caching rewriter results to %s',
183
+ path.join(rewriter.cache.cacheDir, rewriter.modes.has('assess') ? 'assess' : 'protect'),
184
+ );
185
+ logger.debug({ config }, 'Agent configuration');
186
+ return rewriteFile(filename);
187
+ }
@@ -0,0 +1,28 @@
1
+ /*
2
+ * Copyright: 2025 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 { program } from 'commander';
17
+ import path from 'node:path';
18
+ import { action, version } from './index.mjs';
19
+
20
+ program
21
+ .name('npx -p @contrast/cli rewrite')
22
+ .version(version)
23
+ .description('Rewrites application files, caching them so that rewriting does not need to occur when the application runs.')
24
+ .argument('<entrypoint>', 'The entrypoint for the application', entrypoint => path.resolve(entrypoint))
25
+ .option('-a, --assess', 'rewrite in assess mode')
26
+ .option('-p, --protect', 'rewrite in protect mode')
27
+ .action(action)
28
+ .parse(process.argv);
@@ -0,0 +1,51 @@
1
+ /*
2
+ * Copyright: 2025 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 { primordials } from '@contrast/common';
17
+ import configInit from '@contrast/config';
18
+ import appInfoInit from '@contrast/core/lib/app-info.js';
19
+ import systemInfoInit from '@contrast/core/lib/system-info/index.js';
20
+ import loggerInit from '@contrast/logger';
21
+ import Perf from '@contrast/perf';
22
+ import { readFileSync, writeFileSync } from 'node:fs';
23
+ import { EOL } from 'node:os';
24
+
25
+ const { JSONStringify } = primordials;
26
+ const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
27
+ const { name: agentName, version: agentVersion } = packageJson;
28
+
29
+ export { agentVersion as version };
30
+
31
+ export async function action(options) {
32
+ const core = { agentName, agentVersion, Perf };
33
+ configInit(core);
34
+ loggerInit.default(core, { level: 'silent' });
35
+ appInfoInit(core);
36
+ systemInfoInit(core);
37
+
38
+ for (const err of core.config._errors) {
39
+ throw err;
40
+ }
41
+
42
+ const systemInfo = await core.getSystemInfo();
43
+ const content = JSONStringify(systemInfo, null, 2) + EOL;
44
+
45
+ if (!options.quiet) {
46
+ writeFileSync(process.stdout.fd, content, 'utf8');
47
+ }
48
+ if (options.output) {
49
+ writeFileSync(options.output, content, 'utf-8');
50
+ }
51
+ }
package/package.json CHANGED
@@ -1,19 +1,21 @@
1
1
  {
2
2
  "name": "@contrast/cli",
3
- "version": "1.54.0",
3
+ "version": "1.56.0",
4
+ "type": "module",
4
5
  "description": "A collection of agent related CLI utilities",
5
6
  "license": "SEE LICENSE IN LICENSE",
6
7
  "author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
7
8
  "files": [
9
+ "bin/",
8
10
  "lib/",
9
11
  "!*.test.*",
10
12
  "!tsconfig.*",
11
13
  "!*.map"
12
14
  ],
13
15
  "bin": {
14
- "config-diagnostics": "lib/config-diagnostics.js",
15
- "rewrite": "lib/rewrite.js",
16
- "system-diagnostics": "lib/system-diagnostics.js"
16
+ "config-diagnostics": "bin/config-diagnostics.mjs",
17
+ "rewrite": "bin/rewrite.mjs",
18
+ "system-diagnostics": "bin/system-diagnostics.mjs"
17
19
  },
18
20
  "engines": {
19
21
  "npm": ">=6.13.7 <7 || >= 8.3.1",
@@ -24,14 +26,14 @@
24
26
  },
25
27
  "dependencies": {
26
28
  "@contrast/find-package-json": "^1.1.0",
27
- "@contrast/rewriter": "1.32.0",
28
- "@contrast/common": "1.36.0",
29
- "@contrast/config": "1.51.0",
30
- "@contrast/core": "1.56.0",
31
- "@contrast/logger": "1.29.0",
29
+ "@contrast/rewriter": "1.34.0",
30
+ "@contrast/common": "1.37.0",
31
+ "@contrast/config": "1.52.1",
32
+ "@contrast/core": "1.57.1",
33
+ "@contrast/logger": "1.30.1",
32
34
  "@contrast/perf": "1.4.0",
33
- "@contrast/reporter": "1.54.0",
34
- "@contrast/scopes": "1.26.0",
35
+ "@contrast/reporter": "1.55.1",
36
+ "@contrast/scopes": "1.27.1",
35
37
  "@swc/core": "1.13.3",
36
38
  "commander": "^9.4.1"
37
39
  }
package/lib/rewrite.js DELETED
@@ -1,206 +0,0 @@
1
- #!/usr/bin/env node
2
- /*
3
- * Copyright: 2025 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
- // @ts-check
17
- 'use strict';
18
-
19
- const { readFile } = require('node:fs/promises');
20
- const { createRequire } = require('node:module');
21
- const path = require('node:path');
22
- const { program } = require('commander');
23
- const swc = require('@swc/core');
24
- const { Visitor } = require('@swc/core/Visitor');
25
- const { primordials: { RegExpPrototypeTest, JSONParse } } = require('@contrast/common');
26
- const { findPackageJson } = require('@contrast/find-package-json');
27
- const { rewriteIsDeadzoned } = require('@contrast/rewriter/lib/rewrite-is-deadzoned');
28
- const { version } = require('../package.json');
29
-
30
- const JS_FILE_REGEX = /\.[cm]?js$/;
31
-
32
- /** @type {any} */
33
- const core = { Perf: require('@contrast/perf') };
34
- require('@contrast/core/lib/messages')(core);
35
- const config = require('@contrast/config')(core);
36
- if (core.config._errors?.length) throw core.config._errors[0];
37
-
38
- const logger = require('@contrast/logger').default(core, { name: 'contrast:rewriter:cli' });
39
-
40
- if (!config.agent.node.rewrite.enable || !config.agent.node.rewrite.cache.enable) {
41
- logger.warn({ 'config.agent.node.rewrite': config.agent.node.rewrite }, 'rewriter config');
42
- throw new Error('Rewriting disabled.');
43
- }
44
-
45
- const appInfo = require('@contrast/core/lib/app-info')(core);
46
- if (appInfo._errors?.length) throw appInfo._errors[0];
47
-
48
- const rewriter = require('@contrast/rewriter')(core);
49
-
50
- /**
51
- * Keeps track of visited files so we don't bother rewriting multiple times.
52
- * @type {Set<string>}
53
- */
54
- const visited = new Set();
55
-
56
- /** @param {string} filename absolute path */
57
- async function isModuleFile(filename) {
58
- // if the file extension specifies the type, there's no need to do extra IO
59
- const ext = path.extname(filename);
60
- if (ext === '.mjs') {
61
- return true;
62
- }
63
-
64
- if (ext === '.cjs') {
65
- return false;
66
- }
67
-
68
- // check for type: 'module' in a file's package json, otherwise assume CJS.
69
- try {
70
- const pkg = await findPackageJson({ cwd: filename });
71
- if (pkg && JSONParse((await readFile(pkg)).toString()).type === 'module') {
72
- return true;
73
- }
74
- } catch {
75
- return false;
76
- }
77
-
78
- return false;
79
- }
80
-
81
- class RewriteVisitor extends Visitor {
82
- /** @param {string} filename */
83
- constructor(filename) {
84
- super();
85
- visited.add(filename);
86
-
87
- /**
88
- * Create a local `require` function so we can resolve relative filenames.
89
- * @type {NodeRequire}
90
- */
91
- this.require = createRequire(filename);
92
- }
93
-
94
- /**
95
- * Visit the String Literal argument of either `import` or `require`, trying
96
- * to rewrite the arg if it's a valid (i.e., absolute) path.
97
- * @param {swc.StringLiteral} n
98
- */
99
- visitImportOrRequireString(n) {
100
- try {
101
- const filename = this.require.resolve(n.value);
102
- if (path.isAbsolute(filename)) {
103
- rewriteFile(filename);
104
- }
105
- } catch (err) {
106
- logger.debug({ n, err }, 'unable to resolve %s', n.value);
107
- }
108
- }
109
-
110
- /**
111
- * Visit `import ... from 'source'`, recursively rewriting the resolved path
112
- * of `source`.
113
- * @param {swc.ImportDeclaration} n
114
- */
115
- visitImportDeclaration(n) {
116
- this.visitImportOrRequireString(n.source);
117
- return super.visitImportDeclaration(n);
118
- }
119
-
120
- /**
121
- * Visits `import(...)` or `require(...)` call expressions, recursively
122
- * rewriting the resolved path of the first argument if it's a string literal.
123
- * @param {swc.CallExpression} n
124
- */
125
- visitCallExpression(n) {
126
- if (n.callee.type === 'Import' || (n.callee.type === 'Identifier' && n.callee.value === 'require')) {
127
- const { expression } = n.arguments[0];
128
- if (expression.type === 'StringLiteral') {
129
- this.visitImportOrRequireString(expression);
130
- }
131
- }
132
-
133
- return super.visitCallExpression(n);
134
- }
135
- }
136
-
137
- /** @param {string} filename */
138
- async function rewriteFile(filename) {
139
- if (
140
- !RegExpPrototypeTest.call(JS_FILE_REGEX, filename) ||
141
- visited.has(filename) ||
142
- rewriteIsDeadzoned(filename)
143
- ) return;
144
-
145
- try {
146
- const content = (await readFile(filename)).toString();
147
- const isModule = await isModuleFile(filename);
148
-
149
- const result = await rewriter.rewrite(content, {
150
- filename,
151
- isModule,
152
- inject: true,
153
- wrap: !isModule,
154
- minify: true,
155
- });
156
- rewriter.cache.write(filename, result);
157
-
158
- /** @type {swc.Module | swc.Script} */
159
- const program = await swc.parse(content, {
160
- // @ts-expect-error swc types expect a literal, not an arbitrary boolean.
161
- isModule
162
- });
163
- const visitor = new RewriteVisitor(filename);
164
- visitor.visitProgram(program);
165
- } catch (err) {
166
- logger.debug({ err }, 'unable to parse or rewrite %s', filename);
167
- }
168
- }
169
-
170
- /**
171
- * @param {string} filename
172
- * @param {object} opts
173
- * @param {boolean=} opts.assess
174
- * @param {boolean=} opts.protect
175
- */
176
- async function action(filename, opts) {
177
- if (!opts.protect && (config.assess.enable || opts.assess)) {
178
- rewriter.install('assess');
179
- } else {
180
- rewriter.install('protect');
181
- }
182
-
183
- logger.info(
184
- 'Caching rewriter results to %s',
185
- path.join(
186
- rewriter.cache.cacheDir,
187
- rewriter.modes.has('assess') ? 'assess' : 'protect'
188
- ),
189
- );
190
- logger.debug({ config }, 'Agent configuration');
191
- return rewriteFile(filename);
192
- }
193
-
194
- if (require.main === module) {
195
- program
196
- .name('npx -p @contrast/cli rewrite')
197
- .version(version)
198
- .description('Rewrites application files, caching them so that rewriting does not need to occur when the application runs.')
199
- .argument('<entrypoint>', 'The entrypoint for the application', entrypoint => path.resolve(entrypoint))
200
- .option('-a, --assess', 'rewrite in assess mode')
201
- .option('-p, --protect', 'rewrite in protect mode')
202
- .action(action)
203
- .parse(process.argv);
204
- }
205
-
206
- module.exports.action = action;
@@ -1,57 +0,0 @@
1
- #!/usr/bin/env node
2
- /*
3
- * Copyright: 2025 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 commander = require('commander');
19
- const fs = require('fs');
20
- const { EOL } = require('os');
21
- const path = require('path');
22
- const { name: agentName, version: agentVersion } = require('../package.json');
23
- const { primordials: { JSONStringify } } = require('@contrast/common');
24
-
25
- if (require.main === module) {
26
- commander.program
27
- .name('system-diagnostics')
28
- .description('The system-diagnostics utility returns the system info for the server/container the agent is running on.')
29
- .option('-q, --quiet', 'suppress logging to stdout')
30
- .option('-o, --output <string>', 'output directory for generated JSON file', path.join(process.cwd(), 'contrast_system_info.json'))
31
- .action(action)
32
- .parse();
33
- }
34
-
35
- async function action(options) {
36
- const core = { agentName, agentVersion, Perf: require('@contrast/perf') };
37
- require('@contrast/config')(core);
38
- require('@contrast/logger').default(core, { level: 'silent' });
39
- require('@contrast/core/lib/app-info')(core);
40
- require('@contrast/core/lib/system-info')(core);
41
-
42
- for (const err of core.config._errors) {
43
- throw err;
44
- }
45
-
46
- const systemInfo = await core.getSystemInfo();
47
- const content = JSONStringify(systemInfo, null, 2) + EOL;
48
-
49
- if (!options.quiet) {
50
- fs.writeFileSync(1, content, 'utf8');
51
- }
52
- if (options.output) {
53
- fs.writeFileSync(options.output, content, 'utf-8');
54
- }
55
- }
56
-
57
- module.exports.action = action;