@cdxgen/cdxgen 13.1.0 → 13.3.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 +32 -15
- package/bin/cdxgen.js +243 -122
- package/bin/evinse.js +7 -0
- package/bin/sign.js +88 -14
- package/bin/tracebom.js +64 -4
- package/bin/validate.js +29 -7
- package/bin/verify.js +79 -39
- package/data/crypto-oid.json +88 -12
- package/data/remediations.json +47 -0
- package/data/rules/ai-bom.yaml +78 -0
- package/data/rules/cargo-rust.yaml +132 -0
- package/data/rules/mcp-servers.yaml +122 -0
- package/lib/audit/targets.js +6 -1
- package/lib/cli/bomAssembly.js +117 -15
- package/lib/cli/cliOptions.js +93 -1
- package/lib/cli/index.js +235 -5
- package/lib/cli/jsBom.js +108 -26
- package/lib/cli/jvmBom.js +68 -12
- package/lib/cli/managedBom.js +264 -41
- package/lib/cli/nativeBom.js +923 -15
- package/lib/core/activity.js +120 -2
- package/lib/core/env.js +44 -7
- package/lib/core/fs.js +174 -6
- package/lib/core/metadataProperties.js +97 -0
- package/lib/core/projectConfig.js +31 -4
- package/lib/core/userAgent.js +151 -0
- package/lib/ecosystems/bunutils.js +269 -101
- package/lib/ecosystems/cargoCache.js +364 -0
- package/lib/ecosystems/caxa.js +447 -7
- package/lib/ecosystems/cmakeResolver.js +66 -0
- package/lib/ecosystems/cppEvidence.js +104 -33
- package/lib/ecosystems/ecosystems.js +260 -99
- package/lib/ecosystems/golem.js +11 -1
- package/lib/ecosystems/gradleutils.js +185 -9
- package/lib/ecosystems/jsEvidence.js +19 -20
- package/lib/ecosystems/kosi.js +1263 -0
- package/lib/ecosystems/npmutils.js +110 -9
- package/lib/ecosystems/parsers-crystal.js +179 -0
- package/lib/ecosystems/parsers-dotnet.js +120 -49
- package/lib/ecosystems/parsers-elm.js +816 -0
- package/lib/ecosystems/parsers-gleam.js +1 -1
- package/lib/ecosystems/parsers-go.js +47 -9
- package/lib/ecosystems/parsers-js.js +171 -50
- package/lib/ecosystems/parsers-julia.js +268 -0
- package/lib/ecosystems/parsers-lua.js +218 -0
- package/lib/ecosystems/parsers-misc.js +919 -161
- package/lib/ecosystems/parsers-nim.js +221 -0
- package/lib/ecosystems/parsers-opam.js +180 -0
- package/lib/ecosystems/parsers-perl.js +277 -0
- package/lib/ecosystems/parsers-python.js +27 -15
- package/lib/ecosystems/parsers-r.js +148 -0
- package/lib/ecosystems/parsers-rebar.js +411 -0
- package/lib/ecosystems/parsers-rust.js +954 -33
- package/lib/ecosystems/parsers-spack.js +204 -0
- package/lib/ecosystems/parsers-stack.js +264 -0
- package/lib/ecosystems/parsers-terraform.js +194 -0
- package/lib/ecosystems/rubyutils.js +1426 -166
- package/lib/ecosystems/rusi.js +158 -7
- package/lib/evinser/evinser.js +547 -129
- package/lib/evinser/swiftsem.js +1914 -302
- package/lib/helpers/agenticToolutils.js +925 -0
- package/lib/helpers/bomSigner.js +652 -221
- package/lib/inventory/agentFormulationParser.js +57 -4
- package/lib/inventory/aiCollector.js +504 -10
- package/lib/inventory/aiInventory.js +12 -0
- package/lib/inventory/analyzer.js +488 -30
- package/lib/inventory/astWorker.js +6 -0
- package/lib/inventory/atomUtils.js +568 -31
- package/lib/inventory/auditCategories.js +8 -0
- package/lib/inventory/cbomutils.js +30 -0
- package/lib/inventory/cdxrs.js +110 -14
- package/lib/inventory/communityAiConfigParser.js +215 -2
- package/lib/inventory/depsUtils.js +183 -11
- package/lib/inventory/display.js +4 -5
- package/lib/inventory/dosai.js +627 -13
- package/lib/inventory/dynamic.js +39 -1
- package/lib/inventory/envcontext.js +9 -6
- package/lib/inventory/evidenceUtils.js +26 -5
- package/lib/inventory/fetchBatch.js +190 -19
- package/lib/inventory/fetchRate.js +74 -11
- package/lib/inventory/forgeEnricher.js +3 -2
- package/lib/inventory/jvmToolEnv.js +6 -5
- package/lib/inventory/mcpConfigParser.js +166 -2
- package/lib/inventory/mcpDiscovery.js +11 -0
- package/lib/inventory/mcpServerManifest.js +389 -0
- package/lib/inventory/mcpbBundle.js +419 -0
- package/lib/inventory/plugins.js +3 -0
- package/lib/inventory/protobom.js +62 -89
- package/lib/inventory/protobomLoader.js +4 -0
- package/lib/inventory/purl.js +152 -3
- package/lib/inventory/rustFormulationParser.js +1 -1
- package/lib/inventory/sbomDocument.js +2 -70
- package/lib/inventory/source.js +47 -23
- package/lib/inventory/stylePackageRefs.js +41 -0
- package/lib/inventory/svelteUtils.js +524 -0
- package/lib/inventory/traceRunner.js +210 -21
- package/lib/managers/binary.js +115 -24
- package/lib/managers/docker.js +440 -20
- package/lib/managers/oci.js +39 -1
- package/lib/parsers/cmakeLists.js +452 -0
- package/lib/parsers/huggingfaceUtils.js +15 -7
- package/lib/parsers/largeJson.js +454 -0
- package/lib/parsers/npmrc.js +19 -1
- package/lib/parsers/onnx.js +294 -0
- package/lib/parsers/safetensors.js +114 -0
- package/lib/stages/postgen/introspection/markers.js +29 -6
- package/lib/stages/postgen/postgen.js +237 -16
- package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
- package/lib/validator/bomValidator.js +6 -16
- package/lib/validator/index.js +48 -10
- package/lib/validator/reporters/console.js +3 -0
- package/lib/validator/reporters/json.js +1 -0
- package/lib/validator/reporters/sarif.js +7 -0
- package/package.json +27 -27
- package/types/lib/audit/targets.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +2 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +33 -1
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +2 -2
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts +23 -0
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts +33 -0
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts +88 -8
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/activity.d.ts.map +1 -1
- package/types/lib/core/env.d.ts +21 -0
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +55 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/metadataProperties.d.ts +39 -0
- package/types/lib/core/metadataProperties.d.ts.map +1 -0
- package/types/lib/core/projectConfig.d.ts +6 -2
- package/types/lib/core/projectConfig.d.ts.map +1 -1
- package/types/lib/core/userAgent.d.ts +56 -0
- package/types/lib/core/userAgent.d.ts.map +1 -0
- package/types/lib/ecosystems/bunutils.d.ts +12 -5
- package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
- package/types/lib/ecosystems/cargoCache.d.ts +75 -0
- package/types/lib/ecosystems/cargoCache.d.ts.map +1 -0
- package/types/lib/ecosystems/caxa.d.ts +14 -0
- package/types/lib/ecosystems/caxa.d.ts.map +1 -1
- package/types/lib/ecosystems/cmakeResolver.d.ts +23 -0
- package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts +2 -2
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts +15 -1
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +40 -4
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/jsEvidence.d.ts +5 -2
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts +86 -0
- package/types/lib/ecosystems/kosi.d.ts.map +1 -0
- package/types/lib/ecosystems/npmutils.d.ts +42 -0
- package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-crystal.d.ts +27 -0
- package/types/lib/ecosystems/parsers-crystal.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts +19 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-elm.d.ts +79 -0
- package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-go.d.ts +10 -0
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +10 -4
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-julia.d.ts +57 -0
- package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-lua.d.ts +50 -0
- package/types/lib/ecosystems/parsers-lua.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-misc.d.ts +90 -3
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-nim.d.ts +43 -0
- package/types/lib/ecosystems/parsers-nim.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-opam.d.ts +31 -0
- package/types/lib/ecosystems/parsers-opam.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-perl.d.ts +36 -0
- package/types/lib/ecosystems/parsers-perl.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-r.d.ts +35 -0
- package/types/lib/ecosystems/parsers-r.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-rebar.d.ts +42 -0
- package/types/lib/ecosystems/parsers-rebar.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-rust.d.ts +199 -0
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-spack.d.ts +33 -0
- package/types/lib/ecosystems/parsers-spack.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-stack.d.ts +35 -0
- package/types/lib/ecosystems/parsers-stack.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-terraform.d.ts +34 -0
- package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -0
- package/types/lib/ecosystems/pythonutils.d.ts +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts +143 -2
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts +13 -0
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +81 -2
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/swiftsem.d.ts +280 -15
- package/types/lib/evinser/swiftsem.d.ts.map +1 -1
- package/types/lib/helpers/agenticToolutils.d.ts +87 -0
- package/types/lib/helpers/agenticToolutils.d.ts.map +1 -0
- package/types/lib/helpers/bomSigner.d.ts +106 -13
- package/types/lib/helpers/bomSigner.d.ts.map +1 -1
- package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/aiInventory.d.ts.map +1 -1
- package/types/lib/inventory/analyzer.d.ts.map +1 -1
- package/types/lib/inventory/atomUtils.d.ts +145 -3
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/auditCategories.d.ts +4 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -1
- package/types/lib/inventory/cbomutils.d.ts.map +1 -1
- package/types/lib/inventory/cdxrs.d.ts +8 -3
- package/types/lib/inventory/cdxrs.d.ts.map +1 -1
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/depsUtils.d.ts +15 -0
- package/types/lib/inventory/depsUtils.d.ts.map +1 -1
- package/types/lib/inventory/display.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +87 -1
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dynamic.d.ts.map +1 -1
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts +34 -0
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/fetchRate.d.ts +9 -6
- package/types/lib/inventory/fetchRate.d.ts.map +1 -1
- package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
- package/types/lib/inventory/jvmToolEnv.d.ts +5 -4
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -1
- package/types/lib/inventory/mcpServerManifest.d.ts +19 -0
- package/types/lib/inventory/mcpServerManifest.d.ts.map +1 -0
- package/types/lib/inventory/mcpbBundle.d.ts +19 -0
- package/types/lib/inventory/mcpbBundle.d.ts.map +1 -0
- package/types/lib/inventory/plugins.d.ts.map +1 -1
- package/types/lib/inventory/protobom.d.ts +18 -13
- package/types/lib/inventory/protobom.d.ts.map +1 -1
- package/types/lib/inventory/protobomLoader.d.ts.map +1 -1
- package/types/lib/inventory/purl.d.ts +71 -2
- package/types/lib/inventory/purl.d.ts.map +1 -1
- package/types/lib/inventory/sbomDocument.d.ts.map +1 -1
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/stylePackageRefs.d.ts +17 -0
- package/types/lib/inventory/stylePackageRefs.d.ts.map +1 -0
- package/types/lib/inventory/svelteUtils.d.ts +124 -0
- package/types/lib/inventory/svelteUtils.d.ts.map +1 -0
- package/types/lib/inventory/traceRunner.d.ts +47 -1
- package/types/lib/inventory/traceRunner.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts +30 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +29 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +14 -0
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/parsers/cmakeLists.d.ts +76 -0
- package/types/lib/parsers/cmakeLists.d.ts.map +1 -0
- package/types/lib/parsers/huggingfaceUtils.d.ts +8 -1
- package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
- package/types/lib/parsers/largeJson.d.ts +52 -0
- package/types/lib/parsers/largeJson.d.ts.map +1 -0
- package/types/lib/parsers/npmrc.d.ts +5 -0
- package/types/lib/parsers/npmrc.d.ts.map +1 -1
- package/types/lib/parsers/onnx.d.ts +28 -0
- package/types/lib/parsers/onnx.d.ts.map +1 -0
- package/types/lib/parsers/safetensors.d.ts +22 -0
- package/types/lib/parsers/safetensors.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts +3 -2
- package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts +33 -0
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/index.d.ts +10 -1
- package/types/lib/validator/index.d.ts.map +1 -1
- package/types/lib/validator/reporters/console.d.ts.map +1 -1
- package/types/lib/validator/reporters/json.d.ts.map +1 -1
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -1
package/lib/inventory/purl.js
CHANGED
|
@@ -53,15 +53,30 @@ export function purlTypeIsRegisteredString(purlString) {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
|
-
* Encode a string for
|
|
57
|
-
*
|
|
56
|
+
* Encode a string for inclusion in a hand-written PackageURL string,
|
|
57
|
+
* percent-encoding every character the purl grammar does not allow literally,
|
|
58
|
+
* while preserving already-encoded `%40` sequences and keeping `:` and `/`
|
|
59
|
+
* unencoded.
|
|
60
|
+
*
|
|
61
|
+
* `encodeURIComponent` alone leaves `!`, `'`, `(`, `)` and `*` literal, and the
|
|
62
|
+
* purl grammar requires them escaped, so they are escaped here as well.
|
|
63
|
+
*
|
|
64
|
+
* Only for comparing against or assembling purl text. Never pass the result to
|
|
65
|
+
* `build()`, `new Purl()` or one of the helpers in this module: they escape
|
|
66
|
+
* their inputs themselves, and escaping twice turns `+` into `%252B`.
|
|
58
67
|
*
|
|
59
68
|
* @param {string} s String to encode
|
|
60
69
|
* @returns {string} Encoded string suitable for use in a PackageURL component
|
|
61
70
|
*/
|
|
62
71
|
export function encodeForPurl(s) {
|
|
63
72
|
return s && !s.includes("%40")
|
|
64
|
-
? encodeURIComponent(s)
|
|
73
|
+
? encodeURIComponent(s)
|
|
74
|
+
.replace(
|
|
75
|
+
/[!'()*]/g,
|
|
76
|
+
(c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`,
|
|
77
|
+
)
|
|
78
|
+
.replace(/%3A/g, ":")
|
|
79
|
+
.replace(/%2F/g, "/")
|
|
65
80
|
: s;
|
|
66
81
|
}
|
|
67
82
|
|
|
@@ -328,6 +343,140 @@ export function isValidPurl(candidate) {
|
|
|
328
343
|
}
|
|
329
344
|
}
|
|
330
345
|
|
|
346
|
+
/**
|
|
347
|
+
* Split a purl into its components without requiring it to be well-formed.
|
|
348
|
+
*
|
|
349
|
+
* `Purl.parse` rejects a purl whose parts are not already escaped, which is the
|
|
350
|
+
* case this exists to handle: a `bom-ref` (cdxgen writes the decoded form of the
|
|
351
|
+
* purl there), a `go mod graph` token such as `github.com/Azure/go-autorest@v14.2.0+incompatible`,
|
|
352
|
+
* or a purl from a third-party document that left a `+` or a `://` unescaped.
|
|
353
|
+
* The separators (`:`, `/`, `@`, `?`, `#`, `&`, `=`) are structural and are read
|
|
354
|
+
* positionally; everything between them is decoded and handed back raw, ready
|
|
355
|
+
* for `build` to escape correctly.
|
|
356
|
+
*
|
|
357
|
+
* The version is taken after the last `@` of the path, so a scoped npm name
|
|
358
|
+
* (`@scope/name@1.0.0`) and a composer branch version (`dev-feature/x`) both
|
|
359
|
+
* split correctly.
|
|
360
|
+
*
|
|
361
|
+
* @param {string} purl Purl to split
|
|
362
|
+
* @returns {{type: string, namespace: (string|null), name: string, version: (string|null), qualifiers: (Object|null), subpath: (string|null)}|null} Parts, or null when the purl has no type and name
|
|
363
|
+
*/
|
|
364
|
+
export function splitPurlLoose(purl) {
|
|
365
|
+
if (typeof purl !== "string" || !purl.startsWith("pkg:")) {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
const decode = (value) => {
|
|
369
|
+
try {
|
|
370
|
+
return decodeURIComponent(value);
|
|
371
|
+
} catch {
|
|
372
|
+
return value;
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
let rest = purl.slice("pkg:".length).replace(/^\/+/, "");
|
|
376
|
+
let subpath = null;
|
|
377
|
+
const hash = rest.indexOf("#");
|
|
378
|
+
if (hash !== -1) {
|
|
379
|
+
subpath = decode(rest.slice(hash + 1)) || null;
|
|
380
|
+
rest = rest.slice(0, hash);
|
|
381
|
+
}
|
|
382
|
+
let qualifiers = null;
|
|
383
|
+
const query = rest.indexOf("?");
|
|
384
|
+
if (query !== -1) {
|
|
385
|
+
qualifiers = {};
|
|
386
|
+
for (const pair of rest.slice(query + 1).split("&")) {
|
|
387
|
+
const equals = pair.indexOf("=");
|
|
388
|
+
if (equals <= 0) {
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
qualifiers[pair.slice(0, equals)] = decode(pair.slice(equals + 1));
|
|
392
|
+
}
|
|
393
|
+
if (!Object.keys(qualifiers).length) {
|
|
394
|
+
qualifiers = null;
|
|
395
|
+
}
|
|
396
|
+
rest = rest.slice(0, query);
|
|
397
|
+
}
|
|
398
|
+
const slash = rest.indexOf("/");
|
|
399
|
+
if (slash <= 0) {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
const type = rest.slice(0, slash);
|
|
403
|
+
let path = rest.slice(slash + 1);
|
|
404
|
+
let version = null;
|
|
405
|
+
const at = path.lastIndexOf("@");
|
|
406
|
+
if (at > 0 && path[at - 1] !== "/") {
|
|
407
|
+
version = decode(path.slice(at + 1)) || null;
|
|
408
|
+
path = path.slice(0, at);
|
|
409
|
+
}
|
|
410
|
+
const segments = path.split("/").filter(Boolean).map(decode);
|
|
411
|
+
const name = segments.pop();
|
|
412
|
+
if (!name) {
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
type,
|
|
417
|
+
namespace: segments.length ? segments.join("/") : null,
|
|
418
|
+
name,
|
|
419
|
+
version,
|
|
420
|
+
qualifiers,
|
|
421
|
+
subpath,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Parse a purl string that may carry unescaped reserved characters.
|
|
427
|
+
*
|
|
428
|
+
* A well-formed purl is parsed strictly, so its components come back exactly as
|
|
429
|
+
* cdx-purl reads them. Anything else is split structurally by
|
|
430
|
+
* {@link splitPurlLoose}. Use this where the input is known to be a decoded or
|
|
431
|
+
* hand-written purl (a `bom-ref`, a workspace reference, a package manager's
|
|
432
|
+
* `name@version` token), never to accept an invalid purl into a `purl` field:
|
|
433
|
+
* pass the parts to `build()` instead, which escapes them.
|
|
434
|
+
*
|
|
435
|
+
* @param {string} purlString Purl or decoded purl
|
|
436
|
+
* @returns {{type: string, namespace: (string|null), name: string, version: (string|null), qualifiers: (Object|null), subpath: (string|null)}|null} Raw components, or null when nothing purl-shaped remains
|
|
437
|
+
*/
|
|
438
|
+
export function parsePurlLoose(purlString) {
|
|
439
|
+
if (typeof purlString !== "string" || !purlString.startsWith("pkg:")) {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
try {
|
|
443
|
+
const parsed = Purl.parse(purlString);
|
|
444
|
+
return {
|
|
445
|
+
type: parsed.type,
|
|
446
|
+
namespace: parsed.namespace || null,
|
|
447
|
+
name: parsed.name,
|
|
448
|
+
version: parsed.version || null,
|
|
449
|
+
qualifiers: parsed.qualifiers || null,
|
|
450
|
+
subpath: parsed.subpath || null,
|
|
451
|
+
};
|
|
452
|
+
} catch (err) {
|
|
453
|
+
if (!err?.code?.startsWith("E_")) {
|
|
454
|
+
throw err;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return splitPurlLoose(purlString);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Turn a decoded or hand-written purl into a canonical, correctly escaped one.
|
|
462
|
+
*
|
|
463
|
+
* `bom-ref` values are the decoded form of a purl
|
|
464
|
+
* (`pkg:npm/foo@1.0.0+build`), which cdx-purl rightly rejects as a purl. This
|
|
465
|
+
* recovers the purl the ref was made from by splitting it structurally and
|
|
466
|
+
* rebuilding it with `build()`, so every reserved character is escaped exactly
|
|
467
|
+
* once.
|
|
468
|
+
*
|
|
469
|
+
* @param {string} purlString Purl or decoded purl
|
|
470
|
+
* @returns {string|null} Canonical purl string, or `null` when no valid purl can be built
|
|
471
|
+
*/
|
|
472
|
+
export function canonicalPurlFromLooseString(purlString) {
|
|
473
|
+
const parts = parsePurlLoose(purlString);
|
|
474
|
+
if (!parts) {
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
return tryBuildPurl(parts);
|
|
478
|
+
}
|
|
479
|
+
|
|
331
480
|
/**
|
|
332
481
|
* Parse a purl string, returning `null` instead of throwing when it is invalid.
|
|
333
482
|
*
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { build, Purl } from "@cdxgen/cdx-purl";
|
|
15
15
|
|
|
16
16
|
import { isSpdxJsonLd } from "./bomUtils.js";
|
|
17
|
+
import { splitPurlLoose } from "./purl.js";
|
|
17
18
|
import { toCycloneDxLikeBom } from "./spdxUtils.js";
|
|
18
19
|
|
|
19
20
|
// A document larger than this is not a plausible SBOM for a single
|
|
@@ -150,75 +151,6 @@ function encodeVersionBuildMetadata(purl) {
|
|
|
150
151
|
return `${head.slice(0, at + 1)}${head.slice(at + 1).replaceAll("+", "%2B")}${tail}`;
|
|
151
152
|
}
|
|
152
153
|
|
|
153
|
-
/**
|
|
154
|
-
* Split a purl into its components without requiring it to be well-formed.
|
|
155
|
-
*
|
|
156
|
-
* `Purl.parse` rejects a purl whose parts are not already escaped, which is the
|
|
157
|
-
* case this exists to handle. The separators (`:`, `/`, `@`, `?`, `#`, `&`,
|
|
158
|
-
* `=`) are structural and are read positionally; everything between them is
|
|
159
|
-
* decoded and handed back raw, ready for `build` to escape correctly.
|
|
160
|
-
*
|
|
161
|
-
* @param {string} purl Purl to split
|
|
162
|
-
* @returns {{type: string, namespace: (string|null), name: string, version: (string|null), qualifiers: (Object|null), subpath: (string|null)}|null} Parts, or null when the purl has no type and name
|
|
163
|
-
*/
|
|
164
|
-
function splitPurl(purl) {
|
|
165
|
-
const decode = (value) => {
|
|
166
|
-
try {
|
|
167
|
-
return decodeURIComponent(value);
|
|
168
|
-
} catch {
|
|
169
|
-
return value;
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
let rest = purl.slice("pkg:".length).replace(/^\/+/, "");
|
|
173
|
-
let subpath = null;
|
|
174
|
-
const hash = rest.indexOf("#");
|
|
175
|
-
if (hash !== -1) {
|
|
176
|
-
subpath = decode(rest.slice(hash + 1)) || null;
|
|
177
|
-
rest = rest.slice(0, hash);
|
|
178
|
-
}
|
|
179
|
-
let qualifiers = null;
|
|
180
|
-
const query = rest.indexOf("?");
|
|
181
|
-
if (query !== -1) {
|
|
182
|
-
qualifiers = {};
|
|
183
|
-
for (const pair of rest.slice(query + 1).split("&")) {
|
|
184
|
-
const equals = pair.indexOf("=");
|
|
185
|
-
if (equals <= 0) {
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
qualifiers[pair.slice(0, equals)] = decode(pair.slice(equals + 1));
|
|
189
|
-
}
|
|
190
|
-
if (!Object.keys(qualifiers).length) {
|
|
191
|
-
qualifiers = null;
|
|
192
|
-
}
|
|
193
|
-
rest = rest.slice(0, query);
|
|
194
|
-
}
|
|
195
|
-
const slash = rest.indexOf("/");
|
|
196
|
-
if (slash <= 0) {
|
|
197
|
-
return null;
|
|
198
|
-
}
|
|
199
|
-
const type = rest.slice(0, slash);
|
|
200
|
-
let path = rest.slice(slash + 1);
|
|
201
|
-
let version = null;
|
|
202
|
-
const at = path.lastIndexOf("@");
|
|
203
|
-
if (at > 0) {
|
|
204
|
-
version = decode(path.slice(at + 1)) || null;
|
|
205
|
-
path = path.slice(0, at);
|
|
206
|
-
}
|
|
207
|
-
const segments = path.split("/").filter(Boolean).map(decode);
|
|
208
|
-
const name = segments.pop();
|
|
209
|
-
if (!name) {
|
|
210
|
-
return null;
|
|
211
|
-
}
|
|
212
|
-
return {
|
|
213
|
-
type,
|
|
214
|
-
namespace: segments.length ? segments.join("/") : null,
|
|
215
|
-
name,
|
|
216
|
-
version,
|
|
217
|
-
qualifiers,
|
|
218
|
-
subpath,
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
|
|
222
154
|
/**
|
|
223
155
|
* Repair the purl of a component read from a third-party document.
|
|
224
156
|
*
|
|
@@ -247,7 +179,7 @@ export function repairPurl(component) {
|
|
|
247
179
|
} catch {
|
|
248
180
|
// Fall through to the structural rebuild below.
|
|
249
181
|
}
|
|
250
|
-
const parts =
|
|
182
|
+
const parts = splitPurlLoose(repaired);
|
|
251
183
|
if (!parts) {
|
|
252
184
|
return;
|
|
253
185
|
}
|
package/lib/inventory/source.js
CHANGED
|
@@ -28,7 +28,9 @@ import {
|
|
|
28
28
|
prefetchEnabled,
|
|
29
29
|
prefetchedResponse,
|
|
30
30
|
prefetchJson,
|
|
31
|
+
withHostRateLimit,
|
|
31
32
|
} from "./fetchBatch.js";
|
|
33
|
+
import { canonicalPurlFromLooseString } from "./purl.js";
|
|
32
34
|
|
|
33
35
|
/** Raised when a caller omits a registry helper that the purl type requires. */
|
|
34
36
|
class MissingRegistryHelperError extends Error {}
|
|
@@ -953,6 +955,36 @@ function packageNameForLookup(purlObj) {
|
|
|
953
955
|
return `${namespace}/${purlObj.name}`;
|
|
954
956
|
}
|
|
955
957
|
|
|
958
|
+
/**
|
|
959
|
+
* Read a purl supplied as a scan source.
|
|
960
|
+
*
|
|
961
|
+
* People type purls the way their package manager prints versions and URLs:
|
|
962
|
+
* `pkg:cargo/wasi@0.11.0+wasi-snapshot-preview1` or
|
|
963
|
+
* `pkg:npm/foo?vcs_url=git+https://...`. cdx-purl requires those `+` to be
|
|
964
|
+
* escaped, so a purl that does not parse strictly is split on its separators
|
|
965
|
+
* and rebuilt with every component escaped once. The rebuilt purl is then
|
|
966
|
+
* parsed strictly, so validation always inspects cdx-purl's reading of it.
|
|
967
|
+
*
|
|
968
|
+
* @param {string} purlString Package URL as supplied
|
|
969
|
+
* @returns {Purl|undefined} Parsed purl, or undefined when none can be recovered
|
|
970
|
+
*/
|
|
971
|
+
function parsePurlSourceInput(purlString) {
|
|
972
|
+
try {
|
|
973
|
+
return Purl.parse(purlString);
|
|
974
|
+
} catch {
|
|
975
|
+
// Fall through to the structural rebuild.
|
|
976
|
+
}
|
|
977
|
+
const canonical = canonicalPurlFromLooseString(purlString);
|
|
978
|
+
if (!canonical) {
|
|
979
|
+
return undefined;
|
|
980
|
+
}
|
|
981
|
+
try {
|
|
982
|
+
return Purl.parse(canonical);
|
|
983
|
+
} catch {
|
|
984
|
+
return undefined;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
956
988
|
/**
|
|
957
989
|
* Validate package URL source input and return an error object when invalid.
|
|
958
990
|
*
|
|
@@ -963,10 +995,8 @@ export function validatePurlSource(purlString) {
|
|
|
963
995
|
if (!maybePurlSource(purlString)) {
|
|
964
996
|
return null;
|
|
965
997
|
}
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
purlObj = Purl.parse(purlString);
|
|
969
|
-
} catch (_err) {
|
|
998
|
+
const purlObj = parsePurlSourceInput(purlString);
|
|
999
|
+
if (!purlObj) {
|
|
970
1000
|
return {
|
|
971
1001
|
status: 400,
|
|
972
1002
|
error: "Invalid purl source",
|
|
@@ -1183,12 +1213,17 @@ export async function prefetchGitUrlSources(purlStrings) {
|
|
|
1183
1213
|
* @returns {Promise<Object>} A response-shaped object with a `body`.
|
|
1184
1214
|
*/
|
|
1185
1215
|
async function fetchRegistryDocument(lookup) {
|
|
1216
|
+
// A prefetch miss issues the request here instead, so it goes through the
|
|
1217
|
+
// same per-host gate the batch pool uses. That matters most for crates.io,
|
|
1218
|
+
// whose policy is one request per second however the request was reached.
|
|
1186
1219
|
return (
|
|
1187
1220
|
prefetchedResponse(gitUrlSourcePrefetch, lookup.url) ||
|
|
1188
|
-
(await
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1221
|
+
(await withHostRateLimit(lookup.url, () =>
|
|
1222
|
+
cdxgenAgent.get(lookup.url, {
|
|
1223
|
+
...(lookup.accept ? { headers: { Accept: lookup.accept } } : {}),
|
|
1224
|
+
responseType: "json",
|
|
1225
|
+
}),
|
|
1226
|
+
))
|
|
1192
1227
|
);
|
|
1193
1228
|
}
|
|
1194
1229
|
|
|
@@ -1203,24 +1238,13 @@ function parsePurlForSourceResolution(purlString) {
|
|
|
1203
1238
|
if (!maybePurlSource(purlString) || validatePurlSource(purlString)) {
|
|
1204
1239
|
return undefined;
|
|
1205
1240
|
}
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
// the qualifier section before parsing so real-world purls resolve.
|
|
1211
|
-
const qIdx = purlString.indexOf("?");
|
|
1212
|
-
const safePurlString =
|
|
1213
|
-
qIdx >= 0
|
|
1214
|
-
? purlString.slice(0, qIdx + 1) +
|
|
1215
|
-
purlString.slice(qIdx + 1).replaceAll("+", "%2B")
|
|
1216
|
-
: purlString;
|
|
1217
|
-
purlObj = Purl.parse(safePurlString);
|
|
1218
|
-
} catch (err) {
|
|
1241
|
+
// Purls in the wild leave `+` unescaped in versions and in qualifier values
|
|
1242
|
+
// such as `vcs_url=git+https://`; the shared reader rebuilds them.
|
|
1243
|
+
const purlObj = parsePurlSourceInput(purlString);
|
|
1244
|
+
if (!purlObj) {
|
|
1219
1245
|
if (DEBUG_MODE) {
|
|
1220
|
-
const errorMessage = err?.message || String(err);
|
|
1221
1246
|
thoughtLog("Unable to resolve repository URL for purl.", {
|
|
1222
1247
|
purlString,
|
|
1223
|
-
errorMessage,
|
|
1224
1248
|
});
|
|
1225
1249
|
}
|
|
1226
1250
|
return undefined;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Shared CSS package-reference extraction for stylesheet content.
|
|
2
|
+
//
|
|
3
|
+
// Angular style files, Vue and Svelte single-file-component `<style>` blocks,
|
|
4
|
+
// and `additionalData` strings injected by vite/vue configs all refer to npm
|
|
5
|
+
// packages through the same small set of CSS rules (`@use`, `@import`,
|
|
6
|
+
// `@forward`, and `url()` references). The patterns and the `url()` package
|
|
7
|
+
// guard live here so every collector applies one dialect instead of drifting
|
|
8
|
+
// per-framework copies.
|
|
9
|
+
//
|
|
10
|
+
// Both patterns are global and therefore stateful (`lastIndex`): call sites
|
|
11
|
+
// that reuse them across files must reset `lastIndex = 0` first, the way
|
|
12
|
+
// `matchAll` callers in this codebase already do.
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Patterns matching a package reference inside CSS/SCSS/Less/Stylus content.
|
|
16
|
+
* Capture group 1 holds the reference (for example `bulma/sass/utilities`).
|
|
17
|
+
*
|
|
18
|
+
* @type {RegExp[]}
|
|
19
|
+
*/
|
|
20
|
+
export const STYLE_PACKAGE_REFERENCE_PATTERNS = [
|
|
21
|
+
/@(?:import|use|forward)\s+(?:url\(\s*)?['"]([^'"\n)]+)['"]/g,
|
|
22
|
+
/url\(\s*['"](~?[^'"\n)]+)['"]\s*\)/g,
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* True when a `url()` reference points at a package rather than a local file.
|
|
27
|
+
* Only the `~package/...` and `node_modules/...` spellings qualify; plain
|
|
28
|
+
* relative URLs stay local.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} reference Value captured from a `url()` rule.
|
|
31
|
+
* @returns {boolean}
|
|
32
|
+
*/
|
|
33
|
+
export const isPackageStyleUrlReference = (reference) => {
|
|
34
|
+
const normalizedReference = String(reference || "")
|
|
35
|
+
.trim()
|
|
36
|
+
.replaceAll("\\", "/");
|
|
37
|
+
return (
|
|
38
|
+
normalizedReference.startsWith("~") ||
|
|
39
|
+
normalizedReference.startsWith("node_modules/")
|
|
40
|
+
);
|
|
41
|
+
};
|