@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.
Files changed (184) hide show
  1. package/README.md +47 -39
  2. package/bin/cdxgen.js +181 -90
  3. package/bin/evinse.js +4 -4
  4. package/bin/repl.js +3 -3
  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 +484 -440
  14. package/lib/evinser/db.js +137 -0
  15. package/lib/{helpers → evinser}/db.poku.js +2 -6
  16. package/lib/evinser/evinser.js +5 -18
  17. package/lib/evinser/swiftsem.js +1 -1
  18. package/lib/helpers/bomSigner.js +312 -0
  19. package/lib/helpers/bomSigner.poku.js +156 -0
  20. package/lib/helpers/caxa.js +1 -1
  21. package/lib/helpers/ciParsers/azurePipelines.js +295 -0
  22. package/lib/helpers/ciParsers/azurePipelines.poku.js +253 -0
  23. package/lib/helpers/ciParsers/circleCi.js +286 -0
  24. package/lib/helpers/ciParsers/circleCi.poku.js +230 -0
  25. package/lib/helpers/ciParsers/common.js +24 -0
  26. package/lib/helpers/ciParsers/githubActions.js +636 -0
  27. package/lib/helpers/ciParsers/githubActions.poku.js +802 -0
  28. package/lib/helpers/ciParsers/gitlabCi.js +213 -0
  29. package/lib/helpers/ciParsers/gitlabCi.poku.js +247 -0
  30. package/lib/helpers/ciParsers/jenkins.js +181 -0
  31. package/lib/helpers/ciParsers/jenkins.poku.js +197 -0
  32. package/lib/helpers/depsUtils.js +203 -0
  33. package/lib/helpers/depsUtils.poku.js +150 -0
  34. package/lib/helpers/display.js +429 -14
  35. package/lib/helpers/envcontext.js +23 -8
  36. package/lib/helpers/formulationParsers.js +351 -0
  37. package/lib/helpers/logger.js +14 -0
  38. package/lib/helpers/protobom.js +9 -9
  39. package/lib/helpers/pythonutils.js +305 -0
  40. package/lib/helpers/pythonutils.poku.js +469 -0
  41. package/lib/helpers/utils.js +970 -528
  42. package/lib/helpers/utils.poku.js +139 -256
  43. package/lib/helpers/versutils.js +202 -0
  44. package/lib/helpers/versutils.poku.js +315 -0
  45. package/lib/helpers/vsixutils.js +1061 -0
  46. package/lib/helpers/vsixutils.poku.js +2247 -0
  47. package/lib/managers/binary.js +19 -19
  48. package/lib/managers/docker.js +108 -1
  49. package/lib/managers/oci.js +10 -0
  50. package/lib/managers/piptree.js +4 -10
  51. package/lib/parsers/npmrc.js +92 -0
  52. package/lib/parsers/npmrc.poku.js +528 -0
  53. package/lib/server/openapi.yaml +1 -10
  54. package/lib/server/server.js +58 -16
  55. package/lib/server/server.poku.js +123 -144
  56. package/lib/stages/postgen/annotator.js +1 -1
  57. package/lib/stages/postgen/auditBom.js +197 -0
  58. package/lib/stages/postgen/auditBom.poku.js +378 -0
  59. package/lib/stages/postgen/postgen.js +54 -1
  60. package/lib/stages/postgen/postgen.poku.js +90 -1
  61. package/lib/stages/postgen/ruleEngine.js +369 -0
  62. package/lib/stages/pregen/envAudit.js +299 -0
  63. package/lib/stages/pregen/envAudit.poku.js +572 -0
  64. package/lib/stages/pregen/pregen.js +12 -8
  65. package/lib/third-party/arborist/lib/deepest-nesting-target.js +1 -1
  66. package/lib/third-party/arborist/lib/node.js +3 -3
  67. package/lib/third-party/arborist/lib/shrinkwrap.js +1 -1
  68. package/lib/third-party/arborist/lib/tree-check.js +1 -1
  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 -8
  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/bomSigner.d.ts +27 -0
  95. package/types/lib/helpers/bomSigner.d.ts.map +1 -0
  96. package/types/lib/helpers/ciParsers/azurePipelines.d.ts +17 -0
  97. package/types/lib/helpers/ciParsers/azurePipelines.d.ts.map +1 -0
  98. package/types/lib/helpers/ciParsers/circleCi.d.ts +17 -0
  99. package/types/lib/helpers/ciParsers/circleCi.d.ts.map +1 -0
  100. package/types/lib/helpers/ciParsers/common.d.ts +11 -0
  101. package/types/lib/helpers/ciParsers/common.d.ts.map +1 -0
  102. package/types/lib/helpers/ciParsers/githubActions.d.ts +34 -0
  103. package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -0
  104. package/types/lib/helpers/ciParsers/gitlabCi.d.ts +17 -0
  105. package/types/lib/helpers/ciParsers/gitlabCi.d.ts.map +1 -0
  106. package/types/lib/helpers/ciParsers/jenkins.d.ts +17 -0
  107. package/types/lib/helpers/ciParsers/jenkins.d.ts.map +1 -0
  108. package/types/lib/helpers/depsUtils.d.ts +21 -0
  109. package/types/lib/helpers/depsUtils.d.ts.map +1 -0
  110. package/types/lib/helpers/display.d.ts +111 -11
  111. package/types/lib/helpers/display.d.ts.map +1 -1
  112. package/types/lib/helpers/envcontext.d.ts +19 -7
  113. package/types/lib/helpers/envcontext.d.ts.map +1 -1
  114. package/types/lib/helpers/formulationParsers.d.ts +50 -0
  115. package/types/lib/helpers/formulationParsers.d.ts.map +1 -0
  116. package/types/lib/helpers/logger.d.ts +15 -1
  117. package/types/lib/helpers/logger.d.ts.map +1 -1
  118. package/types/lib/helpers/protobom.d.ts +2 -2
  119. package/types/lib/helpers/pythonutils.d.ts +18 -0
  120. package/types/lib/helpers/pythonutils.d.ts.map +1 -0
  121. package/types/lib/helpers/utils.d.ts +532 -128
  122. package/types/lib/helpers/utils.d.ts.map +1 -1
  123. package/types/lib/helpers/versutils.d.ts +8 -0
  124. package/types/lib/helpers/versutils.d.ts.map +1 -0
  125. package/types/lib/helpers/vsixutils.d.ts +130 -0
  126. package/types/lib/helpers/vsixutils.d.ts.map +1 -0
  127. package/types/lib/managers/docker.d.ts +12 -31
  128. package/types/lib/managers/docker.d.ts.map +1 -1
  129. package/types/lib/managers/oci.d.ts +11 -1
  130. package/types/lib/managers/oci.d.ts.map +1 -1
  131. package/types/lib/managers/piptree.d.ts.map +1 -1
  132. package/types/lib/parsers/npmrc.d.ts +26 -0
  133. package/types/lib/parsers/npmrc.d.ts.map +1 -0
  134. package/types/lib/server/server.d.ts +21 -2
  135. package/types/lib/server/server.d.ts.map +1 -1
  136. package/types/lib/stages/postgen/auditBom.d.ts +20 -0
  137. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -0
  138. package/types/lib/stages/postgen/postgen.d.ts +8 -1
  139. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  140. package/types/lib/stages/postgen/ruleEngine.d.ts +18 -0
  141. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -0
  142. package/types/lib/stages/pregen/envAudit.d.ts +8 -0
  143. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -0
  144. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
  145. package/types/lib/{helpers/validator.d.ts → validator/bomValidator.d.ts} +1 -1
  146. package/types/lib/validator/bomValidator.d.ts.map +1 -0
  147. package/types/lib/validator/complianceEngine.d.ts +66 -0
  148. package/types/lib/validator/complianceEngine.d.ts.map +1 -0
  149. package/types/lib/validator/complianceRules.d.ts +70 -0
  150. package/types/lib/validator/complianceRules.d.ts.map +1 -0
  151. package/types/lib/validator/index.d.ts +70 -0
  152. package/types/lib/validator/index.d.ts.map +1 -0
  153. package/types/lib/validator/reporters/annotations.d.ts +31 -0
  154. package/types/lib/validator/reporters/annotations.d.ts.map +1 -0
  155. package/types/lib/validator/reporters/console.d.ts +30 -0
  156. package/types/lib/validator/reporters/console.d.ts.map +1 -0
  157. package/types/lib/validator/reporters/index.d.ts +21 -0
  158. package/types/lib/validator/reporters/index.d.ts.map +1 -0
  159. package/types/lib/validator/reporters/json.d.ts +11 -0
  160. package/types/lib/validator/reporters/json.d.ts.map +1 -0
  161. package/types/lib/validator/reporters/sarif.d.ts +16 -0
  162. package/types/lib/validator/reporters/sarif.d.ts.map +1 -0
  163. package/lib/helpers/db.js +0 -162
  164. package/types/helpers/db.d.ts +0 -35
  165. package/types/helpers/db.d.ts.map +0 -1
  166. package/types/lib/helpers/db.d.ts +0 -35
  167. package/types/lib/helpers/db.d.ts.map +0 -1
  168. package/types/lib/helpers/validator.d.ts.map +0 -1
  169. package/types/managers/binary.d.ts +0 -37
  170. package/types/managers/binary.d.ts.map +0 -1
  171. package/types/managers/docker.d.ts +0 -56
  172. package/types/managers/docker.d.ts.map +0 -1
  173. package/types/managers/oci.d.ts +0 -2
  174. package/types/managers/oci.d.ts.map +0 -1
  175. package/types/managers/piptree.d.ts +0 -2
  176. package/types/managers/piptree.d.ts.map +0 -1
  177. package/types/server/server.d.ts +0 -34
  178. package/types/server/server.d.ts.map +0 -1
  179. package/types/stages/postgen/annotator.d.ts +0 -27
  180. package/types/stages/postgen/annotator.d.ts.map +0 -1
  181. package/types/stages/postgen/postgen.d.ts +0 -51
  182. package/types/stages/postgen/postgen.d.ts.map +0 -1
  183. package/types/stages/pregen/pregen.d.ts +0 -59
  184. package/types/stages/pregen/pregen.d.ts.map +0 -1
@@ -0,0 +1,528 @@
1
+ import { strict as assert } from "node:assert";
2
+
3
+ import { describe, test } from "poku";
4
+
5
+ import { parseNpmrc, parseNpmrcFromEnv } from "./npmrc.js";
6
+
7
+ // biome-ignore-start lint/suspicious/noTemplateCurlyInString: Test data
8
+ const VALID_NPMRC_CASES = [
9
+ {
10
+ name: "basic key=value",
11
+ input: "registry = https://registry.npmjs.org/",
12
+ expected: { registry: "https://registry.npmjs.org/" },
13
+ },
14
+ {
15
+ name: "key=value without spaces",
16
+ input: "cache=/tmp/npm-cache",
17
+ expected: { cache: "/tmp/npm-cache" },
18
+ },
19
+ {
20
+ name: "value containing equals sign",
21
+ input: "init-author-name=John=Doe",
22
+ expected: { "init-author-name": "John=Doe" },
23
+ },
24
+ {
25
+ name: "hash comment",
26
+ input: "# this is a comment\nregistry=https://example.com",
27
+ expected: { registry: "https://example.com" },
28
+ },
29
+ {
30
+ name: "semicolon comment",
31
+ input: "; another comment\nproxy=http://proxy.local",
32
+ expected: { proxy: "http://proxy.local" },
33
+ },
34
+ {
35
+ name: "inline comment (treated as value)",
36
+ input: "registry=https://example.com # comment",
37
+ expected: { registry: "https://example.com # comment" },
38
+ },
39
+ {
40
+ name: "double-quoted value",
41
+ input: 'description = "A package with spaces"',
42
+ expected: { description: "A package with spaces" },
43
+ },
44
+ {
45
+ name: "single-quoted value",
46
+ input: "description = 'Single quoted'",
47
+ expected: { description: "Single quoted" },
48
+ },
49
+ {
50
+ name: "quoted value with inner quotes",
51
+ input: 'note = "He said \\"hello\\""',
52
+ expected: { note: 'He said \\"hello\\"' },
53
+ },
54
+ {
55
+ name: "array values with []",
56
+ input: "proxy[] = http://proxy1.local\nproxy[] = http://proxy2.local",
57
+ expected: { proxy: ["http://proxy1.local", "http://proxy2.local"] },
58
+ },
59
+ {
60
+ name: "single array value",
61
+ input: "registry[] = https://registry.example.com",
62
+ expected: { registry: ["https://registry.example.com"] },
63
+ },
64
+ {
65
+ name: "scoped registry",
66
+ input: "@myscope:registry = https://custom.example.com",
67
+ expected: { "@myscope:registry": "https://custom.example.com" },
68
+ },
69
+ {
70
+ name: "URI-fragment auth config",
71
+ input: "//registry.npmjs.org/:_authToken = abc123xyz",
72
+ expected: { "//registry.npmjs.org/:_authToken": "abc123xyz" },
73
+ },
74
+ {
75
+ name: "scoped auth with quoted token",
76
+ input: '//registry.example.com/:_authToken = "secret-token"',
77
+ expected: { "//registry.example.com/:_authToken": "secret-token" },
78
+ },
79
+ {
80
+ name: "extra whitespace around =",
81
+ input: " key = value ",
82
+ expected: { key: "value" },
83
+ },
84
+ {
85
+ name: "empty lines and mixed whitespace",
86
+ input: "\n\nregistry=https://example.com\n\n \nproxy=http://local\n",
87
+ expected: {
88
+ registry: "https://example.com",
89
+ proxy: "http://local",
90
+ },
91
+ },
92
+ {
93
+ name: "env var substitution syntax",
94
+ input: "cache = ${HOME}/.npm-cache",
95
+ expected: { cache: "${HOME}/.npm-cache" },
96
+ },
97
+ {
98
+ name: "env var with default",
99
+ input: 'prefix = "${NPM_PREFIX:-/usr/local}"',
100
+ expected: { prefix: "${NPM_PREFIX:-/usr/local}" },
101
+ },
102
+ {
103
+ name: "unicode in value",
104
+ input: "description = 日本語パッケージ",
105
+ expected: { description: "日本語パッケージ" },
106
+ },
107
+ {
108
+ name: "emoji in value",
109
+ input: 'note = "Test 🚀 emoji"',
110
+ expected: { note: "Test 🚀 emoji" },
111
+ },
112
+ {
113
+ name: "unicode key (unusual but valid)",
114
+ input: "キー = 値",
115
+ expected: { キー: "値" },
116
+ },
117
+ {
118
+ name: "mixed unicode and ascii",
119
+ input: "registry = https://例え.jp/npm",
120
+ expected: { registry: "https://例え.jp/npm" },
121
+ },
122
+ {
123
+ name: "path with special chars",
124
+ input: "prefix = /usr/local/bin:$HOME/bin",
125
+ expected: { prefix: "/usr/local/bin:$HOME/bin" },
126
+ },
127
+ {
128
+ name: "url with query params",
129
+ input: "registry = https://example.com/npm?token=abc&scope=private",
130
+ expected: { registry: "https://example.com/npm?token=abc&scope=private" },
131
+ },
132
+ ];
133
+
134
+ const MALICIOUS_NPMRC_CASES = [
135
+ {
136
+ name: "command injection via git config",
137
+ input: "git = ./pwn.sh\nregistry=https://registry.npmjs.org/",
138
+ expected: { git: "./pwn.sh", registry: "https://registry.npmjs.org/" },
139
+ note: "Parser returns raw value; filtering happens elsewhere",
140
+ },
141
+ {
142
+ name: "script-shell injection",
143
+ input: "script-shell = /bin/bash -c 'malicious'",
144
+ expected: { "script-shell": "/bin/bash -c 'malicious'" },
145
+ },
146
+ {
147
+ name: "path traversal in value",
148
+ input: "cache = ../../../etc/passwd",
149
+ expected: { cache: "../../../etc/passwd" },
150
+ },
151
+ {
152
+ name: "null byte injection attempt",
153
+ input: "key = value\u0000injection",
154
+ expected: { key: "value\u0000injection" },
155
+ },
156
+ {
157
+ name: "newline injection in value",
158
+ input: "key = value\ninjected = true",
159
+ expected: { key: "value", injected: "true" },
160
+ },
161
+ {
162
+ name: "very long value (potential DoS)",
163
+ input: `longkey = ${"a".repeat(100000)}`,
164
+ expected: { longkey: "a".repeat(100000) },
165
+ },
166
+ {
167
+ name: "many repeated keys",
168
+ input: Array(1000).fill("duplicate = value").join("\n"),
169
+ expected: { duplicate: "value" },
170
+ },
171
+ {
172
+ name: "proxy with credentials",
173
+ input: "proxy = http://user:pass@evil.com:8080",
174
+ expected: { proxy: "http://user:pass@evil.com:8080" },
175
+ },
176
+ {
177
+ name: "cafile pointing to malicious cert",
178
+ input: "cafile = /tmp/evil-cert.pem",
179
+ expected: { cafile: "/tmp/evil-cert.pem" },
180
+ },
181
+ {
182
+ name: "node-options with code execution flags",
183
+ input: 'node-options = "--eval "require("child_process").execSync("id")""',
184
+ expected: {
185
+ "node-options": '--eval "require("child_process").execSync("id")"',
186
+ },
187
+ },
188
+ ];
189
+
190
+ const EDGE_CASE_NPMRC = [
191
+ {
192
+ name: "empty input",
193
+ input: "",
194
+ expected: {},
195
+ },
196
+ {
197
+ name: "only comments",
198
+ input: "# comment\n; another\n \n",
199
+ expected: {},
200
+ },
201
+ {
202
+ name: "line without equals sign",
203
+ input: "invalid-line\nregistry=https://example.com",
204
+ expected: { registry: "https://example.com" },
205
+ },
206
+ {
207
+ name: "key without value",
208
+ input: "emptykey =\nvalid = value",
209
+ expected: { emptykey: "", valid: "value" },
210
+ },
211
+ {
212
+ name: "value without key (should skip)",
213
+ input: "=novalue\nregistry=https://example.com",
214
+ expected: { registry: "https://example.com" },
215
+ },
216
+ {
217
+ name: "multiple equals in line",
218
+ input: "a=b=c=d",
219
+ expected: { a: "b=c=d" },
220
+ },
221
+ {
222
+ name: "tabs as whitespace",
223
+ input: "key\t=\tvalue",
224
+ expected: { key: "value" },
225
+ },
226
+ {
227
+ name: "mixed line endings",
228
+ input: "win=1\r\nunix=2\rmac=3",
229
+ expected: { win: "1", unix: "2", mac: "3" },
230
+ },
231
+ {
232
+ name: "unmatched quotes (treated as literal)",
233
+ input: 'broken = "unclosed quote',
234
+ expected: { broken: '"unclosed quote' },
235
+ },
236
+ {
237
+ name: "array with mixed quoted/unquoted",
238
+ input: 'items[] = "quoted"\nitems[] = unquoted',
239
+ expected: { items: ["quoted", "unquoted"] },
240
+ },
241
+ ];
242
+
243
+ const REDOS_RESILIENCE_TESTS = [
244
+ {
245
+ name: "very long key name",
246
+ input: `${"a".repeat(50000)} = value`,
247
+ },
248
+ {
249
+ name: "many array entries",
250
+ input: Array(10000).fill("list[] = item").join("\n"),
251
+ },
252
+ {
253
+ name: "repeated = characters",
254
+ input: `key = ${"=".repeat(50000)}`,
255
+ },
256
+ {
257
+ name: "deeply nested looking scoped key",
258
+ input: `${"/".repeat(1000)}registry.example.com${"/".repeat(1000)}:token = abc`,
259
+ },
260
+ {
261
+ name: "alternating comment/value lines",
262
+ input: Array(5000).fill("# comment\nkey=value").join("\n"),
263
+ },
264
+ ];
265
+
266
+ // biome-ignore-end lint/suspicious/noTemplateCurlyInString: Test data
267
+
268
+ describe("npmrc Parser - Valid Cases", () => {
269
+ for (const tc of VALID_NPMRC_CASES) {
270
+ test(`should parse: ${tc.name}`, () => {
271
+ const result = parseNpmrc(tc.input);
272
+ assert.deepStrictEqual(result, tc.expected, `Failed for: ${tc.name}`);
273
+ });
274
+ }
275
+ });
276
+
277
+ describe("npmrc Parser - Malicious Inputs", () => {
278
+ for (const tc of MALICIOUS_NPMRC_CASES) {
279
+ test(`should safely parse (no crash): ${tc.name}`, () => {
280
+ let result;
281
+ assert.doesNotThrow(() => {
282
+ result = parseNpmrc(tc.input);
283
+ }, `Parser threw on: ${tc.name}`);
284
+ assert.deepStrictEqual(
285
+ result,
286
+ tc.expected,
287
+ `Output mismatch for: ${tc.name}`,
288
+ );
289
+ });
290
+ }
291
+ });
292
+
293
+ describe("npmrc Parser - Edge Cases", () => {
294
+ for (const tc of EDGE_CASE_NPMRC) {
295
+ test(`should handle: ${tc.name}`, () => {
296
+ const result = parseNpmrc(tc.input);
297
+ assert.deepStrictEqual(result, tc.expected, `Failed for: ${tc.name}`);
298
+ });
299
+ }
300
+ });
301
+
302
+ describe("npmrc Parser - ReDoS Resilience", () => {
303
+ for (const tc of REDOS_RESILIENCE_TESTS) {
304
+ test(`should handle quickly: ${tc.name}`, () => {
305
+ const start = Date.now();
306
+ let result;
307
+ assert.doesNotThrow(() => {
308
+ result = parseNpmrc(tc.input);
309
+ });
310
+ const duration = Date.now() - start;
311
+ assert.ok(
312
+ duration < 100,
313
+ `Parsing took too long (${duration}ms): ${tc.name}`,
314
+ );
315
+ assert.ok(
316
+ typeof result === "object" && result !== null,
317
+ `Should return object for: ${tc.name}`,
318
+ );
319
+ });
320
+ }
321
+ });
322
+
323
+ describe("npmrc Parser - Unicode Handling", () => {
324
+ test("should preserve unicode characters", () => {
325
+ const input = "desc = 测试🔐\nregistry = https://例え.日本/";
326
+ const result = parseNpmrc(input);
327
+ assert.strictEqual(result.desc, "测试🔐");
328
+ assert.strictEqual(result.registry, "https://例え.日本/");
329
+ });
330
+
331
+ test("should handle unicode in keys", () => {
332
+ const input = "キー🔑 = 値🔐";
333
+ const result = parseNpmrc(input);
334
+ assert.strictEqual(result["キー🔑"], "値🔐");
335
+ });
336
+ });
337
+
338
+ describe("npmrc Parser - Security Separation", () => {
339
+ test("parser does not filter - that's caller's responsibility", () => {
340
+ const malicious = "git = ./pwn.sh\nregistry = https://safe.com";
341
+ const result = parseNpmrc(malicious);
342
+ assert.strictEqual(result.git, "./pwn.sh");
343
+ assert.strictEqual(result.registry, "https://safe.com");
344
+ const DANGEROUS = new Set(["git", "script-shell"]);
345
+ const safe = Object.fromEntries(
346
+ Object.entries(result).filter(([key]) => !DANGEROUS.has(key)),
347
+ );
348
+ assert.strictEqual(safe.git, undefined);
349
+ assert.strictEqual(safe.registry, "https://safe.com");
350
+ });
351
+ });
352
+
353
+ const VALID_ENV_CASES = [
354
+ {
355
+ name: "basic npm_config_ prefix",
356
+ env: { npm_config_registry: "https://example.com" },
357
+ expected: { registry: "https://example.com" },
358
+ },
359
+ {
360
+ name: "case-insensitive prefix",
361
+ env: { NPM_CONFIG_PROXY: "http://proxy.local" },
362
+ expected: { proxy: "http://proxy.local" },
363
+ },
364
+ {
365
+ name: "dash-to-underscore conversion (user provides underscore)",
366
+ env: { npm_config_allow_same_version: "true" },
367
+ expected: { allow_same_version: "true" },
368
+ },
369
+ {
370
+ name: "scoped registry auth preserves URL case",
371
+ env: { "npm_config_//registry.example.com/:_authToken": "secret123" },
372
+ expected: { "//registry.example.com/:_authToken": "secret123" },
373
+ },
374
+ {
375
+ name: "scoped package registry preserves scope case",
376
+ env: { "NPM_CONFIG_@MyScope:registry": "https://custom.example.com" },
377
+ expected: { "@MyScope:registry": "https://custom.example.com" },
378
+ },
379
+ {
380
+ name: "simple keys are lowercased regardless of env var case",
381
+ env: { NPM_CONFIG_REGISTRY: "https://example.com" },
382
+ expected: { registry: "https://example.com" },
383
+ },
384
+ {
385
+ name: "mixed: simple + scoped keys",
386
+ env: {
387
+ NPM_CONFIG_REGISTRY: "https://public.com",
388
+ "npm_config_//private.example.com/:_authToken": "token123",
389
+ },
390
+ expected: {
391
+ registry: "https://public.com",
392
+ "//private.example.com/:_authToken": "token123",
393
+ },
394
+ },
395
+ {
396
+ name: "boolean flag with empty value → true",
397
+ env: { npm_config_foo: "" },
398
+ expected: { foo: "true" },
399
+ },
400
+ {
401
+ name: "boolean flag with undefined value → true",
402
+ env: { npm_config_bar: undefined },
403
+ expected: { bar: "true" },
404
+ },
405
+ {
406
+ name: "multiple config vars",
407
+ env: {
408
+ npm_config_registry: "https://a.com",
409
+ npm_config_proxy: "http://b.com",
410
+ npm_config_cache: "/tmp/cache",
411
+ },
412
+ expected: {
413
+ registry: "https://a.com",
414
+ proxy: "http://b.com",
415
+ cache: "/tmp/cache",
416
+ },
417
+ },
418
+ {
419
+ name: "unicode values preserved",
420
+ env: { npm_config_description: "测试🔐" },
421
+ expected: { description: "测试🔐" },
422
+ },
423
+ {
424
+ name: "basic pnpm_config_ prefix",
425
+ env: { pnpm_config_registry: "https://pnpm-registry.example.com" },
426
+ expected: { registry: "https://pnpm-registry.example.com" },
427
+ },
428
+ {
429
+ name: "case-insensitive PNPM_CONFIG_ prefix",
430
+ env: { PNPM_CONFIG_PROXY: "http://proxy.local" },
431
+ expected: { proxy: "http://proxy.local" },
432
+ },
433
+ {
434
+ name: "pnpm_config_ simple key lowercased",
435
+ env: { PNPM_CONFIG_STORE_DIR: "/custom/store" },
436
+ expected: { store_dir: "/custom/store" },
437
+ },
438
+ {
439
+ name: "pnpm_config_ boolean flag with empty value → true",
440
+ env: { pnpm_config_shamefully_hoist: "" },
441
+ expected: { shamefully_hoist: "true" },
442
+ },
443
+ {
444
+ name: "pnpm_config_ overrides npm_config_ for same key",
445
+ env: {
446
+ npm_config_registry: "https://npm-registry.com",
447
+ pnpm_config_registry: "https://pnpm-registry.com",
448
+ },
449
+ expected: { registry: "https://pnpm-registry.com" },
450
+ },
451
+ {
452
+ name: "pnpm_config_ and npm_config_ for different keys are both included",
453
+ env: {
454
+ npm_config_cache: "/npm-cache",
455
+ pnpm_config_store_dir: "/pnpm-store",
456
+ },
457
+ expected: { cache: "/npm-cache", store_dir: "/pnpm-store" },
458
+ },
459
+ {
460
+ name: "empty config key after pnpm_config_ prefix ignored",
461
+ env: { pnpm_config_: "value" },
462
+ expected: {},
463
+ },
464
+ ];
465
+
466
+ const EDGE_ENV_CASES = [
467
+ {
468
+ name: "empty env object",
469
+ env: {},
470
+ expected: {},
471
+ },
472
+ {
473
+ name: "non-npm env vars ignored",
474
+ env: { PATH: "/usr/bin", HOME: "/home/user", npm_config_foo: "bar" },
475
+ expected: { foo: "bar" },
476
+ },
477
+ {
478
+ name: "prefix substring not matched",
479
+ env: { my_npm_config_foo: "bar" },
480
+ expected: {},
481
+ },
482
+ {
483
+ name: "empty config key after prefix",
484
+ env: { npm_config_: "value" },
485
+ expected: {},
486
+ },
487
+ {
488
+ name: "value with special chars preserved",
489
+ env: { npm_config_prefix: "/path:with:colons$VAR" },
490
+ expected: { prefix: "/path:with:colons$VAR" },
491
+ },
492
+ ];
493
+
494
+ describe("parseNpmrcFromEnv - Valid Cases", () => {
495
+ for (const tc of VALID_ENV_CASES) {
496
+ test(`should parse: ${tc.name}`, () => {
497
+ const result = parseNpmrcFromEnv(tc.env);
498
+ assert.deepStrictEqual(result, tc.expected, `Failed for: ${tc.name}`);
499
+ });
500
+ }
501
+ });
502
+
503
+ describe("parseNpmrcFromEnv - Edge Cases", () => {
504
+ for (const tc of EDGE_ENV_CASES) {
505
+ test(`should handle: ${tc.name}`, () => {
506
+ const result = parseNpmrcFromEnv(tc.env);
507
+ assert.deepStrictEqual(result, tc.expected, `Failed for: ${tc.name}`);
508
+ });
509
+ }
510
+ });
511
+
512
+ describe("parseNpmrcFromEnv - Security", () => {
513
+ test("parser returns raw values - filtering is caller's responsibility", () => {
514
+ const maliciousEnv = {
515
+ npm_config_git: "./pwn.sh",
516
+ npm_config_registry: "https://safe.com",
517
+ };
518
+ const result = parseNpmrcFromEnv(maliciousEnv);
519
+ assert.strictEqual(result.git, "./pwn.sh");
520
+ assert.strictEqual(result.registry, "https://safe.com");
521
+ const DANGEROUS = new Set(["git", "script-shell", "shell"]);
522
+ const safe = Object.fromEntries(
523
+ Object.entries(result).filter(([key]) => !DANGEROUS.has(key)),
524
+ );
525
+ assert.strictEqual(safe.git, undefined);
526
+ assert.strictEqual(safe.registry, "https://safe.com");
527
+ });
528
+ });
@@ -154,11 +154,6 @@ paths:
154
154
  required: false
155
155
  schema:
156
156
  $ref: '#/components/schemas/CDXGEN/properties/exclude'
157
- - name: includeFormulation
158
- in: query
159
- required: false
160
- schema:
161
- $ref: '#/components/schemas/CDXGEN/properties/includeFormulation'
162
157
  - name: includeCrypto
163
158
  in: query
164
159
  required: false
@@ -269,7 +264,7 @@ components:
269
264
  specVersion:
270
265
  type: string
271
266
  description: CycloneDX Specification version to use
272
- default: "1.6"
267
+ default: "1.7"
273
268
  filter:
274
269
  type: array
275
270
  items:
@@ -303,10 +298,6 @@ components:
303
298
  items:
304
299
  type: string
305
300
  description: Additional glob pattern(s) to ignore
306
- includeFormulation:
307
- type: boolean
308
- description: Generate formulation section with git metadata and build tools. Use with caution, since there is a risk of exposure of sensitive data such as secrets.
309
- default: false
310
301
  includeCrypto:
311
302
  type: boolean
312
303
  description: Include crypto libraries as components.