@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,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
|
+
});
|
package/lib/server/openapi.yaml
CHANGED
|
@@ -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.
|
|
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.
|