@cyclonedx/cdxgen 12.1.4 → 12.2.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 +47 -39
- package/bin/cdxgen.js +181 -90
- package/bin/evinse.js +4 -4
- package/bin/repl.js +3 -3
- package/bin/sign.js +102 -0
- package/bin/validate.js +233 -0
- package/bin/verify.js +69 -28
- package/data/queries.json +1 -1
- package/data/rules/ci-permissions.yaml +186 -0
- package/data/rules/dependency-sources.yaml +123 -0
- package/data/rules/package-integrity.yaml +135 -0
- package/data/rules/vscode-extensions.yaml +228 -0
- package/lib/cli/index.js +484 -440
- package/lib/evinser/db.js +137 -0
- package/lib/{helpers → evinser}/db.poku.js +2 -6
- package/lib/evinser/evinser.js +5 -18
- package/lib/evinser/swiftsem.js +1 -1
- package/lib/helpers/bomSigner.js +312 -0
- package/lib/helpers/bomSigner.poku.js +156 -0
- package/lib/helpers/caxa.js +1 -1
- package/lib/helpers/ciParsers/azurePipelines.js +295 -0
- package/lib/helpers/ciParsers/azurePipelines.poku.js +253 -0
- package/lib/helpers/ciParsers/circleCi.js +286 -0
- package/lib/helpers/ciParsers/circleCi.poku.js +230 -0
- package/lib/helpers/ciParsers/common.js +24 -0
- package/lib/helpers/ciParsers/githubActions.js +636 -0
- package/lib/helpers/ciParsers/githubActions.poku.js +802 -0
- package/lib/helpers/ciParsers/gitlabCi.js +213 -0
- package/lib/helpers/ciParsers/gitlabCi.poku.js +247 -0
- package/lib/helpers/ciParsers/jenkins.js +181 -0
- package/lib/helpers/ciParsers/jenkins.poku.js +197 -0
- package/lib/helpers/depsUtils.js +203 -0
- package/lib/helpers/depsUtils.poku.js +150 -0
- package/lib/helpers/display.js +429 -14
- package/lib/helpers/envcontext.js +23 -8
- package/lib/helpers/formulationParsers.js +351 -0
- package/lib/helpers/logger.js +14 -0
- package/lib/helpers/protobom.js +9 -9
- package/lib/helpers/pythonutils.js +305 -0
- package/lib/helpers/pythonutils.poku.js +469 -0
- package/lib/helpers/utils.js +970 -528
- package/lib/helpers/utils.poku.js +139 -256
- package/lib/helpers/versutils.js +202 -0
- package/lib/helpers/versutils.poku.js +315 -0
- package/lib/helpers/vsixutils.js +1061 -0
- package/lib/helpers/vsixutils.poku.js +2247 -0
- package/lib/managers/binary.js +19 -19
- package/lib/managers/docker.js +108 -1
- package/lib/managers/oci.js +10 -0
- package/lib/managers/piptree.js +4 -10
- package/lib/parsers/npmrc.js +92 -0
- package/lib/parsers/npmrc.poku.js +528 -0
- package/lib/server/openapi.yaml +1 -10
- package/lib/server/server.js +58 -16
- package/lib/server/server.poku.js +123 -144
- package/lib/stages/postgen/annotator.js +1 -1
- package/lib/stages/postgen/auditBom.js +197 -0
- package/lib/stages/postgen/auditBom.poku.js +378 -0
- package/lib/stages/postgen/postgen.js +54 -1
- package/lib/stages/postgen/postgen.poku.js +90 -1
- package/lib/stages/postgen/ruleEngine.js +369 -0
- package/lib/stages/pregen/envAudit.js +299 -0
- package/lib/stages/pregen/envAudit.poku.js +572 -0
- package/lib/stages/pregen/pregen.js +12 -8
- package/lib/third-party/arborist/lib/deepest-nesting-target.js +1 -1
- package/lib/third-party/arborist/lib/node.js +3 -3
- package/lib/third-party/arborist/lib/shrinkwrap.js +1 -1
- package/lib/third-party/arborist/lib/tree-check.js +1 -1
- package/lib/{helpers/validator.js → validator/bomValidator.js} +107 -47
- package/lib/validator/complianceEngine.js +241 -0
- package/lib/validator/complianceEngine.poku.js +168 -0
- package/lib/validator/complianceRules.js +1610 -0
- package/lib/validator/complianceRules.poku.js +328 -0
- package/lib/validator/index.js +222 -0
- package/lib/validator/index.poku.js +144 -0
- package/lib/validator/reporters/annotations.js +121 -0
- package/lib/validator/reporters/console.js +149 -0
- package/lib/validator/reporters/index.js +41 -0
- package/lib/validator/reporters/json.js +37 -0
- package/lib/validator/reporters/sarif.js +184 -0
- package/lib/validator/reporters.poku.js +150 -0
- package/package.json +8 -8
- package/types/bin/sign.d.ts +3 -0
- package/types/bin/sign.d.ts.map +1 -0
- package/types/bin/validate.d.ts +3 -0
- package/types/bin/validate.d.ts.map +1 -0
- package/types/helpers/utils.d.ts +0 -1
- package/types/lib/cli/index.d.ts +49 -52
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/evinser/db.d.ts +34 -0
- package/types/lib/evinser/db.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +63 -16
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +27 -0
- package/types/lib/helpers/bomSigner.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/azurePipelines.d.ts +17 -0
- package/types/lib/helpers/ciParsers/azurePipelines.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/circleCi.d.ts +17 -0
- package/types/lib/helpers/ciParsers/circleCi.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/common.d.ts +11 -0
- package/types/lib/helpers/ciParsers/common.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts +34 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/gitlabCi.d.ts +17 -0
- package/types/lib/helpers/ciParsers/gitlabCi.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/jenkins.d.ts +17 -0
- package/types/lib/helpers/ciParsers/jenkins.d.ts.map +1 -0
- package/types/lib/helpers/depsUtils.d.ts +21 -0
- package/types/lib/helpers/depsUtils.d.ts.map +1 -0
- package/types/lib/helpers/display.d.ts +111 -11
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/envcontext.d.ts +19 -7
- package/types/lib/helpers/envcontext.d.ts.map +1 -1
- package/types/lib/helpers/formulationParsers.d.ts +50 -0
- package/types/lib/helpers/formulationParsers.d.ts.map +1 -0
- package/types/lib/helpers/logger.d.ts +15 -1
- package/types/lib/helpers/logger.d.ts.map +1 -1
- package/types/lib/helpers/protobom.d.ts +2 -2
- package/types/lib/helpers/pythonutils.d.ts +18 -0
- package/types/lib/helpers/pythonutils.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts +532 -128
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/helpers/versutils.d.ts +8 -0
- package/types/lib/helpers/versutils.d.ts.map +1 -0
- package/types/lib/helpers/vsixutils.d.ts +130 -0
- package/types/lib/helpers/vsixutils.d.ts.map +1 -0
- package/types/lib/managers/docker.d.ts +12 -31
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +11 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/managers/piptree.d.ts.map +1 -1
- package/types/lib/parsers/npmrc.d.ts +26 -0
- package/types/lib/parsers/npmrc.d.ts.map +1 -0
- package/types/lib/server/server.d.ts +21 -2
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts +20 -0
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -0
- package/types/lib/stages/postgen/postgen.d.ts +8 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts +18 -0
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -0
- package/types/lib/stages/pregen/envAudit.d.ts +8 -0
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -0
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/{helpers/validator.d.ts → validator/bomValidator.d.ts} +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -0
- package/types/lib/validator/complianceEngine.d.ts +66 -0
- package/types/lib/validator/complianceEngine.d.ts.map +1 -0
- package/types/lib/validator/complianceRules.d.ts +70 -0
- package/types/lib/validator/complianceRules.d.ts.map +1 -0
- package/types/lib/validator/index.d.ts +70 -0
- package/types/lib/validator/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/annotations.d.ts +31 -0
- package/types/lib/validator/reporters/annotations.d.ts.map +1 -0
- package/types/lib/validator/reporters/console.d.ts +30 -0
- package/types/lib/validator/reporters/console.d.ts.map +1 -0
- package/types/lib/validator/reporters/index.d.ts +21 -0
- package/types/lib/validator/reporters/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/json.d.ts +11 -0
- package/types/lib/validator/reporters/json.d.ts.map +1 -0
- package/types/lib/validator/reporters/sarif.d.ts +16 -0
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -0
- package/lib/helpers/db.js +0 -162
- package/types/helpers/db.d.ts +0 -35
- package/types/helpers/db.d.ts.map +0 -1
- package/types/lib/helpers/db.d.ts +0 -35
- package/types/lib/helpers/db.d.ts.map +0 -1
- package/types/lib/helpers/validator.d.ts.map +0 -1
- package/types/managers/binary.d.ts +0 -37
- package/types/managers/binary.d.ts.map +0 -1
- package/types/managers/docker.d.ts +0 -56
- package/types/managers/docker.d.ts.map +0 -1
- package/types/managers/oci.d.ts +0 -2
- package/types/managers/oci.d.ts.map +0 -1
- package/types/managers/piptree.d.ts +0 -2
- package/types/managers/piptree.d.ts.map +0 -1
- package/types/server/server.d.ts +0 -34
- package/types/server/server.d.ts.map +0 -1
- package/types/stages/postgen/annotator.d.ts +0 -27
- package/types/stages/postgen/annotator.d.ts.map +0 -1
- package/types/stages/postgen/postgen.d.ts +0 -51
- package/types/stages/postgen/postgen.d.ts.map +0 -1
- package/types/stages/pregen/pregen.d.ts +0 -59
- package/types/stages/pregen/pregen.d.ts.map +0 -1
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
import { strict as assert } from "node:assert";
|
|
2
|
+
|
|
3
|
+
import { afterEach, describe, test } from "poku";
|
|
4
|
+
|
|
5
|
+
import { auditEnvironment } from "./envAudit.js";
|
|
6
|
+
|
|
7
|
+
const NODE_OPTIONS_ATTACK_VECTORS = [
|
|
8
|
+
{
|
|
9
|
+
name: "--require flag",
|
|
10
|
+
value: "--require ./evil.js",
|
|
11
|
+
expectedMatch: true,
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: "--require with uppercase",
|
|
15
|
+
value: "--REQUIRE ./evil.js",
|
|
16
|
+
expectedMatch: true,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
// -r alone is ambiguous and not matched to avoid false-positives from legitimate short opts
|
|
20
|
+
name: "-r short flag (not matched)",
|
|
21
|
+
value: "-r ./evil.js",
|
|
22
|
+
expectedMatch: false,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "--eval flag",
|
|
26
|
+
value: "--eval \"console.log('pwned')\"",
|
|
27
|
+
expectedMatch: true,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: "--eval with complex payload",
|
|
31
|
+
value: "--eval \"require('child_process').execSync('id')\"",
|
|
32
|
+
expectedMatch: true,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
// -e alone is not matched to avoid false-positives
|
|
36
|
+
name: "-e short flag (not matched)",
|
|
37
|
+
value: "-e \"console.log('test')\"",
|
|
38
|
+
expectedMatch: false,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: "--import flag (Node 18+)",
|
|
42
|
+
value: "--import ./malicious.mjs",
|
|
43
|
+
expectedMatch: true,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: "--loader flag",
|
|
47
|
+
value: "--loader ./hook-loader.js",
|
|
48
|
+
expectedMatch: true,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: "--inspect flag",
|
|
52
|
+
value: "--inspect=0.0.0.0:9229",
|
|
53
|
+
expectedMatch: true,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "--inspect-brk flag",
|
|
57
|
+
value: "--inspect-brk=9229",
|
|
58
|
+
expectedMatch: true,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: "--inspect with host",
|
|
62
|
+
value: "--inspect 127.0.0.1:9229",
|
|
63
|
+
expectedMatch: true,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
// --test runs the built-in test runner and is not an exploit vector
|
|
67
|
+
name: "--test flag (safe, not matched)",
|
|
68
|
+
value: "--test",
|
|
69
|
+
expectedMatch: false,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "safe memory flag",
|
|
73
|
+
value: "--max-old-space-size=4096",
|
|
74
|
+
expectedMatch: false,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "safe GC flag",
|
|
78
|
+
value: "--expose-gc",
|
|
79
|
+
expectedMatch: false,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "safe trace flag",
|
|
83
|
+
value: "--trace-warnings",
|
|
84
|
+
expectedMatch: false,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "multiple flags with one malicious",
|
|
88
|
+
value: "--max-old-space-size=4096 --require ./evil.js",
|
|
89
|
+
expectedMatch: true,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: "empty string",
|
|
93
|
+
value: "",
|
|
94
|
+
expectedMatch: false,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: "whitespace only",
|
|
98
|
+
value: " ",
|
|
99
|
+
expectedMatch: false,
|
|
100
|
+
},
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
const DANGEROUS_ENV_VAR_CASES = [
|
|
104
|
+
{
|
|
105
|
+
name: "NODE_NO_WARNINGS set",
|
|
106
|
+
env: { NODE_NO_WARNINGS: "1" },
|
|
107
|
+
expectedWarnings: 1,
|
|
108
|
+
expectedVar: "NODE_NO_WARNINGS",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: "NODE_PENDING_DEPRECATION set",
|
|
112
|
+
env: { NODE_PENDING_DEPRECATION: "1" },
|
|
113
|
+
expectedWarnings: 1,
|
|
114
|
+
expectedVar: "NODE_PENDING_DEPRECATION",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: "UV_THREADPOOL_SIZE set",
|
|
118
|
+
env: { UV_THREADPOOL_SIZE: "128" },
|
|
119
|
+
expectedWarnings: 1,
|
|
120
|
+
expectedVar: "UV_THREADPOOL_SIZE",
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: "all dangerous vars set",
|
|
124
|
+
env: {
|
|
125
|
+
NODE_NO_WARNINGS: "1",
|
|
126
|
+
NODE_PENDING_DEPRECATION: "1",
|
|
127
|
+
UV_THREADPOOL_SIZE: "128",
|
|
128
|
+
},
|
|
129
|
+
expectedWarnings: 3,
|
|
130
|
+
expectedVar: null,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: "no dangerous vars",
|
|
134
|
+
env: { PATH: "/usr/bin", HOME: "/home/user" },
|
|
135
|
+
expectedWarnings: 0,
|
|
136
|
+
expectedVar: null,
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: "dangerous var with empty value (falsy)",
|
|
140
|
+
env: { NODE_NO_WARNINGS: "" },
|
|
141
|
+
expectedWarnings: 0,
|
|
142
|
+
expectedVar: null,
|
|
143
|
+
},
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
const COMBINED_ATTACK_CASES = [
|
|
147
|
+
{
|
|
148
|
+
name: "NODE_OPTIONS attack + dangerous vars",
|
|
149
|
+
env: {
|
|
150
|
+
NODE_OPTIONS: "--require ./evil.js",
|
|
151
|
+
NODE_NO_WARNINGS: "1",
|
|
152
|
+
UV_THREADPOOL_SIZE: "128",
|
|
153
|
+
},
|
|
154
|
+
minWarnings: 3,
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: "multiple NODE_OPTIONS patterns",
|
|
158
|
+
env: {
|
|
159
|
+
NODE_OPTIONS: '--require ./a.js --eval "code" --inspect',
|
|
160
|
+
},
|
|
161
|
+
minWarnings: 3,
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: "clean environment",
|
|
165
|
+
env: {},
|
|
166
|
+
minWarnings: 0,
|
|
167
|
+
},
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
describe("auditEnvironment - NODE_OPTIONS Detection", () => {
|
|
171
|
+
for (const tc of NODE_OPTIONS_ATTACK_VECTORS) {
|
|
172
|
+
test(`should detect ${tc.name}`, () => {
|
|
173
|
+
const env = { NODE_OPTIONS: tc.value };
|
|
174
|
+
const warnings = auditEnvironment(env);
|
|
175
|
+
|
|
176
|
+
const hasSuspiciousWarning = warnings.some((w) =>
|
|
177
|
+
w.message.includes("NODE_OPTIONS contains a code-execution flag"),
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
if (tc.expectedMatch) {
|
|
181
|
+
assert.ok(
|
|
182
|
+
hasSuspiciousWarning,
|
|
183
|
+
`Expected warning for ${tc.name} but got: ${warnings.map((w) => `${w.variable}: ${w.message}`).join(", ")}`,
|
|
184
|
+
);
|
|
185
|
+
} else {
|
|
186
|
+
assert.ok(
|
|
187
|
+
!hasSuspiciousWarning,
|
|
188
|
+
`Unexpected warning for ${tc.name}: ${warnings.map((w) => `${w.variable}: ${w.message}`).join(", ")}`,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe("auditEnvironment - Dangerous Env Vars", () => {
|
|
196
|
+
for (const tc of DANGEROUS_ENV_VAR_CASES) {
|
|
197
|
+
test(`should handle ${tc.name}`, () => {
|
|
198
|
+
const warnings = auditEnvironment(tc.env);
|
|
199
|
+
|
|
200
|
+
assert.strictEqual(
|
|
201
|
+
warnings.length,
|
|
202
|
+
tc.expectedWarnings,
|
|
203
|
+
`Expected ${tc.expectedWarnings} warnings, got ${warnings.length}: ${warnings.map((w) => `${w.variable}: ${w.message}`).join(", ")}`,
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
if (tc.expectedVar) {
|
|
207
|
+
assert.ok(
|
|
208
|
+
warnings.some((w) => w.message.includes(tc.expectedVar)),
|
|
209
|
+
`Expected warning about ${tc.expectedVar} but got: ${warnings.map((w) => `${w.variable}: ${w.message}`).join(", ")}`,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe("auditEnvironment - Combined Attacks", () => {
|
|
217
|
+
for (const tc of COMBINED_ATTACK_CASES) {
|
|
218
|
+
test(`should handle ${tc.name}`, () => {
|
|
219
|
+
const warnings = auditEnvironment(tc.env);
|
|
220
|
+
|
|
221
|
+
assert.ok(
|
|
222
|
+
warnings.length >= tc.minWarnings,
|
|
223
|
+
`Expected at least ${tc.minWarnings} warnings, got ${warnings.length}: ${warnings.map((w) => `${w.variable}: ${w.message}`).join(", ")}`,
|
|
224
|
+
);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe("auditEnvironment - Edge Cases", () => {
|
|
230
|
+
test("should handle undefined NODE_OPTIONS", () => {
|
|
231
|
+
const warnings = auditEnvironment({});
|
|
232
|
+
const hasSuspiciousWarning = warnings.some((w) =>
|
|
233
|
+
w.message.includes("NODE_OPTIONS contains a code-execution flag"),
|
|
234
|
+
);
|
|
235
|
+
assert.ok(!hasSuspiciousWarning);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("should handle null env (uses process.env)", () => {
|
|
239
|
+
const warnings = auditEnvironment();
|
|
240
|
+
assert.ok(Array.isArray(warnings));
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test("should return empty array for completely clean env", () => {
|
|
244
|
+
const warnings = auditEnvironment({
|
|
245
|
+
PATH: "/usr/bin",
|
|
246
|
+
HOME: "/home/user",
|
|
247
|
+
LANG: "en_US.UTF-8",
|
|
248
|
+
});
|
|
249
|
+
assert.deepStrictEqual(warnings, []);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test("should detect all dangerous vars individually", () => {
|
|
253
|
+
const warnings1 = auditEnvironment({ NODE_NO_WARNINGS: "1" });
|
|
254
|
+
const warnings2 = auditEnvironment({ NODE_PENDING_DEPRECATION: "1" });
|
|
255
|
+
const warnings3 = auditEnvironment({ UV_THREADPOOL_SIZE: "128" });
|
|
256
|
+
|
|
257
|
+
assert.strictEqual(warnings1.length, 1);
|
|
258
|
+
assert.strictEqual(warnings2.length, 1);
|
|
259
|
+
assert.strictEqual(warnings3.length, 1);
|
|
260
|
+
|
|
261
|
+
assert.ok(warnings1[0].message.includes("NODE_NO_WARNINGS"));
|
|
262
|
+
assert.ok(warnings2[0].message.includes("NODE_PENDING_DEPRECATION"));
|
|
263
|
+
assert.ok(warnings3[0].message.includes("UV_THREADPOOL_SIZE"));
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("should be case-sensitive for env var names", () => {
|
|
267
|
+
const warnings = auditEnvironment({
|
|
268
|
+
node_no_warnings: "1",
|
|
269
|
+
Node_Options: "--require ./evil.js",
|
|
270
|
+
});
|
|
271
|
+
assert.strictEqual(warnings.length, 0);
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
describe("auditEnvironment - Warning Message Format", () => {
|
|
276
|
+
test("dangerous var warning should mention unsetting", () => {
|
|
277
|
+
const warnings = auditEnvironment({ NODE_NO_WARNINGS: "1" });
|
|
278
|
+
assert.ok(warnings[0].mitigation.includes("Unset"));
|
|
279
|
+
assert.ok(warnings[0].mitigation.includes("NODE_NO_WARNINGS"));
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test("NODE_OPTIONS warning should mention the pattern", () => {
|
|
283
|
+
const warnings = auditEnvironment({ NODE_OPTIONS: "--require ./evil.js" });
|
|
284
|
+
assert.ok(warnings[0].message.includes("NODE_OPTIONS"));
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test("warnings should be human-readable strings", () => {
|
|
288
|
+
const warnings = auditEnvironment({
|
|
289
|
+
NODE_OPTIONS: "--eval test",
|
|
290
|
+
NODE_NO_WARNINGS: "1",
|
|
291
|
+
});
|
|
292
|
+
for (const w of warnings) {
|
|
293
|
+
assert.strictEqual(typeof w.message, "string");
|
|
294
|
+
assert.ok(w.message.length > 0);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
describe("auditEnvironment - NODE_TLS_REJECT_UNAUTHORIZED", () => {
|
|
300
|
+
test("should flag when set to '0' (TLS disabled)", () => {
|
|
301
|
+
const warnings = auditEnvironment({ NODE_TLS_REJECT_UNAUTHORIZED: "0" });
|
|
302
|
+
assert.strictEqual(warnings.length, 1);
|
|
303
|
+
assert.strictEqual(warnings[0].severity, "high");
|
|
304
|
+
assert.ok(
|
|
305
|
+
warnings[0].message.includes("TLS certificate verification is disabled"),
|
|
306
|
+
);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test("should not flag when set to '1' (TLS enabled)", () => {
|
|
310
|
+
const warnings = auditEnvironment({ NODE_TLS_REJECT_UNAUTHORIZED: "1" });
|
|
311
|
+
assert.strictEqual(warnings.length, 0);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test("should not flag when unset", () => {
|
|
315
|
+
const warnings = auditEnvironment({});
|
|
316
|
+
const hasTlsWarning = warnings.some(
|
|
317
|
+
(w) => w.variable === "NODE_TLS_REJECT_UNAUTHORIZED",
|
|
318
|
+
);
|
|
319
|
+
assert.ok(!hasTlsWarning);
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
describe("auditEnvironment - JVM Code Execution", () => {
|
|
324
|
+
test("should flag -javaagent in JAVA_TOOL_OPTIONS", () => {
|
|
325
|
+
const warnings = auditEnvironment({
|
|
326
|
+
JAVA_TOOL_OPTIONS: "-javaagent:/evil/agent.jar",
|
|
327
|
+
});
|
|
328
|
+
assert.ok(warnings.some((w) => w.variable === "JAVA_TOOL_OPTIONS"));
|
|
329
|
+
assert.ok(warnings.some((w) => w.type === "code-execution"));
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
test("should flag -javaagent in JDK_JAVA_OPTIONS", () => {
|
|
333
|
+
const warnings = auditEnvironment({
|
|
334
|
+
JDK_JAVA_OPTIONS: "-javaagent:/evil/agent.jar",
|
|
335
|
+
});
|
|
336
|
+
assert.ok(warnings.some((w) => w.variable === "JDK_JAVA_OPTIONS"));
|
|
337
|
+
assert.ok(warnings.some((w) => w.type === "code-execution"));
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
test("should flag --add-opens in JAVA_TOOL_OPTIONS", () => {
|
|
341
|
+
const warnings = auditEnvironment({
|
|
342
|
+
JAVA_TOOL_OPTIONS: "--add-opens java.base/java.lang=ALL-UNNAMED",
|
|
343
|
+
});
|
|
344
|
+
assert.ok(warnings.some((w) => w.type === "code-execution"));
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
test("should not flag safe JVM options", () => {
|
|
348
|
+
const warnings = auditEnvironment({
|
|
349
|
+
JAVA_TOOL_OPTIONS: "-Xmx4g -Xms512m",
|
|
350
|
+
});
|
|
351
|
+
assert.ok(!warnings.some((w) => w.variable === "JAVA_TOOL_OPTIONS"));
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
test("should not flag empty JAVA_TOOL_OPTIONS", () => {
|
|
355
|
+
const warnings = auditEnvironment({ JAVA_TOOL_OPTIONS: "" });
|
|
356
|
+
assert.ok(!warnings.some((w) => w.variable === "JAVA_TOOL_OPTIONS"));
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
describe("auditEnvironment - Proxy Interception", () => {
|
|
361
|
+
test("should flag HTTP_PROXY when set", () => {
|
|
362
|
+
const warnings = auditEnvironment({ HTTP_PROXY: "http://proxy:3128" });
|
|
363
|
+
assert.ok(warnings.some((w) => w.type === "network-interception"));
|
|
364
|
+
assert.ok(warnings.some((w) => w.variable === "HTTP_PROXY"));
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
test("should flag https_proxy (lowercase) when set", () => {
|
|
368
|
+
const warnings = auditEnvironment({ https_proxy: "http://proxy:3128" });
|
|
369
|
+
assert.ok(warnings.some((w) => w.type === "network-interception"));
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
test("should deduplicate network-interception findings when multiple proxy vars are set", () => {
|
|
373
|
+
const warnings = auditEnvironment({
|
|
374
|
+
HTTP_PROXY: "http://proxy:3128",
|
|
375
|
+
HTTPS_PROXY: "http://proxy:3128",
|
|
376
|
+
});
|
|
377
|
+
assert.strictEqual(
|
|
378
|
+
warnings.filter((w) => w.type === "network-interception").length,
|
|
379
|
+
1,
|
|
380
|
+
);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
test("should not flag proxy vars when unset", () => {
|
|
384
|
+
const warnings = auditEnvironment({ PATH: "/usr/bin" });
|
|
385
|
+
assert.ok(!warnings.some((w) => w.type === "network-interception"));
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
describe("auditEnvironment - Credential Exposure", () => {
|
|
390
|
+
test("should flag GITHUB_TOKEN (matches _TOKEN suffix pattern)", () => {
|
|
391
|
+
const warnings = auditEnvironment({ GITHUB_TOKEN: "ghp_test1234" });
|
|
392
|
+
assert.ok(warnings.some((w) => w.variable === "GITHUB_TOKEN"));
|
|
393
|
+
assert.ok(warnings.some((w) => w.type === "credential-exposure"));
|
|
394
|
+
assert.ok(
|
|
395
|
+
warnings.find((w) => w.variable === "GITHUB_TOKEN")?.severity === "low",
|
|
396
|
+
);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test("should flag NPM_TOKEN (matches _TOKEN suffix pattern)", () => {
|
|
400
|
+
const warnings = auditEnvironment({ NPM_TOKEN: "npm_secret" });
|
|
401
|
+
assert.ok(warnings.some((w) => w.variable === "NPM_TOKEN"));
|
|
402
|
+
assert.ok(warnings.some((w) => w.type === "credential-exposure"));
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
test("should flag vars matching _KEY, _SECRET, _PASS, _PASSWORD patterns", () => {
|
|
406
|
+
const envs = {
|
|
407
|
+
MY_API_KEY: "key123",
|
|
408
|
+
DEPLOY_SECRET: "shhh",
|
|
409
|
+
DB_PASS: "hunter2",
|
|
410
|
+
APP_PASSWORD: "p@ssw0rd",
|
|
411
|
+
};
|
|
412
|
+
const warnings = auditEnvironment(envs);
|
|
413
|
+
assert.ok(warnings.some((w) => w.variable === "MY_API_KEY"));
|
|
414
|
+
assert.ok(warnings.some((w) => w.variable === "DEPLOY_SECRET"));
|
|
415
|
+
assert.ok(warnings.some((w) => w.variable === "DB_PASS"));
|
|
416
|
+
assert.ok(warnings.some((w) => w.variable === "APP_PASSWORD"));
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
test("should flag vars matching _CREDENTIAL and _CREDENTIALS patterns", () => {
|
|
420
|
+
const warnings = auditEnvironment({
|
|
421
|
+
SVC_CREDENTIAL: "cred1",
|
|
422
|
+
CLOUD_CREDENTIALS: "cred2",
|
|
423
|
+
});
|
|
424
|
+
assert.ok(warnings.some((w) => w.variable === "SVC_CREDENTIAL"));
|
|
425
|
+
assert.ok(warnings.some((w) => w.variable === "CLOUD_CREDENTIALS"));
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
test("should NOT flag common system vars with credential-like substrings mid-name", () => {
|
|
429
|
+
// SSH_AUTH_SOCK contains _AUTH but does NOT end with _AUTH → should not match
|
|
430
|
+
// __CF_USER_TEXT_ENCODING contains _USER but does NOT end with _USER → should not match
|
|
431
|
+
const warnings = auditEnvironment({
|
|
432
|
+
SSH_AUTH_SOCK: "/tmp/ssh-agent",
|
|
433
|
+
__CF_USER_TEXT_ENCODING: "0x1F4:0x8000100",
|
|
434
|
+
});
|
|
435
|
+
assert.ok(!warnings.some((w) => w.variable === "SSH_AUTH_SOCK"));
|
|
436
|
+
assert.ok(!warnings.some((w) => w.variable === "__CF_USER_TEXT_ENCODING"));
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
test("should not flag vars that do not match a credential pattern", () => {
|
|
440
|
+
const warnings = auditEnvironment({ PATH: "/usr/bin", HOME: "/home/user" });
|
|
441
|
+
assert.ok(!warnings.some((w) => w.type === "credential-exposure"));
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
test("should not flag credential-named vars with empty value", () => {
|
|
445
|
+
const warnings = auditEnvironment({ GITHUB_TOKEN: "" });
|
|
446
|
+
assert.ok(!warnings.some((w) => w.variable === "GITHUB_TOKEN"));
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
describe("auditEnvironment - Debug Mode Exposure", () => {
|
|
451
|
+
test("should flag CDXGEN_DEBUG_MODE=verbose", () => {
|
|
452
|
+
const warnings = auditEnvironment({ CDXGEN_DEBUG_MODE: "verbose" });
|
|
453
|
+
assert.ok(warnings.some((w) => w.type === "debug-exposure"));
|
|
454
|
+
assert.strictEqual(
|
|
455
|
+
warnings.find((w) => w.type === "debug-exposure")?.severity,
|
|
456
|
+
"low",
|
|
457
|
+
);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
test("should flag CDXGEN_DEBUG_MODE=debug", () => {
|
|
461
|
+
const warnings = auditEnvironment({ CDXGEN_DEBUG_MODE: "debug" });
|
|
462
|
+
assert.ok(warnings.some((w) => w.type === "debug-exposure"));
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
test("should flag SCAN_DEBUG_MODE=debug", () => {
|
|
466
|
+
const warnings = auditEnvironment({ SCAN_DEBUG_MODE: "debug" });
|
|
467
|
+
assert.ok(warnings.some((w) => w.type === "debug-exposure"));
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
test("should not flag when CDXGEN_DEBUG_MODE is not set", () => {
|
|
471
|
+
const warnings = auditEnvironment({ PATH: "/usr/bin" });
|
|
472
|
+
assert.ok(!warnings.some((w) => w.type === "debug-exposure"));
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
test("should not flag when CDXGEN_DEBUG_MODE is an unrecognised value", () => {
|
|
476
|
+
const warnings = auditEnvironment({ CDXGEN_DEBUG_MODE: "info" });
|
|
477
|
+
assert.ok(!warnings.some((w) => w.type === "debug-exposure"));
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
describe("auditEnvironment - Deno Certificate", () => {
|
|
482
|
+
test("should flag DENO_CERT when set to a non-empty value", () => {
|
|
483
|
+
const warnings = auditEnvironment({
|
|
484
|
+
DENO_CERT: "/etc/ssl/private/corp-ca.pem",
|
|
485
|
+
});
|
|
486
|
+
assert.ok(warnings.some((w) => w.variable === "DENO_CERT"));
|
|
487
|
+
assert.ok(warnings.some((w) => w.type === "environment-variable"));
|
|
488
|
+
assert.strictEqual(
|
|
489
|
+
warnings.find((w) => w.variable === "DENO_CERT")?.severity,
|
|
490
|
+
"high",
|
|
491
|
+
);
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
test("should not flag DENO_CERT when unset", () => {
|
|
495
|
+
const warnings = auditEnvironment({ PATH: "/usr/bin" });
|
|
496
|
+
assert.ok(!warnings.some((w) => w.variable === "DENO_CERT"));
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
test("should not flag DENO_CERT when set to empty string", () => {
|
|
500
|
+
const warnings = auditEnvironment({ DENO_CERT: "" });
|
|
501
|
+
assert.ok(!warnings.some((w) => w.variable === "DENO_CERT"));
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
describe("auditEnvironment - Deno Permissions", () => {
|
|
506
|
+
// Save original so we restore correctly even if Deno were already defined.
|
|
507
|
+
const originalDeno = globalThis.Deno;
|
|
508
|
+
|
|
509
|
+
// Helper to build a minimal Deno mock where only the listed commands have run permission.
|
|
510
|
+
const createDenoMock = (os, allowedCommands) => ({
|
|
511
|
+
build: { os },
|
|
512
|
+
permissions: {
|
|
513
|
+
querySync: (desc) => ({
|
|
514
|
+
state:
|
|
515
|
+
desc.name === "run" && allowedCommands.includes(desc.command)
|
|
516
|
+
? "granted"
|
|
517
|
+
: "denied",
|
|
518
|
+
}),
|
|
519
|
+
},
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
// afterEach restores globalThis.Deno after each test so mocks cannot leak.
|
|
523
|
+
afterEach(() => {
|
|
524
|
+
if (originalDeno === undefined) {
|
|
525
|
+
delete globalThis.Deno;
|
|
526
|
+
} else {
|
|
527
|
+
globalThis.Deno = originalDeno;
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
test("should flag permission-misuse when Deno shell execution is broadly granted (Unix)", () => {
|
|
532
|
+
globalThis.Deno = createDenoMock("linux", ["sh", "bash"]);
|
|
533
|
+
const warnings = auditEnvironment({});
|
|
534
|
+
assert.ok(warnings.some((w) => w.variable === "DENO_PERMISSIONS"));
|
|
535
|
+
assert.ok(warnings.some((w) => w.type === "permission-misuse"));
|
|
536
|
+
assert.strictEqual(
|
|
537
|
+
warnings.find((w) => w.variable === "DENO_PERMISSIONS")?.severity,
|
|
538
|
+
"high",
|
|
539
|
+
);
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
test("should flag permission-misuse when Deno shell execution is broadly granted (Windows)", () => {
|
|
543
|
+
globalThis.Deno = createDenoMock("windows", ["cmd", "powershell"]);
|
|
544
|
+
const warnings = auditEnvironment({});
|
|
545
|
+
assert.ok(warnings.some((w) => w.variable === "DENO_PERMISSIONS"));
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
test("should NOT flag permission-misuse when Deno restricts shell execution", () => {
|
|
549
|
+
// Only npm and node are allowed; sh/bash are not — no false positive expected.
|
|
550
|
+
globalThis.Deno = createDenoMock("linux", ["npm", "node"]);
|
|
551
|
+
const warnings = auditEnvironment({});
|
|
552
|
+
assert.ok(!warnings.some((w) => w.variable === "DENO_PERMISSIONS"));
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
test("should silently skip Deno permission check when querySync throws", () => {
|
|
556
|
+
globalThis.Deno = {
|
|
557
|
+
build: { os: "linux" },
|
|
558
|
+
permissions: {
|
|
559
|
+
querySync: () => {
|
|
560
|
+
throw new Error("querySync not available");
|
|
561
|
+
},
|
|
562
|
+
},
|
|
563
|
+
};
|
|
564
|
+
assert.doesNotThrow(() => auditEnvironment({}));
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
test("should not flag when globalThis.Deno is undefined (Node.js environment)", () => {
|
|
568
|
+
// globalThis.Deno is already undefined in the test runtime (Node.js)
|
|
569
|
+
const warnings = auditEnvironment({});
|
|
570
|
+
assert.ok(!warnings.some((w) => w.variable === "DENO_PERMISSIONS"));
|
|
571
|
+
});
|
|
572
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdtempSync, readdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import { arch, platform } from "node:os";
|
|
3
3
|
import { delimiter, dirname, join, resolve } from "node:path";
|
|
4
4
|
import process from "node:process";
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
isMac,
|
|
27
27
|
isSecureMode,
|
|
28
28
|
isWin,
|
|
29
|
+
safeExistsSync,
|
|
29
30
|
safeSpawnSync,
|
|
30
31
|
TIMEOUT_MS,
|
|
31
32
|
} from "../../helpers/utils.js";
|
|
@@ -340,7 +341,7 @@ export function prepareSwiftEnv(filePath, options) {
|
|
|
340
341
|
"The Swift package command did not yield the expected result. Build this project manually before invoking cdxgen.",
|
|
341
342
|
);
|
|
342
343
|
}
|
|
343
|
-
if (!
|
|
344
|
+
if (!safeExistsSync(resolvedFile)) {
|
|
344
345
|
console.log(
|
|
345
346
|
"Package.resolved file did not get generated successfully. Check the Package.swift file for declared dependencies.\nCheck if any private registry needs to be configured for the build to succeed.",
|
|
346
347
|
);
|
|
@@ -402,13 +403,13 @@ export function prepareRubyEnv(filePath, options) {
|
|
|
402
403
|
mkdtempSync(join(getTmpDir(), "cdxgen-gem-home-"));
|
|
403
404
|
process.env.CDXGEN_GEM_HOME = cdxgenGemHome;
|
|
404
405
|
// Is there a .ruby-version file in the project?
|
|
405
|
-
if (
|
|
406
|
+
if (safeExistsSync(join(filePath, ".ruby-version"))) {
|
|
406
407
|
rubyVersionNeeded = readFileSync(join(filePath, ".ruby-version"), {
|
|
407
408
|
encoding: "utf-8",
|
|
408
409
|
})
|
|
409
410
|
.trim()
|
|
410
411
|
.replace("ruby-", "");
|
|
411
|
-
} else if (
|
|
412
|
+
} else if (safeExistsSync(join(filePath, "Gemfile.lock"))) {
|
|
412
413
|
// Is there a lock file that can be used to identify the needed Ruby version?
|
|
413
414
|
const gemlockData = readFileSync(join(filePath, "Gemfile.lock"), {
|
|
414
415
|
encoding: "utf-8",
|
|
@@ -466,7 +467,7 @@ export function prepareRubyEnv(filePath, options) {
|
|
|
466
467
|
process.env.CDXGEN_BUNDLE_CMD = "bundle";
|
|
467
468
|
rubyVersionNeeded = undefined;
|
|
468
469
|
// Do we have a proper GEM_HOME already?
|
|
469
|
-
if (cdxgenGemHome &&
|
|
470
|
+
if (cdxgenGemHome && safeExistsSync(cdxgenGemHome)) {
|
|
470
471
|
const gemspecFiles = getAllFiles(
|
|
471
472
|
cdxgenGemHome,
|
|
472
473
|
"**/specifications/**/*.gemspec",
|
|
@@ -540,7 +541,7 @@ export function prepareRubyEnv(filePath, options) {
|
|
|
540
541
|
process.env.CDXGEN_BUNDLE_CMD = join(fullToolBinDir, "bundle");
|
|
541
542
|
bundleTool = join(fullToolBinDir, "bundle");
|
|
542
543
|
process.env.CDXGEN_BUNDLE_CMD = bundleTool;
|
|
543
|
-
if (!
|
|
544
|
+
if (!safeExistsSync(bundleTool)) {
|
|
544
545
|
const bundlerStatus = installRubyBundler(
|
|
545
546
|
rubyVersionNeeded,
|
|
546
547
|
undefined,
|
|
@@ -553,7 +554,7 @@ export function prepareRubyEnv(filePath, options) {
|
|
|
553
554
|
}
|
|
554
555
|
}
|
|
555
556
|
// Do we have a proper GEM_HOME already?
|
|
556
|
-
if (cdxgenGemHome &&
|
|
557
|
+
if (cdxgenGemHome && safeExistsSync(cdxgenGemHome)) {
|
|
557
558
|
const gemspecFiles = getAllFiles(
|
|
558
559
|
cdxgenGemHome,
|
|
559
560
|
"**/specifications/**/*.gemspec",
|
|
@@ -574,7 +575,10 @@ export function prepareRubyEnv(filePath, options) {
|
|
|
574
575
|
return;
|
|
575
576
|
}
|
|
576
577
|
}
|
|
577
|
-
if (
|
|
578
|
+
if (
|
|
579
|
+
bundleTool &&
|
|
580
|
+
(bundleTool === "bundle" || safeExistsSync(bundleTool))
|
|
581
|
+
) {
|
|
578
582
|
if (DEBUG_MODE) {
|
|
579
583
|
if (bundleTool === "bundle") {
|
|
580
584
|
console.log("cdxgen will use the default bundle command.");
|
|
@@ -1052,7 +1052,7 @@ class Node {
|
|
|
1052
1052
|
}
|
|
1053
1053
|
|
|
1054
1054
|
// it's a top level pkg, or a dep of one
|
|
1055
|
-
if (!this.resolveParent
|
|
1055
|
+
if (!this.resolveParent?.resolveParent) {
|
|
1056
1056
|
return false;
|
|
1057
1057
|
}
|
|
1058
1058
|
|
|
@@ -1382,7 +1382,7 @@ class Node {
|
|
|
1382
1382
|
|
|
1383
1383
|
updateOverridesEdgeInRemoved(otherOverrideSet) {
|
|
1384
1384
|
// If this edge's overrides isn't equal to this node's overrides, then removing it won't change newOverrideSet later.
|
|
1385
|
-
if (!this.overrides
|
|
1385
|
+
if (!this.overrides?.isEqual(otherOverrideSet)) {
|
|
1386
1386
|
return false;
|
|
1387
1387
|
}
|
|
1388
1388
|
let newOverrideSet;
|
|
@@ -1453,7 +1453,7 @@ class Node {
|
|
|
1453
1453
|
|
|
1454
1454
|
addEdgeIn(edge) {
|
|
1455
1455
|
// We need to handle the case where the new edge in has an overrides field which is different from the current value.
|
|
1456
|
-
if (!this.overrides
|
|
1456
|
+
if (!this.overrides?.isEqual(edge.overrides)) {
|
|
1457
1457
|
this.updateOverridesEdgeInAdded(edge.overrides);
|
|
1458
1458
|
}
|
|
1459
1459
|
this.edgesIn.add(edge);
|