@cdxgen/cdxgen 13.1.0 → 13.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 +8 -7
- package/bin/cdxgen.js +16 -3
- package/bin/evinse.js +2 -0
- package/data/crypto-oid.json +88 -12
- package/data/remediations.json +23 -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 +12 -0
- package/lib/cli/index.js +192 -1
- package/lib/cli/jsBom.js +59 -2
- package/lib/cli/jvmBom.js +22 -0
- package/lib/cli/managedBom.js +152 -0
- package/lib/cli/nativeBom.js +798 -1
- package/lib/core/activity.js +120 -2
- package/lib/core/env.js +39 -1
- package/lib/core/userAgent.js +151 -0
- package/lib/ecosystems/cargoCache.js +364 -0
- package/lib/ecosystems/caxa.js +13 -5
- package/lib/ecosystems/cppEvidence.js +40 -2
- package/lib/ecosystems/ecosystems.js +241 -91
- package/lib/ecosystems/golem.js +11 -1
- package/lib/ecosystems/gradleutils.js +143 -2
- package/lib/ecosystems/kosi.js +1256 -0
- package/lib/ecosystems/parsers-crystal.js +179 -0
- package/lib/ecosystems/parsers-dotnet.js +69 -39
- package/lib/ecosystems/parsers-elm.js +816 -0
- package/lib/ecosystems/parsers-gleam.js +1 -1
- package/lib/ecosystems/parsers-julia.js +268 -0
- package/lib/ecosystems/parsers-lua.js +218 -0
- package/lib/ecosystems/parsers-misc.js +172 -0
- 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 +1 -1
- package/lib/ecosystems/parsers-r.js +148 -0
- package/lib/ecosystems/parsers-rebar.js +411 -0
- package/lib/ecosystems/parsers-rust.js +932 -17
- 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/rusi.js +126 -6
- package/lib/evinser/evinser.js +111 -1
- package/lib/helpers/agenticToolutils.js +925 -0
- 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/auditCategories.js +8 -0
- package/lib/inventory/cbomutils.js +30 -0
- package/lib/inventory/cdxrs.js +7 -2
- package/lib/inventory/communityAiConfigParser.js +215 -2
- package/lib/inventory/dosai.js +77 -0
- package/lib/inventory/envcontext.js +9 -6
- package/lib/inventory/evidenceUtils.js +13 -0
- package/lib/inventory/fetchBatch.js +74 -2
- 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 +54 -88
- package/lib/inventory/protobomLoader.js +4 -0
- package/lib/inventory/rustFormulationParser.js +1 -1
- package/lib/inventory/source.js +10 -4
- package/lib/inventory/stylePackageRefs.js +41 -0
- package/lib/inventory/svelteUtils.js +524 -0
- package/lib/parsers/huggingfaceUtils.js +15 -7
- 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 +169 -1
- package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
- package/package.json +25 -25
- package/types/lib/audit/targets.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.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/userAgent.d.ts +56 -0
- package/types/lib/core/userAgent.d.ts.map +1 -0
- 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.map +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts +1 -1
- 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/gradleutils.d.ts +20 -0
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts +73 -0
- package/types/lib/ecosystems/kosi.d.ts.map +1 -0
- 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-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 +23 -0
- 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-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/rusi.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +29 -0
- package/types/lib/evinser/evinser.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/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/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/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +23 -0
- package/types/lib/inventory/dosai.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/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/parsers/huggingfaceUtils.d.ts +8 -1
- package/types/lib/parsers/huggingfaceUtils.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
|
@@ -66,11 +66,13 @@ import {
|
|
|
66
66
|
prefetchEnabled,
|
|
67
67
|
prefetchedResponse,
|
|
68
68
|
prefetchJson,
|
|
69
|
+
withHostRateLimit,
|
|
69
70
|
} from "../inventory/fetchBatch.js";
|
|
70
71
|
import {
|
|
71
72
|
applyPurl,
|
|
72
73
|
encodeForPurl,
|
|
73
74
|
genericPurl,
|
|
75
|
+
nugetPurl,
|
|
74
76
|
pypiPurl,
|
|
75
77
|
} from "../inventory/purl.js";
|
|
76
78
|
import {
|
|
@@ -80,6 +82,10 @@ import {
|
|
|
80
82
|
} from "../inventory/spdx.js";
|
|
81
83
|
import { extractLicenseText, extractRepoUrl } from "../parsers/htmlExtract.js";
|
|
82
84
|
import { xml2js } from "../parsers/xml.js";
|
|
85
|
+
import {
|
|
86
|
+
localCargoMetadataEnabled,
|
|
87
|
+
readCargoCacheMetadata,
|
|
88
|
+
} from "./cargoCache.js";
|
|
83
89
|
import {
|
|
84
90
|
applyPypiClassifierMetadata,
|
|
85
91
|
applyPypiModuleNames,
|
|
@@ -814,11 +820,13 @@ export async function fetchPomXml({ urlPrefix, group, name, version }) {
|
|
|
814
820
|
// A direct POM may have been prefetched by getMvnMetadata's batch round.
|
|
815
821
|
// Parent POMs (fetched from inside fetchPomXmlAsJson) are not prefetched
|
|
816
822
|
// because their URL depends on the direct POM's content, so they fall
|
|
817
|
-
// through to the serial request
|
|
823
|
+
// through to the serial request. That fallback still reaches Maven Central
|
|
824
|
+
// and still counts against the same budget as the batch round, so it queues
|
|
825
|
+
// behind the same per-host gate rather than a second, ungoverned one.
|
|
818
826
|
try {
|
|
819
827
|
const res =
|
|
820
828
|
prefetchedResponse(prefetchedPoms, fullUrl) ||
|
|
821
|
-
(await cdxgenAgent.get(fullUrl));
|
|
829
|
+
(await withHostRateLimit(fullUrl, () => cdxgenAgent.get(fullUrl)));
|
|
822
830
|
return res.body;
|
|
823
831
|
} catch (_err) {
|
|
824
832
|
return undefined;
|
|
@@ -1792,24 +1800,61 @@ function getGoPkgFullName(group, name) {
|
|
|
1792
1800
|
/**
|
|
1793
1801
|
* Method to retrieve metadata for rust packages by querying crates
|
|
1794
1802
|
*
|
|
1803
|
+
* The local Cargo registry is consulted first, because crates.io's crawler
|
|
1804
|
+
* policy allows one request per second and a populated `~/.cargo` already
|
|
1805
|
+
* holds the license, description, repository, checksum and yanked flag for
|
|
1806
|
+
* every crate the build compiled.
|
|
1807
|
+
*
|
|
1808
|
+
* What it does not hold is publisher identity, so a caller that needs the
|
|
1809
|
+
* publisher-drift and release-cadence signals — the predictive audit does —
|
|
1810
|
+
* passes `preferLocalCache: false` and takes the slower registry path.
|
|
1811
|
+
*
|
|
1795
1812
|
* @param {Array} pkgList Package list
|
|
1813
|
+
* @param {Object} [options] Options
|
|
1814
|
+
* @param {boolean} [options.preferLocalCache=true] Answer from the local Cargo
|
|
1815
|
+
* registry where it can, and query crates.io only for the rest.
|
|
1796
1816
|
*/
|
|
1797
|
-
export async function getCratesMetadata(pkgList) {
|
|
1817
|
+
export async function getCratesMetadata(pkgList, options = {}) {
|
|
1798
1818
|
const CRATES_URL =
|
|
1799
1819
|
readEnvironmentVariable("RUST_CRATES_URL") ||
|
|
1800
1820
|
"https://crates.io/api/v1/crates/";
|
|
1801
1821
|
const cdepList = [];
|
|
1822
|
+
// The local Cargo registry answers first. crates.io allows one request per
|
|
1823
|
+
// second, so a populated ~/.cargo turns minutes of waiting into a few file
|
|
1824
|
+
// reads; only the crates it cannot account for reach the network.
|
|
1825
|
+
const servedLocally = new Set();
|
|
1826
|
+
const preferLocalCache = options.preferLocalCache !== false;
|
|
1827
|
+
if (preferLocalCache && localCargoMetadataEnabled()) {
|
|
1828
|
+
for (const p of pkgList) {
|
|
1829
|
+
if (!p?.name || !p?.version || p.version === "workspace") {
|
|
1830
|
+
continue;
|
|
1831
|
+
}
|
|
1832
|
+
if (applyCargoCacheMetadata(p)) {
|
|
1833
|
+
servedLocally.add(p);
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
if (DEBUG_MODE && servedLocally.size) {
|
|
1837
|
+
console.log(
|
|
1838
|
+
`Resolved ${servedLocally.size} of ${pkgList.length} crate(s) from the local Cargo registry.`,
|
|
1839
|
+
);
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
const needsRegistry = pkgList.filter(
|
|
1843
|
+
(p) =>
|
|
1844
|
+
p?.name &&
|
|
1845
|
+
p?.version &&
|
|
1846
|
+
p.version !== "workspace" &&
|
|
1847
|
+
!servedLocally.has(p),
|
|
1848
|
+
);
|
|
1802
1849
|
// Two URLs per crate — the crate document and its owners — fetched for the
|
|
1803
1850
|
// whole list at once. The `workspace` guard below is mirrored here so the
|
|
1804
1851
|
// batch does not request anything the loop would have skipped.
|
|
1805
1852
|
const prefetched = await prefetchJson(
|
|
1806
1853
|
prefetchEnabled()
|
|
1807
|
-
?
|
|
1808
|
-
|
|
1809
|
-
.
|
|
1810
|
-
|
|
1811
|
-
{ url: `${CRATES_URL + p.name}/owners` },
|
|
1812
|
-
])
|
|
1854
|
+
? needsRegistry.flatMap((p) => [
|
|
1855
|
+
{ url: CRATES_URL + p.name },
|
|
1856
|
+
{ url: `${CRATES_URL + p.name}/owners` },
|
|
1857
|
+
])
|
|
1813
1858
|
: [],
|
|
1814
1859
|
);
|
|
1815
1860
|
for (const p of pkgList) {
|
|
@@ -1818,23 +1863,35 @@ export async function getCratesMetadata(pkgList) {
|
|
|
1818
1863
|
cdepList.push(p);
|
|
1819
1864
|
continue;
|
|
1820
1865
|
}
|
|
1866
|
+
if (servedLocally.has(p)) {
|
|
1867
|
+
cdepList.push(p);
|
|
1868
|
+
continue;
|
|
1869
|
+
}
|
|
1821
1870
|
if (DEBUG_MODE) {
|
|
1822
1871
|
console.log(`Querying crates.io for ${p.name}@${p.version}`);
|
|
1823
1872
|
}
|
|
1824
1873
|
const crateUrl = CRATES_URL + p.name;
|
|
1874
|
+
// A prefetch miss falls back to a direct request, which must queue
|
|
1875
|
+
// behind the same crates.io gate the batch pool uses: the registry
|
|
1876
|
+
// allows one request per second and does not care which code path in
|
|
1877
|
+
// this process issued it.
|
|
1825
1878
|
const res =
|
|
1826
1879
|
prefetchedResponse(prefetched, crateUrl) ||
|
|
1827
|
-
(await
|
|
1828
|
-
|
|
1829
|
-
|
|
1880
|
+
(await withHostRateLimit(crateUrl, () =>
|
|
1881
|
+
cdxgenAgent.get(crateUrl, {
|
|
1882
|
+
responseType: "json",
|
|
1883
|
+
}),
|
|
1884
|
+
));
|
|
1830
1885
|
let ownersRes;
|
|
1831
1886
|
try {
|
|
1832
1887
|
const ownersUrl = `${crateUrl}/owners`;
|
|
1833
1888
|
ownersRes =
|
|
1834
1889
|
prefetchedResponse(prefetched, ownersUrl) ||
|
|
1835
|
-
(await
|
|
1836
|
-
|
|
1837
|
-
|
|
1890
|
+
(await withHostRateLimit(ownersUrl, () =>
|
|
1891
|
+
cdxgenAgent.get(ownersUrl, {
|
|
1892
|
+
responseType: "json",
|
|
1893
|
+
}),
|
|
1894
|
+
));
|
|
1838
1895
|
} catch (_err) {
|
|
1839
1896
|
ownersRes = undefined;
|
|
1840
1897
|
}
|
|
@@ -1904,6 +1961,86 @@ export async function getCratesMetadata(pkgList) {
|
|
|
1904
1961
|
return cdepList;
|
|
1905
1962
|
}
|
|
1906
1963
|
|
|
1964
|
+
/**
|
|
1965
|
+
* Apply the local Cargo registry's view of a crate to its component.
|
|
1966
|
+
*
|
|
1967
|
+
* Returns whether the component is complete enough to skip crates.io. The bar
|
|
1968
|
+
* is the license: `getCratesMetadata` runs only under `FETCH_LICENSE`, so a
|
|
1969
|
+
* crate whose license the local registry cannot name has not answered the
|
|
1970
|
+
* question that was asked and still needs the network. That happens when the
|
|
1971
|
+
* index knows a version but Cargo never extracted its sources — a crate the
|
|
1972
|
+
* lockfile pins but the build never compiled.
|
|
1973
|
+
*
|
|
1974
|
+
* Publisher identity has no local equivalent, so a component served from here
|
|
1975
|
+
* carries the publish time and yanked flag but none of the publisher-drift
|
|
1976
|
+
* properties; `cdx:cargo:metadataSource` records which it was.
|
|
1977
|
+
*
|
|
1978
|
+
* @param {Object} p Package component to enrich in place.
|
|
1979
|
+
* @returns {boolean} true when crates.io can be skipped for this crate.
|
|
1980
|
+
*/
|
|
1981
|
+
function applyCargoCacheMetadata(p) {
|
|
1982
|
+
const local = readCargoCacheMetadata(p.name, p.version);
|
|
1983
|
+
if (!local?.license) {
|
|
1984
|
+
return false;
|
|
1985
|
+
}
|
|
1986
|
+
p.license = local.license;
|
|
1987
|
+
if (local.description) {
|
|
1988
|
+
p.description = local.description;
|
|
1989
|
+
}
|
|
1990
|
+
if (local.repository) {
|
|
1991
|
+
p.repository = { url: local.repository };
|
|
1992
|
+
}
|
|
1993
|
+
if (local.homepage && local.homepage !== local.repository) {
|
|
1994
|
+
p.homepage = { url: local.homepage };
|
|
1995
|
+
}
|
|
1996
|
+
if (!p._integrity && local.checksum) {
|
|
1997
|
+
p._integrity = normalizeCargoIntegrity(local.checksum);
|
|
1998
|
+
}
|
|
1999
|
+
if (!p.properties) {
|
|
2000
|
+
p.properties = [];
|
|
2001
|
+
}
|
|
2002
|
+
p.properties.push({
|
|
2003
|
+
name: "cdx:cargo:metadataSource",
|
|
2004
|
+
value: "local-registry",
|
|
2005
|
+
});
|
|
2006
|
+
if (local.rustVersion) {
|
|
2007
|
+
p.properties.push({
|
|
2008
|
+
name: "cdx:cargo:rust_version",
|
|
2009
|
+
value: local.rustVersion,
|
|
2010
|
+
});
|
|
2011
|
+
}
|
|
2012
|
+
if (local.latestVersion) {
|
|
2013
|
+
p.properties.push({
|
|
2014
|
+
name: "cdx:cargo:latest_version",
|
|
2015
|
+
value: local.latestVersion,
|
|
2016
|
+
});
|
|
2017
|
+
}
|
|
2018
|
+
if (typeof local.yanked === "boolean") {
|
|
2019
|
+
p.properties.push({
|
|
2020
|
+
name: "cdx:cargo:yanked",
|
|
2021
|
+
value: String(local.yanked),
|
|
2022
|
+
});
|
|
2023
|
+
}
|
|
2024
|
+
if (local.publishTime) {
|
|
2025
|
+
p.properties.push({
|
|
2026
|
+
name: "cdx:cargo:publishTime",
|
|
2027
|
+
value: local.publishTime,
|
|
2028
|
+
});
|
|
2029
|
+
}
|
|
2030
|
+
if (local.features && Object.keys(local.features).length) {
|
|
2031
|
+
p.properties.push({
|
|
2032
|
+
name: "cdx:cargo:features",
|
|
2033
|
+
value: JSON.stringify(local.features),
|
|
2034
|
+
});
|
|
2035
|
+
}
|
|
2036
|
+
// The download path is a documented, stable URL shape rather than something
|
|
2037
|
+
// the API had to tell us, so it costs no request to state it.
|
|
2038
|
+
p.distribution = {
|
|
2039
|
+
url: `https://crates.io/api/v1/crates/${p.name}/${p.version}/download`,
|
|
2040
|
+
};
|
|
2041
|
+
return true;
|
|
2042
|
+
}
|
|
2043
|
+
|
|
1907
2044
|
/**
|
|
1908
2045
|
* Method to retrieve metadata for dart packages by querying pub.dev
|
|
1909
2046
|
*
|
|
@@ -2168,17 +2305,23 @@ export async function extractJarArchive(jarFile, tempDir, jarNSMapping = {}) {
|
|
|
2168
2305
|
try {
|
|
2169
2306
|
const sha = await checksumFile("sha1", jf);
|
|
2170
2307
|
const searchurl = `https://central.sonatype.com/solrsearch/select?q=1:%22${sha}%22&rows=20&wt=json`;
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2308
|
+
// One Solr query per unidentified jar, against Central's origin
|
|
2309
|
+
// rather than its CDN. A directory of jars issues these back to
|
|
2310
|
+
// back, so they go through the same per-host gate the batch pool
|
|
2311
|
+
// uses (500 ms, serial) instead of a second, ungoverned path.
|
|
2312
|
+
const res = await withHostRateLimit(searchurl, () =>
|
|
2313
|
+
cdxgenAgent.get(searchurl, {
|
|
2314
|
+
responseType: "json",
|
|
2315
|
+
timeout: {
|
|
2316
|
+
lookup: 1000,
|
|
2317
|
+
connect: 5000,
|
|
2318
|
+
secureConnect: 5000,
|
|
2319
|
+
socket: 1000,
|
|
2320
|
+
send: 10000,
|
|
2321
|
+
response: 1000,
|
|
2322
|
+
},
|
|
2323
|
+
}),
|
|
2324
|
+
);
|
|
2182
2325
|
const data = res?.body ? res.body["response"] : undefined;
|
|
2183
2326
|
if (data && data["numFound"] === 1) {
|
|
2184
2327
|
const jarInfo = data["docs"][0];
|
|
@@ -2480,6 +2623,12 @@ async function unresolvedJarComponents(unresolvedJars, jarFile, tempDir) {
|
|
|
2480
2623
|
return components;
|
|
2481
2624
|
}
|
|
2482
2625
|
|
|
2626
|
+
/**
|
|
2627
|
+
* Property recording the NuGet release a versionless component's metadata was
|
|
2628
|
+
* read from, so the description and licence in the output can be traced.
|
|
2629
|
+
*/
|
|
2630
|
+
const METADATA_VERSION_PROP = "cdx:nuget:metadata_version";
|
|
2631
|
+
|
|
2483
2632
|
async function getNugetUrl() {
|
|
2484
2633
|
const req = "https://api.nuget.org/v3/index.json";
|
|
2485
2634
|
const res = await cdxgenAgent.get(req, {
|
|
@@ -2555,60 +2704,46 @@ async function queryNuget(p, NUGET_URL) {
|
|
|
2555
2704
|
if (!items?.[0]) {
|
|
2556
2705
|
return [np, newBody, body];
|
|
2557
2706
|
}
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2707
|
+
// A component that states no version at all is not the same as one carrying a
|
|
2708
|
+
// `latest` or `0.0.0` placeholder. The placeholder asks to be replaced, so the
|
|
2709
|
+
// registry's newest release becomes the component's version. A missing version
|
|
2710
|
+
// means the manifest never said which release is in use - a `<Reference>` to an
|
|
2711
|
+
// assembly is the usual source - and the newest release is then only a key for
|
|
2712
|
+
// looking metadata up, never a version to write back. See #4359.
|
|
2713
|
+
const versionStated = p.version && !["0.0.0", "latest"].includes(p.version);
|
|
2714
|
+
let lookupVersion = p.version;
|
|
2715
|
+
if (!versionStated) {
|
|
2716
|
+
lookupVersion = setLatestVersion(items[items.length - 1].upper);
|
|
2717
|
+
np.version = p.version ? lookupVersion : undefined;
|
|
2718
|
+
np.metadataVersion = lookupVersion;
|
|
2719
|
+
}
|
|
2720
|
+
if (lookupVersion) {
|
|
2563
2721
|
for (const item of items) {
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
res = await cdxgenAgent.get(item["@id"], { responseType: "json" });
|
|
2575
|
-
for (const i of res.body.items.reverse()) {
|
|
2576
|
-
if (
|
|
2577
|
-
i.catalogEntry &&
|
|
2578
|
-
i.catalogEntry.version === coerceUp(np.version)
|
|
2579
|
-
) {
|
|
2580
|
-
newBody.push(i);
|
|
2581
|
-
return [np, newBody];
|
|
2582
|
-
}
|
|
2583
|
-
}
|
|
2584
|
-
}
|
|
2722
|
+
const lower = compare(
|
|
2723
|
+
coerce(item.lower, { loose: true }),
|
|
2724
|
+
coerce(lookupVersion, { loose: true }),
|
|
2725
|
+
);
|
|
2726
|
+
const upper = compare(
|
|
2727
|
+
coerce(item.upper, { loose: true }),
|
|
2728
|
+
coerce(lookupVersion, { loose: true }),
|
|
2729
|
+
);
|
|
2730
|
+
if (lower === 1 || upper === -1) {
|
|
2731
|
+
continue;
|
|
2585
2732
|
}
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
for (const
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
)
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
coerce(np.version, { loose: true }),
|
|
2601
|
-
);
|
|
2602
|
-
if (lower !== 1 && upper !== -1) {
|
|
2603
|
-
for (const i of item.items.reverse()) {
|
|
2604
|
-
if (
|
|
2605
|
-
i.catalogEntry &&
|
|
2606
|
-
i.catalogEntry.version === coerceUp(np.version)
|
|
2607
|
-
) {
|
|
2608
|
-
newBody.push(i);
|
|
2609
|
-
return [np, newBody];
|
|
2610
|
-
}
|
|
2611
|
-
}
|
|
2733
|
+
// The registration index either inlines the leaves of each page or points
|
|
2734
|
+
// at a page that must be fetched.
|
|
2735
|
+
let leaves = item.items;
|
|
2736
|
+
if (!leaves) {
|
|
2737
|
+
res = await cdxgenAgent.get(item["@id"], { responseType: "json" });
|
|
2738
|
+
leaves = res.body.items;
|
|
2739
|
+
}
|
|
2740
|
+
for (const i of leaves.reverse()) {
|
|
2741
|
+
if (
|
|
2742
|
+
i.catalogEntry &&
|
|
2743
|
+
i.catalogEntry.version === coerceUp(lookupVersion)
|
|
2744
|
+
) {
|
|
2745
|
+
newBody.push(i);
|
|
2746
|
+
return [np, newBody];
|
|
2612
2747
|
}
|
|
2613
2748
|
}
|
|
2614
2749
|
}
|
|
@@ -2673,25 +2808,36 @@ export async function getNugetMetadata(pkgList, dependencies = undefined) {
|
|
|
2673
2808
|
[np, newBody] = await queryNuget(p, NUGET_URL);
|
|
2674
2809
|
if (p.version !== np.version) {
|
|
2675
2810
|
const oldRef = p["bom-ref"];
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
namespace: "" || null,
|
|
2680
|
-
name: np.name,
|
|
2681
|
-
version: np.version || null,
|
|
2682
|
-
}),
|
|
2683
|
-
);
|
|
2684
|
-
depRepList[oldRef] = p["bom-ref"];
|
|
2811
|
+
// The purl is rebuilt alongside the bom-ref. Leaving it behind let a
|
|
2812
|
+
// component state one version in `version` and another - or none - in
|
|
2813
|
+
// its purl, which is what a scanner range matches on. See #4359.
|
|
2685
2814
|
p.version = np.version;
|
|
2815
|
+
applyPurl(p, nugetPurl(np.name, np.version));
|
|
2816
|
+
if (oldRef && p["bom-ref"] !== oldRef) {
|
|
2817
|
+
depRepList[oldRef] = p["bom-ref"];
|
|
2818
|
+
}
|
|
2686
2819
|
}
|
|
2687
2820
|
if (newBody && newBody.length > 0) {
|
|
2688
2821
|
body = newBody[0];
|
|
2689
2822
|
}
|
|
2690
2823
|
if (body) {
|
|
2691
2824
|
metadata_cache[cacheKey] = body;
|
|
2692
|
-
//
|
|
2693
|
-
|
|
2694
|
-
|
|
2825
|
+
// The component states no version, so the description and licence
|
|
2826
|
+
// below were read from whichever release the lookup fell back to.
|
|
2827
|
+
// That release is recorded rather than adopted as the version: a
|
|
2828
|
+
// version cdxgen chose is not a version the project declared.
|
|
2829
|
+
if (!p.version && np.metadataVersion) {
|
|
2830
|
+
p.properties = p.properties || [];
|
|
2831
|
+
if (
|
|
2832
|
+
!p.properties.some(
|
|
2833
|
+
(prop) => prop.name === METADATA_VERSION_PROP,
|
|
2834
|
+
)
|
|
2835
|
+
) {
|
|
2836
|
+
p.properties.push({
|
|
2837
|
+
name: METADATA_VERSION_PROP,
|
|
2838
|
+
value: np.metadataVersion,
|
|
2839
|
+
});
|
|
2840
|
+
}
|
|
2695
2841
|
}
|
|
2696
2842
|
p.description = body.catalogEntry.description;
|
|
2697
2843
|
if (body.catalogEntry.authors) {
|
|
@@ -2720,8 +2866,12 @@ export async function getNugetMetadata(pkgList, dependencies = undefined) {
|
|
|
2720
2866
|
}
|
|
2721
2867
|
if (body.catalogEntry.projectUrl) {
|
|
2722
2868
|
p.repository = { url: body.catalogEntry.projectUrl };
|
|
2869
|
+
// A versionless component gets the package's landing page. Pasting
|
|
2870
|
+
// an undefined version into the path produced a 404 link.
|
|
2723
2871
|
p.homepage = {
|
|
2724
|
-
url:
|
|
2872
|
+
url: p.version
|
|
2873
|
+
? `https://www.nuget.org/packages/${p.name}/${p.version}/`
|
|
2874
|
+
: `https://www.nuget.org/packages/${p.name}/`,
|
|
2725
2875
|
};
|
|
2726
2876
|
if (
|
|
2727
2877
|
(!p.license || typeof p.license === "string") &&
|
package/lib/ecosystems/golem.js
CHANGED
|
@@ -1360,11 +1360,21 @@ function normalizedGolemDataFlowCallGraphMode(options = {}) {
|
|
|
1360
1360
|
return callgraphMode;
|
|
1361
1361
|
}
|
|
1362
1362
|
|
|
1363
|
+
function availableCpuCount() {
|
|
1364
|
+
try {
|
|
1365
|
+
return cpus()?.length || 1;
|
|
1366
|
+
} catch {
|
|
1367
|
+
// Deno denies the probe without --allow-sys=cpus; a worker-count hint
|
|
1368
|
+
// must not abort the analysis, so fall back to a single worker.
|
|
1369
|
+
return 1;
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1363
1373
|
function appendGolemDataFlowArgs(args, dataFlowMode, options = {}) {
|
|
1364
1374
|
if (dataFlowMode === "none") {
|
|
1365
1375
|
return;
|
|
1366
1376
|
}
|
|
1367
|
-
const cpuCount =
|
|
1377
|
+
const cpuCount = availableCpuCount();
|
|
1368
1378
|
const performanceWorkerCount = Math.max(1, Math.min(cpuCount, 4));
|
|
1369
1379
|
const dataFlowCallgraph = normalizedGolemDataFlowCallGraphMode(options);
|
|
1370
1380
|
const dataFlowPacks =
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
|
-
import { chmodSync } from "node:fs";
|
|
2
|
+
import { chmodSync, readFileSync } from "node:fs";
|
|
3
3
|
import { homedir, platform } from "node:os";
|
|
4
4
|
import { basename, join, resolve } from "node:path";
|
|
5
5
|
|
|
6
6
|
import { build } from "@cdxgen/cdx-purl";
|
|
7
|
+
import * as toml from "smol-toml";
|
|
7
8
|
|
|
8
9
|
import {
|
|
9
10
|
DEBUG_MODE,
|
|
@@ -23,7 +24,7 @@ import { thoughtLog } from "../core/logger.js";
|
|
|
23
24
|
import { isWin } from "../core/paths.js";
|
|
24
25
|
import { collectJarNS } from "../inventory/deps.js";
|
|
25
26
|
import { minimumGradleVersionForJava } from "../inventory/jvmToolEnv.js";
|
|
26
|
-
import { applyPurl } from "../inventory/purl.js";
|
|
27
|
+
import { applyPurl, tryBuildPurl } from "../inventory/purl.js";
|
|
27
28
|
import { parsePkgJson } from "./parsers-js.js";
|
|
28
29
|
|
|
29
30
|
/**
|
|
@@ -1386,3 +1387,143 @@ export function getMavenCommand(srcPath, rootPath) {
|
|
|
1386
1387
|
}
|
|
1387
1388
|
return mavenCmd;
|
|
1388
1389
|
}
|
|
1390
|
+
|
|
1391
|
+
/**
|
|
1392
|
+
* Parse a Gradle version catalog (`gradle/libs.versions.toml`).
|
|
1393
|
+
*
|
|
1394
|
+
* A catalog declares coordinates the build scripts then reference by alias.
|
|
1395
|
+
* It is a declaration, not a resolution: entries may go unused, and their
|
|
1396
|
+
* versions may be references into the `[versions]` table. Because of that,
|
|
1397
|
+
* catalog components are emitted only as a fallback when Gradle itself
|
|
1398
|
+
* produced no dependency information, and every component carries a
|
|
1399
|
+
* `cdx:gradle:catalog=true` property so consumers can tell declared catalog
|
|
1400
|
+
* entries from a resolved graph.
|
|
1401
|
+
*
|
|
1402
|
+
* Only libraries whose version resolves to a literal (inline or via
|
|
1403
|
+
* `version.ref`) are emitted; rich or unresolved versions would produce purls
|
|
1404
|
+
* that identify no artifact. Plugins are skipped because they are build-time
|
|
1405
|
+
* only.
|
|
1406
|
+
*
|
|
1407
|
+
* @param {string} catalogFile Path to `libs.versions.toml`
|
|
1408
|
+
* @returns {Object[]} Package records for resolvable library entries
|
|
1409
|
+
*/
|
|
1410
|
+
export function parseGradleVersionCatalog(catalogFile) {
|
|
1411
|
+
let catalog;
|
|
1412
|
+
try {
|
|
1413
|
+
catalog = toml.parse(readFileSync(catalogFile, "utf-8"));
|
|
1414
|
+
} catch (error) {
|
|
1415
|
+
console.warn(`Failed to parse ${catalogFile}: ${error.message}`);
|
|
1416
|
+
return [];
|
|
1417
|
+
}
|
|
1418
|
+
const versions =
|
|
1419
|
+
catalog?.versions && typeof catalog.versions === "object"
|
|
1420
|
+
? catalog.versions
|
|
1421
|
+
: {};
|
|
1422
|
+
const libraries =
|
|
1423
|
+
catalog?.libraries && typeof catalog.libraries === "object"
|
|
1424
|
+
? catalog.libraries
|
|
1425
|
+
: {};
|
|
1426
|
+
const pkgList = [];
|
|
1427
|
+
for (const entry of Object.values(libraries)) {
|
|
1428
|
+
const coordinate = catalogCoordinate(entry);
|
|
1429
|
+
if (!coordinate) {
|
|
1430
|
+
continue;
|
|
1431
|
+
}
|
|
1432
|
+
const version = resolveCatalogVersion(entry, versions);
|
|
1433
|
+
if (!version) {
|
|
1434
|
+
continue;
|
|
1435
|
+
}
|
|
1436
|
+
const { group, name } = coordinate;
|
|
1437
|
+
// A catalog is authored input, but a malformed coordinate must cost the
|
|
1438
|
+
// one entry, not the whole scan.
|
|
1439
|
+
const purl = tryBuildPurl({
|
|
1440
|
+
type: "maven",
|
|
1441
|
+
namespace: group,
|
|
1442
|
+
name,
|
|
1443
|
+
version,
|
|
1444
|
+
});
|
|
1445
|
+
if (!purl) {
|
|
1446
|
+
continue;
|
|
1447
|
+
}
|
|
1448
|
+
pkgList.push({
|
|
1449
|
+
group,
|
|
1450
|
+
name,
|
|
1451
|
+
version,
|
|
1452
|
+
type: "library",
|
|
1453
|
+
scope: "required",
|
|
1454
|
+
purl,
|
|
1455
|
+
"bom-ref": decodeURIComponent(purl),
|
|
1456
|
+
properties: [
|
|
1457
|
+
{ name: "internal:SrcFile", value: catalogFile },
|
|
1458
|
+
{ name: "cdx:gradle:catalog", value: "true" },
|
|
1459
|
+
],
|
|
1460
|
+
evidence: {
|
|
1461
|
+
identity: {
|
|
1462
|
+
field: "purl",
|
|
1463
|
+
confidence: 0.5,
|
|
1464
|
+
methods: [
|
|
1465
|
+
{
|
|
1466
|
+
technique: "source-code-analysis",
|
|
1467
|
+
confidence: 0.5,
|
|
1468
|
+
value: `Filename ${catalogFile}`,
|
|
1469
|
+
},
|
|
1470
|
+
],
|
|
1471
|
+
},
|
|
1472
|
+
},
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
return pkgList;
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
/**
|
|
1479
|
+
* Read the Maven coordinate of one catalog entry.
|
|
1480
|
+
*
|
|
1481
|
+
* A catalog writes the coordinate either as `module = "group:name"` or as a
|
|
1482
|
+
* separate `group` and `name` pair; both forms are equivalent and both appear
|
|
1483
|
+
* in real catalogs.
|
|
1484
|
+
*
|
|
1485
|
+
* @param {object} entry Catalog library entry
|
|
1486
|
+
* @returns {{group: string, name: string}|undefined} Coordinate, or undefined
|
|
1487
|
+
*/
|
|
1488
|
+
function catalogCoordinate(entry) {
|
|
1489
|
+
const module = typeof entry?.module === "string" ? entry.module : undefined;
|
|
1490
|
+
if (module?.includes(":")) {
|
|
1491
|
+
const separator = module.indexOf(":");
|
|
1492
|
+
return {
|
|
1493
|
+
group: module.slice(0, separator),
|
|
1494
|
+
name: module.slice(separator + 1),
|
|
1495
|
+
};
|
|
1496
|
+
}
|
|
1497
|
+
if (typeof entry?.group === "string" && typeof entry?.name === "string") {
|
|
1498
|
+
return { group: entry.group, name: entry.name };
|
|
1499
|
+
}
|
|
1500
|
+
return undefined;
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
/**
|
|
1504
|
+
* Resolve the version of one catalog entry.
|
|
1505
|
+
*
|
|
1506
|
+
* An entry may pin `version = "1.2.3"` directly or reference the versions
|
|
1507
|
+
* table with `version.ref = "alias"`. Version references that are missing, or
|
|
1508
|
+
* that carry Gradle rich-version syntax (ranges, `!!`, separators), resolve
|
|
1509
|
+
* to nothing and the entry is skipped.
|
|
1510
|
+
*
|
|
1511
|
+
* @param {object} entry Catalog library entry
|
|
1512
|
+
* @param {object} versions Parsed `[versions]` table
|
|
1513
|
+
* @returns {string|undefined} Concrete version, or undefined
|
|
1514
|
+
*/
|
|
1515
|
+
function resolveCatalogVersion(entry, versions) {
|
|
1516
|
+
const raw =
|
|
1517
|
+
typeof entry?.version === "string"
|
|
1518
|
+
? entry.version
|
|
1519
|
+
: typeof entry?.version?.ref === "string"
|
|
1520
|
+
? versions[entry.version.ref]
|
|
1521
|
+
: undefined;
|
|
1522
|
+
if (typeof raw !== "string" || !raw) {
|
|
1523
|
+
return undefined;
|
|
1524
|
+
}
|
|
1525
|
+
if (/[[\](),!]|->|\.\./u.test(raw)) {
|
|
1526
|
+
return undefined;
|
|
1527
|
+
}
|
|
1528
|
+
return raw;
|
|
1529
|
+
}
|