@cyclonedx/cdxgen 12.1.5 → 12.2.1

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.
Files changed (193) hide show
  1. package/README.md +51 -40
  2. package/bin/cdxgen.js +194 -97
  3. package/bin/evinse.js +4 -4
  4. package/bin/repl.js +1 -1
  5. package/bin/sign.js +102 -0
  6. package/bin/validate.js +233 -0
  7. package/bin/verify.js +69 -28
  8. package/data/queries.json +1 -1
  9. package/data/rules/ci-permissions.yaml +186 -0
  10. package/data/rules/dependency-sources.yaml +123 -0
  11. package/data/rules/package-integrity.yaml +135 -0
  12. package/data/rules/vscode-extensions.yaml +228 -0
  13. package/lib/cli/index.js +449 -429
  14. package/lib/cli/index.poku.js +117 -0
  15. package/lib/evinser/db.js +137 -0
  16. package/lib/{helpers → evinser}/db.poku.js +2 -6
  17. package/lib/evinser/evinser.js +2 -14
  18. package/lib/helpers/analyzer.js +606 -3
  19. package/lib/helpers/analyzer.poku.js +230 -0
  20. package/lib/helpers/bomSigner.js +312 -0
  21. package/lib/helpers/bomSigner.poku.js +156 -0
  22. package/lib/helpers/ciParsers/azurePipelines.js +295 -0
  23. package/lib/helpers/ciParsers/azurePipelines.poku.js +253 -0
  24. package/lib/helpers/ciParsers/circleCi.js +286 -0
  25. package/lib/helpers/ciParsers/circleCi.poku.js +230 -0
  26. package/lib/helpers/ciParsers/common.js +24 -0
  27. package/lib/helpers/ciParsers/githubActions.js +636 -0
  28. package/lib/helpers/ciParsers/githubActions.poku.js +802 -0
  29. package/lib/helpers/ciParsers/gitlabCi.js +213 -0
  30. package/lib/helpers/ciParsers/gitlabCi.poku.js +247 -0
  31. package/lib/helpers/ciParsers/jenkins.js +181 -0
  32. package/lib/helpers/ciParsers/jenkins.poku.js +197 -0
  33. package/lib/helpers/depsUtils.js +219 -0
  34. package/lib/helpers/depsUtils.poku.js +207 -0
  35. package/lib/helpers/display.js +426 -5
  36. package/lib/helpers/envcontext.js +18 -3
  37. package/lib/helpers/formulationParsers.js +351 -0
  38. package/lib/helpers/logger.js +14 -0
  39. package/lib/helpers/protobom.js +9 -9
  40. package/lib/helpers/pythonutils.js +9 -0
  41. package/lib/helpers/remote/dependency-track.js +84 -0
  42. package/lib/helpers/remote/dependency-track.poku.js +119 -0
  43. package/lib/helpers/table.js +384 -0
  44. package/lib/helpers/table.poku.js +186 -0
  45. package/lib/helpers/utils.js +865 -416
  46. package/lib/helpers/utils.poku.js +172 -265
  47. package/lib/helpers/versutils.js +202 -0
  48. package/lib/helpers/versutils.poku.js +315 -0
  49. package/lib/helpers/vsixutils.js +1061 -0
  50. package/lib/helpers/vsixutils.poku.js +2247 -0
  51. package/lib/managers/binary.js +19 -19
  52. package/lib/managers/docker.js +108 -1
  53. package/lib/managers/oci.js +10 -0
  54. package/lib/managers/piptree.js +3 -9
  55. package/lib/parsers/npmrc.js +17 -13
  56. package/lib/parsers/npmrc.poku.js +41 -5
  57. package/lib/server/openapi.yaml +34 -1
  58. package/lib/server/server.js +50 -13
  59. package/lib/server/server.poku.js +332 -144
  60. package/lib/stages/postgen/annotator.js +1 -1
  61. package/lib/stages/postgen/auditBom.js +196 -0
  62. package/lib/stages/postgen/auditBom.poku.js +378 -0
  63. package/lib/stages/postgen/postgen.js +54 -1
  64. package/lib/stages/postgen/postgen.poku.js +90 -1
  65. package/lib/stages/postgen/ruleEngine.js +369 -0
  66. package/lib/stages/pregen/envAudit.js +299 -0
  67. package/lib/stages/pregen/envAudit.poku.js +572 -0
  68. package/lib/stages/pregen/pregen.js +12 -8
  69. package/lib/{helpers/validator.js → validator/bomValidator.js} +107 -47
  70. package/lib/validator/complianceEngine.js +241 -0
  71. package/lib/validator/complianceEngine.poku.js +168 -0
  72. package/lib/validator/complianceRules.js +1610 -0
  73. package/lib/validator/complianceRules.poku.js +328 -0
  74. package/lib/validator/index.js +222 -0
  75. package/lib/validator/index.poku.js +144 -0
  76. package/lib/validator/reporters/annotations.js +121 -0
  77. package/lib/validator/reporters/console.js +149 -0
  78. package/lib/validator/reporters/index.js +41 -0
  79. package/lib/validator/reporters/json.js +37 -0
  80. package/lib/validator/reporters/sarif.js +184 -0
  81. package/lib/validator/reporters.poku.js +150 -0
  82. package/package.json +8 -9
  83. package/types/bin/sign.d.ts +3 -0
  84. package/types/bin/sign.d.ts.map +1 -0
  85. package/types/bin/validate.d.ts +3 -0
  86. package/types/bin/validate.d.ts.map +1 -0
  87. package/types/helpers/utils.d.ts +0 -1
  88. package/types/lib/cli/index.d.ts +49 -52
  89. package/types/lib/cli/index.d.ts.map +1 -1
  90. package/types/lib/evinser/db.d.ts +34 -0
  91. package/types/lib/evinser/db.d.ts.map +1 -0
  92. package/types/lib/evinser/evinser.d.ts +63 -16
  93. package/types/lib/evinser/evinser.d.ts.map +1 -1
  94. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  95. package/types/lib/helpers/bomSigner.d.ts +27 -0
  96. package/types/lib/helpers/bomSigner.d.ts.map +1 -0
  97. package/types/lib/helpers/ciParsers/azurePipelines.d.ts +17 -0
  98. package/types/lib/helpers/ciParsers/azurePipelines.d.ts.map +1 -0
  99. package/types/lib/helpers/ciParsers/circleCi.d.ts +17 -0
  100. package/types/lib/helpers/ciParsers/circleCi.d.ts.map +1 -0
  101. package/types/lib/helpers/ciParsers/common.d.ts +11 -0
  102. package/types/lib/helpers/ciParsers/common.d.ts.map +1 -0
  103. package/types/lib/helpers/ciParsers/githubActions.d.ts +34 -0
  104. package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -0
  105. package/types/lib/helpers/ciParsers/gitlabCi.d.ts +17 -0
  106. package/types/lib/helpers/ciParsers/gitlabCi.d.ts.map +1 -0
  107. package/types/lib/helpers/ciParsers/jenkins.d.ts +17 -0
  108. package/types/lib/helpers/ciParsers/jenkins.d.ts.map +1 -0
  109. package/types/lib/helpers/depsUtils.d.ts +21 -0
  110. package/types/lib/helpers/depsUtils.d.ts.map +1 -0
  111. package/types/lib/helpers/display.d.ts +111 -11
  112. package/types/lib/helpers/display.d.ts.map +1 -1
  113. package/types/lib/helpers/envcontext.d.ts +19 -7
  114. package/types/lib/helpers/envcontext.d.ts.map +1 -1
  115. package/types/lib/helpers/formulationParsers.d.ts +50 -0
  116. package/types/lib/helpers/formulationParsers.d.ts.map +1 -0
  117. package/types/lib/helpers/logger.d.ts +15 -1
  118. package/types/lib/helpers/logger.d.ts.map +1 -1
  119. package/types/lib/helpers/protobom.d.ts +2 -2
  120. package/types/lib/helpers/pythonutils.d.ts +10 -1
  121. package/types/lib/helpers/pythonutils.d.ts.map +1 -1
  122. package/types/lib/helpers/remote/dependency-track.d.ts +16 -0
  123. package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -0
  124. package/types/lib/helpers/table.d.ts +6 -0
  125. package/types/lib/helpers/table.d.ts.map +1 -0
  126. package/types/lib/helpers/utils.d.ts +533 -128
  127. package/types/lib/helpers/utils.d.ts.map +1 -1
  128. package/types/lib/helpers/versutils.d.ts +8 -0
  129. package/types/lib/helpers/versutils.d.ts.map +1 -0
  130. package/types/lib/helpers/vsixutils.d.ts +130 -0
  131. package/types/lib/helpers/vsixutils.d.ts.map +1 -0
  132. package/types/lib/managers/docker.d.ts +12 -31
  133. package/types/lib/managers/docker.d.ts.map +1 -1
  134. package/types/lib/managers/oci.d.ts +11 -1
  135. package/types/lib/managers/oci.d.ts.map +1 -1
  136. package/types/lib/managers/piptree.d.ts.map +1 -1
  137. package/types/lib/parsers/npmrc.d.ts +4 -1
  138. package/types/lib/parsers/npmrc.d.ts.map +1 -1
  139. package/types/lib/server/server.d.ts +22 -2
  140. package/types/lib/server/server.d.ts.map +1 -1
  141. package/types/lib/stages/postgen/auditBom.d.ts +20 -0
  142. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -0
  143. package/types/lib/stages/postgen/postgen.d.ts +8 -1
  144. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  145. package/types/lib/stages/postgen/ruleEngine.d.ts +18 -0
  146. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -0
  147. package/types/lib/stages/pregen/envAudit.d.ts +8 -0
  148. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -0
  149. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
  150. package/types/lib/{helpers/validator.d.ts → validator/bomValidator.d.ts} +1 -1
  151. package/types/lib/validator/bomValidator.d.ts.map +1 -0
  152. package/types/lib/validator/complianceEngine.d.ts +66 -0
  153. package/types/lib/validator/complianceEngine.d.ts.map +1 -0
  154. package/types/lib/validator/complianceRules.d.ts +70 -0
  155. package/types/lib/validator/complianceRules.d.ts.map +1 -0
  156. package/types/lib/validator/index.d.ts +70 -0
  157. package/types/lib/validator/index.d.ts.map +1 -0
  158. package/types/lib/validator/reporters/annotations.d.ts +31 -0
  159. package/types/lib/validator/reporters/annotations.d.ts.map +1 -0
  160. package/types/lib/validator/reporters/console.d.ts +30 -0
  161. package/types/lib/validator/reporters/console.d.ts.map +1 -0
  162. package/types/lib/validator/reporters/index.d.ts +21 -0
  163. package/types/lib/validator/reporters/index.d.ts.map +1 -0
  164. package/types/lib/validator/reporters/json.d.ts +11 -0
  165. package/types/lib/validator/reporters/json.d.ts.map +1 -0
  166. package/types/lib/validator/reporters/sarif.d.ts +16 -0
  167. package/types/lib/validator/reporters/sarif.d.ts.map +1 -0
  168. package/lib/helpers/db.js +0 -162
  169. package/lib/stages/pregen/env-audit.js +0 -34
  170. package/lib/stages/pregen/env-audit.poku.js +0 -290
  171. package/types/helpers/db.d.ts +0 -35
  172. package/types/helpers/db.d.ts.map +0 -1
  173. package/types/lib/helpers/db.d.ts +0 -35
  174. package/types/lib/helpers/db.d.ts.map +0 -1
  175. package/types/lib/helpers/validator.d.ts.map +0 -1
  176. package/types/lib/stages/pregen/env-audit.d.ts +0 -2
  177. package/types/lib/stages/pregen/env-audit.d.ts.map +0 -1
  178. package/types/managers/binary.d.ts +0 -37
  179. package/types/managers/binary.d.ts.map +0 -1
  180. package/types/managers/docker.d.ts +0 -56
  181. package/types/managers/docker.d.ts.map +0 -1
  182. package/types/managers/oci.d.ts +0 -2
  183. package/types/managers/oci.d.ts.map +0 -1
  184. package/types/managers/piptree.d.ts +0 -2
  185. package/types/managers/piptree.d.ts.map +0 -1
  186. package/types/server/server.d.ts +0 -34
  187. package/types/server/server.d.ts.map +0 -1
  188. package/types/stages/postgen/annotator.d.ts +0 -27
  189. package/types/stages/postgen/annotator.d.ts.map +0 -1
  190. package/types/stages/postgen/postgen.d.ts +0 -51
  191. package/types/stages/postgen/postgen.d.ts.map +0 -1
  192. package/types/stages/pregen/pregen.d.ts +0 -59
  193. 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 { existsSync, mkdtempSync, readdirSync, readFileSync } from "node:fs";
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 (!existsSync(resolvedFile)) {
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 (existsSync(join(filePath, ".ruby-version"))) {
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 (existsSync(join(filePath, "Gemfile.lock"))) {
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 && existsSync(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 (!existsSync(bundleTool)) {
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 && existsSync(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 (bundleTool && (bundleTool === "bundle" || existsSync(bundleTool))) {
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.");