@cdxgen/cdxgen 13.0.1 → 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 +30 -16
- package/bin/audit.js +6 -0
- package/bin/cdxgen.js +141 -7
- package/bin/evinse.js +3 -1
- package/data/component-tags.json +8 -1
- package/data/crypto-oid.json +88 -12
- package/data/frameworks-list.json +86 -1
- package/data/remediations.json +1167 -0
- package/data/rules/ai-bom.yaml +78 -0
- package/data/rules/asar-archives.yaml +1 -1
- package/data/rules/build-fidelity.yaml +782 -0
- package/data/rules/cargo-rust.yaml +132 -0
- package/data/rules/mcp-servers.yaml +122 -0
- package/data/rules/package-integrity.yaml +13 -13
- package/lib/audit/index.js +48 -4
- package/lib/audit/scoring.js +2 -8
- package/lib/audit/targets.js +6 -1
- package/lib/cli/bomAssembly.js +32 -1
- package/lib/cli/cliOptions.js +94 -7
- package/lib/cli/index.js +268 -49
- package/lib/cli/jsBom.js +91 -5
- package/lib/cli/jvmBom.js +1515 -1314
- package/lib/cli/managedBom.js +249 -28
- package/lib/cli/nativeBom.js +986 -10
- package/lib/core/activity.js +120 -2
- package/lib/core/buildLedger.js +799 -0
- package/lib/core/deferredExit.js +141 -0
- package/lib/core/env.js +108 -2
- package/lib/core/fs.js +60 -0
- package/lib/core/logger.js +12 -99
- package/lib/core/severity.js +16 -0
- package/lib/core/syncFileWriter.js +106 -0
- 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 +250 -91
- package/lib/ecosystems/golem.js +11 -1
- package/lib/ecosystems/gradleutils.js +498 -17
- 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-js.js +26 -0
- package/lib/ecosystems/parsers-julia.js +268 -0
- package/lib/ecosystems/parsers-jvm.js +9 -0
- package/lib/ecosystems/parsers-lua.js +218 -0
- package/lib/ecosystems/parsers-misc.js +188 -2
- 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 +172 -36
- package/lib/ecosystems/parsers-r.js +148 -0
- package/lib/ecosystems/parsers-rebar.js +411 -0
- package/lib/ecosystems/parsers-rust.js +944 -19
- 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/pypiClassifiers.js +326 -0
- package/lib/ecosystems/pythonutils.js +49 -1
- package/lib/ecosystems/rubyutils.js +13 -0
- package/lib/ecosystems/rusi.js +126 -6
- package/lib/evinser/evinser.js +141 -13
- package/lib/helpers/agenticToolutils.js +925 -0
- package/lib/helpers/remote/dependency-track.js +81 -14
- 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 +17 -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/depsUtils.js +72 -0
- package/lib/inventory/dosai.js +508 -22
- package/lib/inventory/envcontext.js +215 -24
- package/lib/inventory/evidenceUtils.js +13 -0
- package/lib/inventory/fetchBatch.js +131 -2
- package/lib/inventory/fetchRate.js +74 -11
- package/lib/inventory/forgeEnricher.js +3 -2
- package/lib/inventory/jvmToolEnv.js +594 -0
- 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/osinfo.js +1 -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/inventory/toolRequirements.js +903 -0
- package/lib/managers/binary.js +24 -0
- package/lib/managers/docker.js +26 -5
- package/lib/parsers/huggingfaceUtils.js +15 -7
- package/lib/parsers/onnx.js +294 -0
- package/lib/parsers/safetensors.js +114 -0
- package/lib/server/server.js +1 -1
- package/lib/stages/postgen/annotationBuilder.js +97 -0
- package/lib/stages/postgen/auditBom.js +76 -79
- package/lib/stages/postgen/introspection/annotate.js +320 -0
- package/lib/stages/postgen/introspection/commandFacts.js +293 -0
- package/lib/stages/postgen/introspection/emit.js +192 -0
- package/lib/stages/postgen/introspection/formulationEvidence.js +427 -0
- package/lib/stages/postgen/introspection/markers.js +250 -0
- package/lib/stages/postgen/introspection/reflect.js +1214 -0
- package/lib/stages/postgen/introspection/report.js +1208 -0
- package/lib/stages/postgen/introspection/score.js +1067 -0
- package/lib/stages/postgen/introspection/shapeCommand.js +304 -0
- package/lib/stages/postgen/parentEdge.js +156 -0
- package/lib/stages/postgen/postgen.js +284 -3
- package/lib/stages/postgen/ruleEngine.js +17 -0
- package/lib/stages/pregen/envAudit.js +42 -2
- package/lib/stages/pregen/pregen.js +1171 -6
- package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
- package/lib/validator/bomValidator.js +11 -2
- package/package.json +26 -26
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/audit/scoring.d.ts +2 -8
- package/types/lib/audit/scoring.d.ts.map +1 -1
- package/types/lib/audit/targets.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +2 -1
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +10 -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/buildLedger.d.ts +272 -0
- package/types/lib/core/buildLedger.d.ts.map +1 -0
- package/types/lib/core/deferredExit.d.ts +84 -0
- package/types/lib/core/deferredExit.d.ts.map +1 -0
- package/types/lib/core/env.d.ts +43 -1
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +21 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/logger.d.ts +2 -11
- package/types/lib/core/logger.d.ts.map +1 -1
- package/types/lib/core/severity.d.ts +11 -0
- package/types/lib/core/severity.d.ts.map +1 -0
- package/types/lib/core/syncFileWriter.d.ts +33 -0
- package/types/lib/core/syncFileWriter.d.ts.map +1 -0
- 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 +53 -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-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-jvm.d.ts.map +1 -1
- 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-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/pypiClassifiers.d.ts +80 -0
- package/types/lib/ecosystems/pypiClassifiers.d.ts.map +1 -0
- package/types/lib/ecosystems/pythonutils.d.ts +21 -1
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +34 -1
- 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/helpers/remote/dependency-track.d.ts +45 -4
- package/types/lib/helpers/remote/dependency-track.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/auditCategories.d.ts +12 -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/depsUtils.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +43 -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 +43 -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 +190 -0
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -0
- 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/osinfo.d.ts.map +1 -1
- 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/inventory/toolRequirements.d.ts +216 -0
- package/types/lib/inventory/toolRequirements.d.ts.map +1 -0
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- 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/annotationBuilder.d.ts +53 -0
- package/types/lib/stages/postgen/annotationBuilder.d.ts.map +1 -0
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/introspection/annotate.d.ts +81 -0
- package/types/lib/stages/postgen/introspection/annotate.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/commandFacts.d.ts +32 -0
- package/types/lib/stages/postgen/introspection/commandFacts.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/emit.d.ts +71 -0
- package/types/lib/stages/postgen/introspection/emit.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts +154 -0
- package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts +58 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/reflect.d.ts +77 -0
- package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/report.d.ts +85 -0
- package/types/lib/stages/postgen/introspection/report.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/score.d.ts +149 -0
- package/types/lib/stages/postgen/introspection/score.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/shapeCommand.d.ts +79 -0
- package/types/lib/stages/postgen/introspection/shapeCommand.d.ts.map +1 -0
- package/types/lib/stages/postgen/parentEdge.d.ts +35 -0
- package/types/lib/stages/postgen/parentEdge.d.ts.map +1 -0
- package/types/lib/stages/postgen/postgen.d.ts +35 -2
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
- package/types/lib/stages/pregen/pregen.d.ts +131 -0
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
// PyPI Trove classifier interpretation and package -> module-name resolution.
|
|
2
|
+
//
|
|
3
|
+
// PyPI responses carry two structured signals that were previously read only for
|
|
4
|
+
// their license lines and then discarded:
|
|
5
|
+
//
|
|
6
|
+
// * `Framework :: X` — the author's declaration that the distribution belongs to
|
|
7
|
+
// framework X. It is more reliable than any name-prefix heuristic: it catches
|
|
8
|
+
// `flask-talisman`, `dj-database-url` and `starlette-context` without anyone
|
|
9
|
+
// maintaining a list, and it never fires on a package that merely has a
|
|
10
|
+
// framework's name embedded in its own.
|
|
11
|
+
// * `Topic :: ...` — a curated taxonomy of what the distribution does, which maps
|
|
12
|
+
// cleanly onto the description-tag vocabulary in `data/component-tags.json`.
|
|
13
|
+
//
|
|
14
|
+
// Tags derived here flow into `component.tags` and, for the `framework` tag, into
|
|
15
|
+
// `determinePackageType()` in `lib/cli/bomAssembly.js`, which already promotes any
|
|
16
|
+
// package carrying a `framework` tag to `type: "framework"`.
|
|
17
|
+
|
|
18
|
+
import { PYPI_MODULE_PACKAGE_MAPPING } from "../core/state.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* `Framework ::` values that do not mean "an application is built on this".
|
|
22
|
+
*
|
|
23
|
+
* Two groups, both of which would otherwise put a `framework` tag — and therefore
|
|
24
|
+
* `type: "framework"` — on a large share of an ordinary dependency tree, drowning
|
|
25
|
+
* the runtime frameworks the tag exists to surface:
|
|
26
|
+
*
|
|
27
|
+
* * Development, build, test and documentation tooling. A pytest plugin
|
|
28
|
+
* declaring `Framework :: Pytest` is making a true statement, but it belongs
|
|
29
|
+
* to the dev dependencies, not to what the application runs on.
|
|
30
|
+
* * Concurrency runtimes. `Framework :: AsyncIO` is declared by a very large
|
|
31
|
+
* number of libraries to advertise that they work under asyncio; observed on
|
|
32
|
+
* `fastapi`, and `Framework :: AnyIO` likewise on `starlette`. It describes an
|
|
33
|
+
* execution model, not a framework the application is written against.
|
|
34
|
+
*
|
|
35
|
+
* The declared name is still emitted as a tag in every case, and still recorded in
|
|
36
|
+
* `cdx:pypi:frameworks`; only the generic `framework` tag is withheld.
|
|
37
|
+
*/
|
|
38
|
+
export const PYPI_NON_APPLICATION_FRAMEWORKS = new Set([
|
|
39
|
+
"anyio",
|
|
40
|
+
"asyncio",
|
|
41
|
+
"buildout",
|
|
42
|
+
"cookiecutter",
|
|
43
|
+
"flake8",
|
|
44
|
+
"hatch",
|
|
45
|
+
"hypothesis",
|
|
46
|
+
"mkdocs",
|
|
47
|
+
"pytest",
|
|
48
|
+
"robotframework",
|
|
49
|
+
"setuptools",
|
|
50
|
+
"sphinx",
|
|
51
|
+
"tox",
|
|
52
|
+
"trio",
|
|
53
|
+
"wheel",
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* `Topic ::` prefix -> tags, longest prefix wins.
|
|
58
|
+
*
|
|
59
|
+
* Every value on the right-hand side is an existing entry in
|
|
60
|
+
* `data/component-tags.json` so that a tag derived from a classifier and the same
|
|
61
|
+
* tag derived from a description are indistinguishable to consumers.
|
|
62
|
+
*/
|
|
63
|
+
const TOPIC_TAGS = [
|
|
64
|
+
[
|
|
65
|
+
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
66
|
+
["framework"],
|
|
67
|
+
],
|
|
68
|
+
["Topic :: Software Development :: Object Brokering", ["rpc"]],
|
|
69
|
+
["Topic :: Software Development :: Testing", ["test"]],
|
|
70
|
+
["Topic :: Internet :: WWW/HTTP :: WSGI", ["web", "http", "wsgi"]],
|
|
71
|
+
["Topic :: Internet :: WWW/HTTP :: Session", ["web", "http", "auth"]],
|
|
72
|
+
["Topic :: Internet :: WWW/HTTP :: Dynamic Content", ["web", "http"]],
|
|
73
|
+
["Topic :: Internet :: WWW/HTTP", ["web", "http"]],
|
|
74
|
+
["Topic :: Internet :: XML-RPC", ["rpc", "xml"]],
|
|
75
|
+
["Topic :: Internet :: Proxy Servers", ["web", "traffic"]],
|
|
76
|
+
["Topic :: Database :: Front-Ends", ["database"]],
|
|
77
|
+
["Topic :: Database", ["database"]],
|
|
78
|
+
["Topic :: Security :: Cryptography", ["crypto", "security"]],
|
|
79
|
+
["Topic :: Security", ["security"]],
|
|
80
|
+
["Topic :: System :: Logging", ["logging"]],
|
|
81
|
+
["Topic :: System :: Distributed Computing", ["rpc"]],
|
|
82
|
+
["Topic :: Communications :: Email", ["mail"]],
|
|
83
|
+
["Topic :: Text Processing :: Markup :: HTML", ["html", "template"]],
|
|
84
|
+
["Topic :: Text Processing :: Markup :: XML", ["xml"]],
|
|
85
|
+
["Topic :: Scientific/Engineering :: Artificial Intelligence", ["ai", "ml"]],
|
|
86
|
+
[
|
|
87
|
+
"Topic :: Scientific/Engineering :: Image Recognition",
|
|
88
|
+
["ml", "object-detect"],
|
|
89
|
+
],
|
|
90
|
+
["Topic :: Office/Business :: Financial", ["finance"]],
|
|
91
|
+
["Topic :: Terminals", ["cli"]],
|
|
92
|
+
["Topic :: Utilities", ["tools"]],
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Normalize a distribution name per PEP 503.
|
|
97
|
+
*
|
|
98
|
+
* @param {string} name Distribution name
|
|
99
|
+
* @returns {string} Normalized name, or an empty string for unusable input
|
|
100
|
+
*/
|
|
101
|
+
export function normalizePypiName(name) {
|
|
102
|
+
if (!name || typeof name !== "string") {
|
|
103
|
+
return "";
|
|
104
|
+
}
|
|
105
|
+
let out = "";
|
|
106
|
+
let lastWasSeparator = false;
|
|
107
|
+
for (const ch of name.trim().toLowerCase()) {
|
|
108
|
+
if (ch === "-" || ch === "_" || ch === ".") {
|
|
109
|
+
if (!lastWasSeparator && out.length) {
|
|
110
|
+
out += "-";
|
|
111
|
+
}
|
|
112
|
+
lastWasSeparator = true;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
out += ch;
|
|
116
|
+
lastWasSeparator = false;
|
|
117
|
+
}
|
|
118
|
+
return out.endsWith("-") ? out.slice(0, -1) : out;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* `data/pypi-pkg-aliases.json` maps module name -> distribution name. Inverting it
|
|
123
|
+
* gives the mapping consumers actually need: distribution -> the module names it
|
|
124
|
+
* installs. Built once on first use.
|
|
125
|
+
*
|
|
126
|
+
* @type {Map<string, string[]> | undefined}
|
|
127
|
+
*/
|
|
128
|
+
let invertedAliases;
|
|
129
|
+
|
|
130
|
+
function packageToModules() {
|
|
131
|
+
if (invertedAliases) {
|
|
132
|
+
return invertedAliases;
|
|
133
|
+
}
|
|
134
|
+
invertedAliases = new Map();
|
|
135
|
+
for (const [moduleName, pkgName] of Object.entries(
|
|
136
|
+
PYPI_MODULE_PACKAGE_MAPPING,
|
|
137
|
+
)) {
|
|
138
|
+
const key = normalizePypiName(pkgName);
|
|
139
|
+
if (!key || !moduleName) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const existing = invertedAliases.get(key);
|
|
143
|
+
if (existing) {
|
|
144
|
+
if (!existing.includes(moduleName)) {
|
|
145
|
+
existing.push(moduleName);
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
invertedAliases.set(key, [moduleName]);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return invertedAliases;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Best-known import module names for a PyPI distribution.
|
|
156
|
+
*
|
|
157
|
+
* Two sources, in order: the curated alias map (which is where the cases that
|
|
158
|
+
* cannot be derived from the name live — `pyyaml` installs `yaml`, `absl-py`
|
|
159
|
+
* installs `absl`, `beautifulsoup4` installs `bs4`), and the name-derived form,
|
|
160
|
+
* which is correct for the large majority of distributions. Callers that need to
|
|
161
|
+
* distinguish the two should read the alias map themselves; for tagging purposes
|
|
162
|
+
* both are usable and a wrong extra candidate simply matches nothing.
|
|
163
|
+
*
|
|
164
|
+
* This is deliberately not a guess at *every* module a distribution ships — only
|
|
165
|
+
* at its top-level import names.
|
|
166
|
+
*
|
|
167
|
+
* @param {string} name Distribution name, e.g. "PyYAML"
|
|
168
|
+
* @returns {string[]} Candidate top-level module names, deduplicated
|
|
169
|
+
*/
|
|
170
|
+
export function pypiModulesForPackage(name) {
|
|
171
|
+
const normalized = normalizePypiName(name);
|
|
172
|
+
if (!normalized) {
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
175
|
+
const modules = [...(packageToModules().get(normalized) || [])];
|
|
176
|
+
// The name-derived form. PEP 503 collapses `.` and `_` to `-`; imports use `_`.
|
|
177
|
+
const derived = normalized.replaceAll("-", "_");
|
|
178
|
+
if (derived && !modules.includes(derived)) {
|
|
179
|
+
modules.push(derived);
|
|
180
|
+
}
|
|
181
|
+
return modules;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Interpret a PyPI classifier list.
|
|
186
|
+
*
|
|
187
|
+
* @param {string[]} classifiers Raw `info.classifiers` from the PyPI JSON API
|
|
188
|
+
* @returns {{frameworks: string[], tags: string[]}} Declared frameworks (lowercased,
|
|
189
|
+
* e.g. `["django"]`) and the tag vocabulary they and the `Topic ::` lines imply
|
|
190
|
+
*/
|
|
191
|
+
export function parsePypiClassifiers(classifiers) {
|
|
192
|
+
const frameworks = [];
|
|
193
|
+
const tags = new Set();
|
|
194
|
+
if (!Array.isArray(classifiers)) {
|
|
195
|
+
return { frameworks, tags: [] };
|
|
196
|
+
}
|
|
197
|
+
for (const classifier of classifiers) {
|
|
198
|
+
if (!classifier || typeof classifier !== "string") {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (classifier.startsWith("Framework :: ")) {
|
|
202
|
+
// "Framework :: Django :: 5.0" -> "django". The version segment is dropped:
|
|
203
|
+
// it is the framework version the package supports, not a distinct signal.
|
|
204
|
+
const declared = classifier.split(" :: ")[1]?.trim();
|
|
205
|
+
if (!declared) {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const name = declared.toLowerCase().replaceAll(" ", "-");
|
|
209
|
+
if (!frameworks.includes(name)) {
|
|
210
|
+
frameworks.push(name);
|
|
211
|
+
}
|
|
212
|
+
tags.add(name);
|
|
213
|
+
if (!PYPI_NON_APPLICATION_FRAMEWORKS.has(name)) {
|
|
214
|
+
tags.add("framework");
|
|
215
|
+
}
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (!classifier.startsWith("Topic :: ")) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
for (const [prefix, topicTags] of TOPIC_TAGS) {
|
|
222
|
+
if (classifier === prefix || classifier.startsWith(`${prefix} ::`)) {
|
|
223
|
+
for (const tag of topicTags) {
|
|
224
|
+
tags.add(tag);
|
|
225
|
+
}
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return { frameworks, tags: Array.from(tags) };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function addProperty(pkg, name, value) {
|
|
234
|
+
if (!value) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (!pkg.properties) {
|
|
238
|
+
pkg.properties = [];
|
|
239
|
+
}
|
|
240
|
+
const existing = pkg.properties.find((prop) => prop.name === name);
|
|
241
|
+
if (existing) {
|
|
242
|
+
existing.value = value;
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
pkg.properties.push({ name, value });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Attach classifier-derived tags and properties to a package.
|
|
250
|
+
*
|
|
251
|
+
* Tags are merged into any the package already carries rather than replacing
|
|
252
|
+
* them — `lib/managers/binary.js` sets `tags: ["source"]` as an identity marker,
|
|
253
|
+
* and losing it would change how that package is reconciled later.
|
|
254
|
+
*
|
|
255
|
+
* @param {Object} pkg Package object, mutated in place
|
|
256
|
+
* @param {string[]} classifiers Raw `info.classifiers` from the PyPI JSON API
|
|
257
|
+
* @returns {Object} The same package object
|
|
258
|
+
*/
|
|
259
|
+
export function applyPypiClassifierMetadata(pkg, classifiers) {
|
|
260
|
+
if (!pkg || !Array.isArray(classifiers) || !classifiers.length) {
|
|
261
|
+
return pkg;
|
|
262
|
+
}
|
|
263
|
+
const { frameworks, tags } = parsePypiClassifiers(classifiers);
|
|
264
|
+
if (tags.length) {
|
|
265
|
+
const merged = new Set(Array.isArray(pkg.tags) ? pkg.tags : []);
|
|
266
|
+
for (const tag of tags) {
|
|
267
|
+
merged.add(tag);
|
|
268
|
+
}
|
|
269
|
+
pkg.tags = Array.from(merged);
|
|
270
|
+
}
|
|
271
|
+
if (frameworks.length) {
|
|
272
|
+
addProperty(pkg, "cdx:pypi:frameworks", frameworks.join(","));
|
|
273
|
+
}
|
|
274
|
+
// The full list is retained so that consumers wanting the whole taxonomy are
|
|
275
|
+
// not limited to the tag vocabulary mapped above. Classifiers come from a
|
|
276
|
+
// closed PyPI-maintained set, so there is no free-text or secret exposure.
|
|
277
|
+
addProperty(
|
|
278
|
+
pkg,
|
|
279
|
+
"cdx:pypi:classifiers",
|
|
280
|
+
classifiers.filter((c) => typeof c === "string" && c.length).join("\n"),
|
|
281
|
+
);
|
|
282
|
+
return pkg;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Attach the distribution's top-level import module names as `internal:Namespaces`.
|
|
287
|
+
*
|
|
288
|
+
* This is the same property jar, gem, nuget and composer components already carry,
|
|
289
|
+
* so downstream consumers that map namespaces back to components need no
|
|
290
|
+
* pypi-specific path. Unlike the jar case the value is name-derived and
|
|
291
|
+
* alias-curated rather than read out of the artifact, which is why
|
|
292
|
+
* `cdx:pypi:modulesFrom` records the provenance.
|
|
293
|
+
*
|
|
294
|
+
* @param {Object} pkg Package object, mutated in place
|
|
295
|
+
* @returns {Object} The same package object
|
|
296
|
+
*/
|
|
297
|
+
export function applyPypiModuleNames(pkg) {
|
|
298
|
+
if (!pkg?.name) {
|
|
299
|
+
return pkg;
|
|
300
|
+
}
|
|
301
|
+
// Extras (`requests[security]`) are not part of the distribution name.
|
|
302
|
+
const name = pkg.name.includes("[") ? pkg.name.split("[")[0] : pkg.name;
|
|
303
|
+
if (name.includes("https")) {
|
|
304
|
+
return pkg;
|
|
305
|
+
}
|
|
306
|
+
const normalized = normalizePypiName(name);
|
|
307
|
+
const modules = pypiModulesForPackage(name);
|
|
308
|
+
if (!modules.length) {
|
|
309
|
+
return pkg;
|
|
310
|
+
}
|
|
311
|
+
// Most alias-map entries map a distribution to the module its own name already
|
|
312
|
+
// implies, so the map having an entry says nothing. The provenance worth
|
|
313
|
+
// recording is whether it contributed a module the name could not have given -
|
|
314
|
+
// that is the difference between `flask` and `pyyaml` -> `yaml`.
|
|
315
|
+
const derived = normalized.replaceAll("-", "_");
|
|
316
|
+
const curatedAddedSomething = (packageToModules().get(normalized) || []).some(
|
|
317
|
+
(moduleName) => moduleName !== derived,
|
|
318
|
+
);
|
|
319
|
+
addProperty(pkg, "internal:Namespaces", modules.join("\n"));
|
|
320
|
+
addProperty(
|
|
321
|
+
pkg,
|
|
322
|
+
"cdx:pypi:modulesFrom",
|
|
323
|
+
curatedAddedSomething ? "alias-map,distribution-name" : "distribution-name",
|
|
324
|
+
);
|
|
325
|
+
return pkg;
|
|
326
|
+
}
|
|
@@ -331,6 +331,53 @@ export function get_python_command_from_env(env) {
|
|
|
331
331
|
return fallbackCmd;
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
+
/**
|
|
335
|
+
* Is this import satisfied by a module that lives in the project itself?
|
|
336
|
+
*
|
|
337
|
+
* `atom parsedeps` reports every non-stdlib import it sees, including imports of the
|
|
338
|
+
* project's own packages. Attributing those to a distribution invents dependencies:
|
|
339
|
+
* `data/pypi-pkg-aliases.json` maps the module `app` to `zope2`, `test` to `pytabix`,
|
|
340
|
+
* `models` to `asposestorage` and `src` to `auto-mix-prep`, so any project with a
|
|
341
|
+
* local package of that name acquires an obscure component it never depended on -
|
|
342
|
+
* along with every namespace and tag that component carries downstream. Filtering by
|
|
343
|
+
* name would need a denylist of every plausible first-party name; asking the
|
|
344
|
+
* filesystem whether the module is ours answers it exactly.
|
|
345
|
+
*
|
|
346
|
+
* Checks the project root and a `src/` layout for a module file, a package directory,
|
|
347
|
+
* and a PEP 420 namespace package directory.
|
|
348
|
+
*
|
|
349
|
+
* @param {string} src Path to the project being analysed
|
|
350
|
+
* @param {string} name Imported top-level module name
|
|
351
|
+
* @returns {boolean} `true` if the project supplies this module itself
|
|
352
|
+
*/
|
|
353
|
+
export function isFirstPartyModule(src, name) {
|
|
354
|
+
if (!src || !name || name.includes("/") || name.includes("\\")) {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
for (const root of [src, join(src, "src")]) {
|
|
358
|
+
if (
|
|
359
|
+
safeExistsSync(join(root, `${name}.py`)) ||
|
|
360
|
+
safeExistsSync(join(root, name, "__init__.py")) ||
|
|
361
|
+
safeExistsSync(join(root, name, "__main__.py"))
|
|
362
|
+
) {
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
// PEP 420 namespace package: a directory of the right name holding python
|
|
366
|
+
// sources but no __init__.py.
|
|
367
|
+
const candidate = join(root, name);
|
|
368
|
+
if (safeExistsSync(candidate)) {
|
|
369
|
+
try {
|
|
370
|
+
if (readdirSync(candidate).some((f) => f.endsWith(".py"))) {
|
|
371
|
+
return true;
|
|
372
|
+
}
|
|
373
|
+
} catch {
|
|
374
|
+
// not a readable directory - treat as not first-party
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
|
|
334
381
|
/**
|
|
335
382
|
* Method to find python modules by parsing the imports and then checking with PyPI to obtain the latest version
|
|
336
383
|
*
|
|
@@ -362,7 +409,8 @@ export async function getPyModules(src, epkgList, options) {
|
|
|
362
409
|
(x) =>
|
|
363
410
|
!pyDefaultModules.has(x.name.toLowerCase()) &&
|
|
364
411
|
!x.name.startsWith("_") &&
|
|
365
|
-
!x.name.startsWith(".")
|
|
412
|
+
!x.name.startsWith(".") &&
|
|
413
|
+
!isFirstPartyModule(src, x.name),
|
|
366
414
|
);
|
|
367
415
|
let pkgList = modList.map((p) => {
|
|
368
416
|
const apkg = {
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
prefetchEnabled,
|
|
20
20
|
prefetchedResponse,
|
|
21
21
|
prefetchJson,
|
|
22
|
+
recordPolicyDegradationFromError,
|
|
22
23
|
} from "../inventory/fetchBatch.js";
|
|
23
24
|
|
|
24
25
|
// FIXME. This has to get removed, once we improve the module detection one-liner.
|
|
@@ -780,6 +781,7 @@ export async function getRubyGemsMetadata(pkgList) {
|
|
|
780
781
|
continue;
|
|
781
782
|
}
|
|
782
783
|
} catch (err) {
|
|
784
|
+
recordPolicyDegradationFromError(err);
|
|
783
785
|
if (DEBUG_MODE) {
|
|
784
786
|
console.error(name, err);
|
|
785
787
|
}
|
|
@@ -832,6 +834,7 @@ async function enrichGemFromVersionEndpoint(
|
|
|
832
834
|
}
|
|
833
835
|
applyGemMetadata(p, body);
|
|
834
836
|
} catch (err) {
|
|
837
|
+
recordPolicyDegradationFromError(err);
|
|
835
838
|
if (DEBUG_MODE) {
|
|
836
839
|
console.error(p, err);
|
|
837
840
|
}
|
|
@@ -1241,6 +1244,16 @@ export async function parseGemspecData(gemspecData, gemspecFile) {
|
|
|
1241
1244
|
pkg.externalReferences.push({ type: refType, url: value, comment: key });
|
|
1242
1245
|
}
|
|
1243
1246
|
}
|
|
1247
|
+
// `s.homepage = "..."` is the project website. Keep it as an external
|
|
1248
|
+
// reference; the transient `homepage` key is a plain string that never
|
|
1249
|
+
// reaches the BOM because component conversion expects the `{ url }` shape.
|
|
1250
|
+
if (pkg.homepage) {
|
|
1251
|
+
pkg.externalReferences = pkg.externalReferences || [];
|
|
1252
|
+
if (!pkg.externalReferences.some((ref) => ref.url === pkg.homepage)) {
|
|
1253
|
+
pkg.externalReferences.push({ type: "website", url: pkg.homepage });
|
|
1254
|
+
}
|
|
1255
|
+
delete pkg.homepage;
|
|
1256
|
+
}
|
|
1244
1257
|
if (pkg.name) {
|
|
1245
1258
|
const purlString = toGemPurl(pkg.name, pkg.version, gemPlatform);
|
|
1246
1259
|
pkg.purl = purlString;
|
package/lib/ecosystems/rusi.js
CHANGED
|
@@ -9,11 +9,16 @@ import {
|
|
|
9
9
|
safeRmSync,
|
|
10
10
|
safeSpawnSync,
|
|
11
11
|
} from "../core/fs.js";
|
|
12
|
+
import { dirNameStr } from "../core/paths.js";
|
|
12
13
|
import { applyAlgorithmProperties } from "../inventory/cryptoAlgorithmFamily.js";
|
|
13
14
|
import { resolvePluginBinary } from "../inventory/plugins.js";
|
|
14
15
|
|
|
15
16
|
const RUST_LANGUAGES = new Set(["rust", "rs", "rust-lang"]);
|
|
16
17
|
|
|
18
|
+
const RUSI_CRYPTO_OIDS = JSON.parse(
|
|
19
|
+
readFileSync(join(dirNameStr, "data", "crypto-oid.json"), "utf-8"),
|
|
20
|
+
);
|
|
21
|
+
|
|
17
22
|
/**
|
|
18
23
|
* Resolves the rusi binary.
|
|
19
24
|
*
|
|
@@ -82,6 +87,75 @@ function createPurlAliasMap(components = []) {
|
|
|
82
87
|
return purlAliasMap;
|
|
83
88
|
}
|
|
84
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Path roots that never name a dependency crate.
|
|
92
|
+
*/
|
|
93
|
+
const RUST_NON_CRATE_PATH_ROOTS = new Set([
|
|
94
|
+
"crate",
|
|
95
|
+
"self",
|
|
96
|
+
"super",
|
|
97
|
+
"std",
|
|
98
|
+
"core",
|
|
99
|
+
"alloc",
|
|
100
|
+
"proc_macro",
|
|
101
|
+
"test",
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Index the cargo components by the identifier a Rust path spells them with.
|
|
106
|
+
*
|
|
107
|
+
* A crate named `cfg-if` is written `cfg_if` in source, and a lock file can
|
|
108
|
+
* carry several versions of the same crate, so a name maps to every matching
|
|
109
|
+
* purl.
|
|
110
|
+
*
|
|
111
|
+
* @param {Object[]} components Components from the BOM
|
|
112
|
+
* @returns {Map<string, string[]>} Crate identifier to purls
|
|
113
|
+
*/
|
|
114
|
+
function createCrateNameIndex(components = []) {
|
|
115
|
+
const crateNameIndex = new Map();
|
|
116
|
+
for (const component of components) {
|
|
117
|
+
if (!component?.purl?.startsWith("pkg:cargo/") || !component.name) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const crateIdentifier = component.name.toLowerCase().replaceAll("-", "_");
|
|
121
|
+
const purls = crateNameIndex.get(crateIdentifier) || [];
|
|
122
|
+
if (!purls.includes(component.purl)) {
|
|
123
|
+
purls.push(component.purl);
|
|
124
|
+
}
|
|
125
|
+
crateNameIndex.set(crateIdentifier, purls);
|
|
126
|
+
}
|
|
127
|
+
return crateNameIndex;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Resolve the dependency crates a Rust path refers to.
|
|
132
|
+
*
|
|
133
|
+
* rusi reports an import against the package that declares it, so the imported
|
|
134
|
+
* crate has to be recovered from the path itself. This is what turns an
|
|
135
|
+
* `use hex::encode;` into usage evidence for the `hex` component.
|
|
136
|
+
*
|
|
137
|
+
* @param {string} importPath Rust path, such as `hex::encode`
|
|
138
|
+
* @param {Map<string, string[]>} crateNameIndex Index from {@link createCrateNameIndex}
|
|
139
|
+
* @param {string} [ownerPurl] purl of the package that declares the path
|
|
140
|
+
* @returns {string[]} Matching component purls
|
|
141
|
+
*/
|
|
142
|
+
function resolveImportedCratePurls(importPath, crateNameIndex, ownerPurl) {
|
|
143
|
+
if (!importPath || !crateNameIndex?.size) {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
const pathRoot = String(importPath)
|
|
147
|
+
.replace(/^::/, "")
|
|
148
|
+
.split("::")[0]
|
|
149
|
+
.trim()
|
|
150
|
+
.toLowerCase();
|
|
151
|
+
if (!pathRoot || RUST_NON_CRATE_PATH_ROOTS.has(pathRoot)) {
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
return (crateNameIndex.get(pathRoot) || []).filter(
|
|
155
|
+
(purl) => purl !== ownerPurl,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
85
159
|
function resolveComponentPurl(purl, purlAliasMap) {
|
|
86
160
|
if (!purl) {
|
|
87
161
|
return undefined;
|
|
@@ -182,19 +256,37 @@ function mergeCryptoComponent(componentsByRef, component, item) {
|
|
|
182
256
|
return existing;
|
|
183
257
|
}
|
|
184
258
|
|
|
259
|
+
// The schema requires an oid for algorithm assets, so an algorithm the OID
|
|
260
|
+
// table cannot name is skipped rather than emitted invalid. rusi reports
|
|
261
|
+
// display-cased names ("SHA-256") while the table is lowercase ("sha-256").
|
|
262
|
+
function resolveCryptoAlgorithmMetadata(name) {
|
|
263
|
+
if (!name) {
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
const lowered = String(name).toLowerCase();
|
|
267
|
+
return (
|
|
268
|
+
RUSI_CRYPTO_OIDS[name] ||
|
|
269
|
+
RUSI_CRYPTO_OIDS[lowered] ||
|
|
270
|
+
RUSI_CRYPTO_OIDS[lowered.replace(/[\s_]+/g, "-")]
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
185
274
|
function cryptoAlgorithmComponent(asset) {
|
|
275
|
+
const algorithmName = asset.algorithm || asset.kind || "unknown";
|
|
276
|
+
const algorithmMetadata = resolveCryptoAlgorithmMetadata(algorithmName);
|
|
277
|
+
if (!algorithmMetadata?.oid) {
|
|
278
|
+
return undefined;
|
|
279
|
+
}
|
|
186
280
|
const component = {
|
|
187
281
|
type: "cryptographic-asset",
|
|
188
|
-
name:
|
|
189
|
-
"bom-ref": cryptoBomRef(
|
|
190
|
-
"algorithm",
|
|
191
|
-
asset.algorithm || asset.kind,
|
|
192
|
-
asset.provider || "unknown",
|
|
193
|
-
),
|
|
282
|
+
name: algorithmName,
|
|
283
|
+
"bom-ref": cryptoBomRef("algorithm", algorithmName, algorithmMetadata.oid),
|
|
194
284
|
description:
|
|
285
|
+
algorithmMetadata.description ||
|
|
195
286
|
"Cryptographic algorithm/component detected by rusi source analysis",
|
|
196
287
|
cryptoProperties: {
|
|
197
288
|
assetType: "algorithm",
|
|
289
|
+
oid: algorithmMetadata.oid,
|
|
198
290
|
algorithmProperties: { primitive: asset.kind || "unknown" },
|
|
199
291
|
},
|
|
200
292
|
properties: [],
|
|
@@ -361,6 +453,7 @@ function addImportEvidence(
|
|
|
361
453
|
purlAliasMap,
|
|
362
454
|
purlLocationMap,
|
|
363
455
|
componentPropertiesMap,
|
|
456
|
+
crateNameIndex,
|
|
364
457
|
) {
|
|
365
458
|
for (const importUsage of rusiReport.imports || []) {
|
|
366
459
|
const purl = resolveComponentPurl(importUsage.purl, purlAliasMap);
|
|
@@ -380,6 +473,31 @@ function addImportEvidence(
|
|
|
380
473
|
"cdx:rusi:importAlias",
|
|
381
474
|
importUsage.alias,
|
|
382
475
|
);
|
|
476
|
+
// The import is also evidence about the crate being imported, which is the
|
|
477
|
+
// component a consumer filters on.
|
|
478
|
+
for (const cratePurl of resolveImportedCratePurls(
|
|
479
|
+
importUsage.path,
|
|
480
|
+
crateNameIndex,
|
|
481
|
+
purl,
|
|
482
|
+
)) {
|
|
483
|
+
addSetValue(
|
|
484
|
+
purlLocationMap,
|
|
485
|
+
cratePurl,
|
|
486
|
+
positionLocation(importUsage.position),
|
|
487
|
+
);
|
|
488
|
+
addPropertyValue(
|
|
489
|
+
componentPropertiesMap,
|
|
490
|
+
cratePurl,
|
|
491
|
+
"cdx:rusi:importedBy",
|
|
492
|
+
purl,
|
|
493
|
+
);
|
|
494
|
+
addPropertyValue(
|
|
495
|
+
componentPropertiesMap,
|
|
496
|
+
cratePurl,
|
|
497
|
+
"cdx:rusi:importPath",
|
|
498
|
+
importUsage.path,
|
|
499
|
+
);
|
|
500
|
+
}
|
|
383
501
|
}
|
|
384
502
|
}
|
|
385
503
|
|
|
@@ -761,6 +879,7 @@ export function analyzeRusiProject(src, options = {}) {
|
|
|
761
879
|
*/
|
|
762
880
|
export function collectRusiEvidence(rusiReport = {}, components = []) {
|
|
763
881
|
const purlAliasMap = createPurlAliasMap(components);
|
|
882
|
+
const crateNameIndex = createCrateNameIndex(components);
|
|
764
883
|
const purlLocationMap = {};
|
|
765
884
|
const dataFlowFrames = {};
|
|
766
885
|
const componentPropertiesMap = {};
|
|
@@ -774,6 +893,7 @@ export function collectRusiEvidence(rusiReport = {}, components = []) {
|
|
|
774
893
|
purlAliasMap,
|
|
775
894
|
purlLocationMap,
|
|
776
895
|
componentPropertiesMap,
|
|
896
|
+
crateNameIndex,
|
|
777
897
|
);
|
|
778
898
|
addUsageEvidence(
|
|
779
899
|
rusiReport,
|