@cdxgen/cdxgen 13.6.0 → 13.7.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 +15 -8
- package/bin/audit.js +7 -4
- package/bin/cdxgen.js +256 -37
- package/bin/convert.js +146 -22
- package/bin/evinse.js +25 -3
- package/bin/repl.js +47 -3
- package/bin/sign.js +211 -34
- package/bin/tracebom.js +18 -6
- package/bin/validate.js +18 -3
- package/bin/verify.js +113 -5
- package/data/behavior-taxonomy.schema.json +1491 -0
- package/data/cyclonedx-2.0-bundled.schema.json +18606 -5528
- package/data/cyclonedx-2.0-cryptography-defs.schema.json +857 -0
- package/data/pqc-migration.json +239 -0
- package/data/remediations.json +52 -0
- package/data/rules/cbom-security.yaml +4 -3
- package/data/rules/package-integrity.yaml +28 -0
- package/lib/audit/index.js +25 -3
- package/lib/cli/bomAssembly.js +80 -1
- package/lib/cli/cliOptions.js +121 -17
- package/lib/cli/index.js +609 -898
- package/lib/cli/jsBom.js +3 -0
- package/lib/cli/jvmBom.js +365 -40
- package/lib/cli/managedBom.js +19 -3
- package/lib/cli/nativeBom.js +10 -0
- package/lib/core/env.js +26 -0
- package/lib/core/fs.js +126 -3
- package/lib/core/globPatterns.js +218 -0
- package/lib/core/processTree.js +99 -12
- package/lib/core/projectConfig.js +332 -25
- package/lib/core/toolRegistry.js +15 -1
- package/lib/ecosystems/denoutils.js +2 -2
- package/lib/ecosystems/golem.js +3 -8
- package/lib/ecosystems/gradleutils.js +1082 -6
- package/lib/ecosystems/jsEvidence.js +90 -38
- package/lib/ecosystems/kosi.js +3 -5
- package/lib/ecosystems/npmVersionResolution.js +376 -0
- package/lib/ecosystems/parsers-python.js +89 -28
- package/lib/ecosystems/parsers-rust.js +117 -15
- package/lib/ecosystems/remote/huggingface.js +22 -0
- package/lib/ecosystems/rusi.js +2 -4
- package/lib/ecosystems/scalasem.js +2 -1
- package/lib/evinser/evinser.js +196 -34
- package/lib/evinser/scalasem.js +5 -1
- package/lib/helpers/agenticToolutils.js +112 -1
- package/lib/helpers/bomSigner.js +43 -9
- package/lib/helpers/jssSigner.js +1276 -0
- package/lib/inventory/agentBlueprint.js +193 -0
- package/lib/inventory/agentToolNames.js +145 -0
- package/lib/inventory/aiCollector.js +153 -7
- package/lib/inventory/aiInventory.js +44 -8
- package/lib/inventory/aiProviders.js +324 -0
- package/lib/inventory/bomUtils.js +8 -1
- package/lib/inventory/ciParsers/githubActionsLock.js +821 -0
- package/lib/inventory/citations.js +31 -2
- package/lib/inventory/communityAiConfigParser.js +978 -80
- package/lib/inventory/cyclonedx20.js +152 -0
- package/lib/inventory/cyclonedx20Common.js +286 -0
- package/lib/inventory/cyclonedx20Derived.js +159 -0
- package/lib/inventory/cyclonedx20Parties.js +552 -0
- package/lib/inventory/cyclonedx20Read.js +768 -0
- package/lib/inventory/cyclonedx20ReadCommon.js +374 -0
- package/lib/inventory/cyclonedx20ReadComponents.js +692 -0
- package/lib/inventory/cyclonedx20ReadContext.js +380 -0
- package/lib/inventory/cyclonedx20ReadEvidence.js +466 -0
- package/lib/inventory/cyclonedx20ReadSections.js +599 -0
- package/lib/inventory/cyclonedx20Render.js +766 -0
- package/lib/inventory/cyclonedx20RenderComponents.js +716 -0
- package/lib/inventory/cyclonedx20RenderDefinitions.js +405 -0
- package/lib/inventory/cyclonedx20RenderEvidence.js +726 -0
- package/lib/inventory/cyclonedx20RenderHardware.js +203 -0
- package/lib/inventory/cyclonedx20RenderPatents.js +174 -0
- package/lib/inventory/cyclonedx20RenderRefIds.js +314 -0
- package/lib/inventory/cyclonedx20RenderSections.js +499 -0
- package/lib/inventory/cyclonedx20Tables.js +594 -0
- package/lib/inventory/deps.js +106 -13
- package/lib/inventory/dosai.js +236 -1
- package/lib/inventory/dosaiParsers.js +3 -6
- package/lib/inventory/dotnetRuntime.js +164 -14
- package/lib/inventory/ecosystemFormulationParsers.js +979 -0
- package/lib/inventory/envcontext.js +132 -38
- package/lib/inventory/evidenceUtils.js +2 -1
- package/lib/inventory/formulationParsers.js +95 -13
- package/lib/inventory/hbom.js +45 -6
- package/lib/inventory/hbomLoader.js +166 -0
- package/lib/inventory/mcpConfigParser.js +5 -0
- package/lib/inventory/occurrenceKey.js +90 -0
- package/lib/inventory/pqcMigration.js +426 -0
- package/lib/inventory/rustFormulationParser.js +21 -10
- package/lib/inventory/source.js +65 -6
- package/lib/parsers/huggingfaceManifest.js +9 -1
- package/lib/parsers/huggingfaceUtils.js +18 -0
- package/lib/parsers/kiroPermissions.js +166 -0
- package/lib/server/server.js +36 -34
- package/lib/stages/postgen/postgen.js +41 -1
- package/lib/stages/postgen/specVersionCompat.js +76 -0
- package/lib/validator/bomValidator.js +8 -1
- package/lib/validator/complianceEngine.js +30 -3
- package/lib/validator/complianceRules.js +1416 -4
- package/lib/validator/generated/validate-2.0.mjs +57554 -13851
- package/lib/validator/index.js +48 -7
- package/lib/validator/reporters/json.js +6 -0
- package/package.json +9 -9
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts +6 -2
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +16 -0
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +6 -0
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts +2 -1
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts +15 -0
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/boundedFile.d.ts +30 -0
- package/types/lib/core/boundedFile.d.ts.map +1 -1
- package/types/lib/core/env.d.ts +10 -0
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +4 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/globPatterns.d.ts +70 -0
- package/types/lib/core/globPatterns.d.ts.map +1 -0
- package/types/lib/core/paths.d.ts +9 -0
- package/types/lib/core/paths.d.ts.map +1 -1
- package/types/lib/core/processTree.d.ts +16 -5
- package/types/lib/core/processTree.d.ts.map +1 -1
- package/types/lib/core/projectConfig.d.ts +47 -8
- package/types/lib/core/projectConfig.d.ts.map +1 -1
- package/types/lib/core/toolRegistry.d.ts.map +1 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +248 -8
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/jsEvidence.d.ts +14 -1
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts.map +1 -1
- package/types/lib/ecosystems/npmVersionResolution.d.ts +119 -0
- package/types/lib/ecosystems/npmVersionResolution.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +1 -1
- package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/ecosystems/scalasem.d.ts +58 -16
- package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +61 -9
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/scalasem.d.ts.map +1 -1
- package/types/lib/helpers/agenticToolutils.d.ts +53 -19
- package/types/lib/helpers/agenticToolutils.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +20 -3
- package/types/lib/helpers/bomSigner.d.ts.map +1 -1
- package/types/lib/helpers/jssSigner.d.ts +288 -0
- package/types/lib/helpers/jssSigner.d.ts.map +1 -0
- package/types/lib/helpers/vsixutils.d.ts +303 -19
- package/types/lib/helpers/vsixutils.d.ts.map +1 -1
- package/types/lib/inventory/agentBlueprint.d.ts +70 -0
- package/types/lib/inventory/agentBlueprint.d.ts.map +1 -0
- package/types/lib/inventory/agentToolNames.d.ts +51 -0
- package/types/lib/inventory/agentToolNames.d.ts.map +1 -0
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/aiInventory.d.ts.map +1 -1
- package/types/lib/inventory/aiProviders.d.ts +85 -0
- package/types/lib/inventory/aiProviders.d.ts.map +1 -0
- package/types/lib/inventory/bomUtils.d.ts.map +1 -1
- package/types/lib/inventory/ciParsers/githubActionsLock.d.ts +101 -0
- package/types/lib/inventory/ciParsers/githubActionsLock.d.ts.map +1 -0
- package/types/lib/inventory/citations.d.ts +5 -0
- package/types/lib/inventory/citations.d.ts.map +1 -1
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/cyclonedx20.d.ts +99 -0
- package/types/lib/inventory/cyclonedx20.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Common.d.ts +102 -0
- package/types/lib/inventory/cyclonedx20Common.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Derived.d.ts +74 -0
- package/types/lib/inventory/cyclonedx20Derived.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Parties.d.ts +91 -0
- package/types/lib/inventory/cyclonedx20Parties.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Read.d.ts +51 -0
- package/types/lib/inventory/cyclonedx20Read.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadCommon.d.ts +69 -0
- package/types/lib/inventory/cyclonedx20ReadCommon.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadComponents.d.ts +77 -0
- package/types/lib/inventory/cyclonedx20ReadComponents.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadContext.d.ts +118 -0
- package/types/lib/inventory/cyclonedx20ReadContext.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts +40 -0
- package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadSections.d.ts +45 -0
- package/types/lib/inventory/cyclonedx20ReadSections.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Render.d.ts +53 -0
- package/types/lib/inventory/cyclonedx20Render.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderComponents.d.ts +73 -0
- package/types/lib/inventory/cyclonedx20RenderComponents.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts +29 -0
- package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts +110 -0
- package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderHardware.d.ts +42 -0
- package/types/lib/inventory/cyclonedx20RenderHardware.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderPatents.d.ts +26 -0
- package/types/lib/inventory/cyclonedx20RenderPatents.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts +55 -0
- package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderSections.d.ts +45 -0
- package/types/lib/inventory/cyclonedx20RenderSections.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Tables.d.ts +134 -0
- package/types/lib/inventory/cyclonedx20Tables.d.ts.map +1 -0
- package/types/lib/inventory/deps.d.ts +35 -2
- package/types/lib/inventory/deps.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +19 -0
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
- package/types/lib/inventory/dotnetRuntime.d.ts +51 -4
- package/types/lib/inventory/dotnetRuntime.d.ts.map +1 -1
- package/types/lib/inventory/ecosystemFormulationParsers.d.ts +73 -0
- package/types/lib/inventory/ecosystemFormulationParsers.d.ts.map +1 -0
- package/types/lib/inventory/envcontext.d.ts +22 -6
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
- package/types/lib/inventory/hbom.d.ts +11 -0
- package/types/lib/inventory/hbom.d.ts.map +1 -1
- package/types/lib/inventory/hbomLoader.d.ts +54 -0
- package/types/lib/inventory/hbomLoader.d.ts.map +1 -1
- package/types/lib/inventory/jvmLocalRepos.d.ts +5 -1
- package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -1
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/occurrenceKey.d.ts +52 -0
- package/types/lib/inventory/occurrenceKey.d.ts.map +1 -0
- package/types/lib/inventory/pqcMigration.d.ts +84 -0
- package/types/lib/inventory/pqcMigration.d.ts.map +1 -0
- package/types/lib/inventory/rustFormulationParser.d.ts +7 -2
- package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/scalaCoords.d.ts.map +1 -1
- package/types/lib/inventory/scalaDedupe.d.ts +43 -0
- package/types/lib/inventory/scalaDedupe.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts +15 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/parsers/huggingfaceManifest.d.ts +10 -1
- package/types/lib/parsers/huggingfaceManifest.d.ts.map +1 -1
- package/types/lib/parsers/huggingfaceUtils.d.ts +12 -0
- package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
- package/types/lib/parsers/kiroPermissions.d.ts +62 -0
- package/types/lib/parsers/kiroPermissions.d.ts.map +1 -0
- package/types/lib/parsers/vscodeExtensionId.d.ts +48 -0
- package/types/lib/parsers/vscodeExtensionId.d.ts.map +1 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts +7 -0
- 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/postgen/specVersionCompat.d.ts +25 -0
- package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts +5 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/complianceEngine.d.ts +4 -4
- package/types/lib/validator/complianceEngine.d.ts.map +1 -1
- package/types/lib/validator/complianceRules.d.ts +14 -2
- package/types/lib/validator/complianceRules.d.ts.map +1 -1
- package/types/lib/validator/index.d.ts +10 -0
- package/types/lib/validator/index.d.ts.map +1 -1
- package/types/lib/validator/reporters/json.d.ts.map +1 -1
package/lib/cli/index.js
CHANGED
|
@@ -53,7 +53,11 @@ import {
|
|
|
53
53
|
import { collectObomIdeInventory } from "../helpers/vsixutils.js";
|
|
54
54
|
import { optionIncludesAiInventoryProjectType } from "../inventory/aiInventory.js";
|
|
55
55
|
import { readApkInstalledDb } from "../inventory/apkInstalledDb.js";
|
|
56
|
-
import {
|
|
56
|
+
import {
|
|
57
|
+
isCycloneDx20SpecVersion,
|
|
58
|
+
isCycloneDxComponentTypeEnabled,
|
|
59
|
+
} from "../inventory/bomUtils.js";
|
|
60
|
+
import { DEPENDENCY_TRACK_CDX20_ERROR } from "../inventory/cyclonedx20.js";
|
|
57
61
|
import {
|
|
58
62
|
mergeDependencies,
|
|
59
63
|
mergeServices,
|
|
@@ -852,12 +856,50 @@ export function summarizePurlTypes(components) {
|
|
|
852
856
|
* data object (components, dependencies, bomJson, parentComponent, etc.)
|
|
853
857
|
*/
|
|
854
858
|
export async function createMultiXBom(pathList, options) {
|
|
859
|
+
/**
|
|
860
|
+
* What createMultiXBom reads from the result of a create*Bom builder.
|
|
861
|
+
*
|
|
862
|
+
* @typedef {Object} BuilderBomData
|
|
863
|
+
* @property {{components?: Object[], dependencies?: Object[], services?: Object[]}} [bomJson] The generated BOM
|
|
864
|
+
* @property {Object} [parentComponent] The project the builder detected
|
|
865
|
+
* @property {Object[]} [dependencies] Dependencies returned beside bomJson (crypto scan)
|
|
866
|
+
* @property {Object[]} [formulationList] Formulation components for postProcess
|
|
867
|
+
* @property {Object[]} [metadataProperties] Properties for metadata (php platform data)
|
|
868
|
+
*/
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* One ecosystem that createMultiXBom scans every path for.
|
|
872
|
+
*
|
|
873
|
+
* @typedef {Object} PathScanStep
|
|
874
|
+
* @property {(path: string, options: Object) => boolean} applies Whether the step runs for this path
|
|
875
|
+
* @property {(path: string, options: Object) => any} [prepare] Computes a value once for the hooks below
|
|
876
|
+
* @property {(path: string, options: Object, context: any) => void} [announce] Thought logs written before the scan
|
|
877
|
+
* @property {string|((context: any) => string)} activityType Project type recorded in the activity context
|
|
878
|
+
* @property {(path: string) => void} [beforeBuild] Runs after the activity context is set
|
|
879
|
+
* @property {(path: string, options: Object) => any} build The create*Bom builder, whose result is read as BuilderBomData
|
|
880
|
+
* @property {(bomData: BuilderBomData | undefined) => boolean} [isUseful] Whether the result is merged; by default, when it has components
|
|
881
|
+
* @property {(count: number, path: string, context: any) => string} [foundThought] Thought log for the components found
|
|
882
|
+
* @property {(count: number, path: string, context: any) => string} debugMessage Debug log for the components found
|
|
883
|
+
* @property {"concat"|"topLevel"|"mergeWithParent"} [dependencyMerge] How dependencies are merged; mergeDependencies by default
|
|
884
|
+
* @property {boolean} [mergeServices] Merge bomJson.services into options.services
|
|
885
|
+
* @property {boolean} [keepParent] Record the detected project as a sub-component; true by default
|
|
886
|
+
* @property {"plain"|"gradle"} [hoistSubComponents] Hoist the project's own sub-components
|
|
887
|
+
* @property {boolean} [collectMetadataProperties] Collect metadataProperties whether or not components were found
|
|
888
|
+
* @property {boolean} [collectFormulation] Collect the formulation list whether or not components were found
|
|
889
|
+
*/
|
|
890
|
+
|
|
891
|
+
/** @type {Object[]} */
|
|
855
892
|
let components = [];
|
|
893
|
+
/** @type {Object[]} */
|
|
856
894
|
let dependencies = [];
|
|
895
|
+
/** @type {BuilderBomData | undefined} */
|
|
857
896
|
let bomData;
|
|
897
|
+
/** @type {Object[]} */
|
|
858
898
|
let formulationList = [];
|
|
899
|
+
/** @type {Object[]} */
|
|
859
900
|
let metadataProperties = [];
|
|
860
901
|
let parentComponent = determineParentComponent(options) || {};
|
|
902
|
+
/** @type {Object[]} */
|
|
861
903
|
let parentSubComponents = [];
|
|
862
904
|
let osFileOwners;
|
|
863
905
|
const setProjectTypeActivityContext = (projectType, sourcePath) => {
|
|
@@ -960,943 +1002,600 @@ export async function createMultiXBom(pathList, options) {
|
|
|
960
1002
|
dependencies.splice(0, 0, {
|
|
961
1003
|
ref: parentComponent["bom-ref"],
|
|
962
1004
|
dependsOn: Array.from(parentDependsOn).sort(),
|
|
963
|
-
});
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
if (hasAnyProjectType(["os"], options, false) && options.bomData) {
|
|
967
|
-
bomData = options.bomData;
|
|
968
|
-
if (bomData?.bomJson?.components) {
|
|
969
|
-
if (DEBUG_MODE) {
|
|
970
|
-
console.log(`Found ${bomData.bomJson.components.length} OS components`);
|
|
971
|
-
}
|
|
972
|
-
if (bomData.bomJson.components.length) {
|
|
973
|
-
thoughtLog(
|
|
974
|
-
`I found ${bomData.bomJson.components.length} OS packages 😎.`,
|
|
975
|
-
);
|
|
976
|
-
}
|
|
977
|
-
components = components.concat(bomData.bomJson.components);
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
const sharedOptions = options;
|
|
981
|
-
// The exploded image layers are written once by the extractor and only read
|
|
982
|
-
// from here on, so repeated walks of them can share one view of the tree.
|
|
983
|
-
setDirWalkCacheRoot(options.allLayersExplodedDir);
|
|
984
|
-
const scanOnePath = async (path) => {
|
|
985
|
-
try {
|
|
986
|
-
return await scanOnePathInner(path);
|
|
987
|
-
} catch (err) {
|
|
988
|
-
if (!isDeferredFailOnError(err)) {
|
|
989
|
-
throw err;
|
|
990
|
-
}
|
|
991
|
-
// The failing path contributes nothing, and the remaining paths still
|
|
992
|
-
// do: fail-on-error scopes the loss to the leg that failed.
|
|
993
|
-
return {
|
|
994
|
-
components: [],
|
|
995
|
-
dependencies: [],
|
|
996
|
-
options: { ...sharedOptions },
|
|
997
|
-
parentSubComponents: [],
|
|
998
|
-
metadataProperties: [],
|
|
999
|
-
};
|
|
1000
|
-
}
|
|
1001
|
-
};
|
|
1002
|
-
const scanOnePathInner = async (path) => {
|
|
1003
|
-
// Each path accumulates into its own lists and its own view of options, so
|
|
1004
|
-
// that legs which record a parent component or a tool cannot overwrite
|
|
1005
|
-
// another path's. The lists are merged below in pathList order.
|
|
1006
|
-
const options = { ...sharedOptions };
|
|
1007
|
-
let bomData;
|
|
1008
|
-
let components = [];
|
|
1009
|
-
let dependencies = [];
|
|
1010
|
-
let parentSubComponents = [];
|
|
1011
|
-
let metadataProperties = [];
|
|
1012
|
-
// Each builder detects the project of its own ecosystem. A builder that sets
|
|
1013
|
-
// options.parentComponent to force its project into its own BOM must not
|
|
1014
|
-
// make that project the next builder's too, or the next ecosystem's project
|
|
1015
|
-
// is lost: every builder starts from the parent the caller gave.
|
|
1016
|
-
const callerParent = options.parentComponent;
|
|
1017
|
-
const withOwnParent = async (build) => {
|
|
1018
|
-
options.parentComponent = callerParent;
|
|
1019
|
-
try {
|
|
1020
|
-
return await build();
|
|
1021
|
-
} finally {
|
|
1022
|
-
options.parentComponent = callerParent;
|
|
1023
|
-
}
|
|
1024
|
-
};
|
|
1025
|
-
setActivityContext({
|
|
1026
|
-
projectType: Array.isArray(options.projectType)
|
|
1027
|
-
? options.projectType.join(",")
|
|
1028
|
-
: options.projectType,
|
|
1029
|
-
sourcePath: path,
|
|
1030
|
-
});
|
|
1031
|
-
recordActivity({
|
|
1032
|
-
kind: "read",
|
|
1033
|
-
reason:
|
|
1034
|
-
"Scanning project path for supported languages and package managers.",
|
|
1035
|
-
status: "completed",
|
|
1036
|
-
target: path,
|
|
1037
|
-
});
|
|
1038
|
-
if (DEBUG_MODE) {
|
|
1039
|
-
console.log("Scanning", path);
|
|
1040
|
-
}
|
|
1041
|
-
if (pathList.length > 2) {
|
|
1042
|
-
thoughtLog(`Let's thoroughly check the path ${path}.`);
|
|
1043
|
-
}
|
|
1044
|
-
// Node.js and AI inventory
|
|
1045
|
-
if (
|
|
1046
|
-
hasAnyProjectType(["oci", "js"], options) ||
|
|
1047
|
-
hasAnyProjectType(["mcp", "ai-skill", "ai"], options) ||
|
|
1048
|
-
getDirectAiInventoryType(path, options)
|
|
1049
|
-
) {
|
|
1050
|
-
const exactAiInventoryType = getDirectAiInventoryType(path, options);
|
|
1051
|
-
if (!hasAnyProjectType(["oci"], options, false)) {
|
|
1052
|
-
if (exactAiInventoryType === "mcp") {
|
|
1053
|
-
thoughtLog(
|
|
1054
|
-
"**MCP**: Looking for MCP services, MCP configs, and related AI control-plane artifacts.",
|
|
1055
|
-
);
|
|
1056
|
-
} else if (exactAiInventoryType === "ai") {
|
|
1057
|
-
thoughtLog(
|
|
1058
|
-
"**AI-BOM**: Looking for AI models, direct model metadata, pedigree, quantization, and operator-facing AI context.",
|
|
1059
|
-
);
|
|
1060
|
-
} else if (exactAiInventoryType === "ai-skill") {
|
|
1061
|
-
thoughtLog(
|
|
1062
|
-
"**AI-SKILL**: Looking for AI instruction, skill, and agent-definition files that can influence build or release flows.",
|
|
1063
|
-
);
|
|
1064
|
-
} else {
|
|
1065
|
-
thoughtLog(
|
|
1066
|
-
"**JS**: Now looking for JavaScript projects (npm, yarn, pnpm) and files.",
|
|
1067
|
-
);
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
setProjectTypeActivityContext(exactAiInventoryType || "js", path);
|
|
1071
|
-
bomData = await withOwnParent(() => createNodejsBom(path, options));
|
|
1072
|
-
if (bomData?.bomJson?.components?.length) {
|
|
1073
|
-
if (exactAiInventoryType) {
|
|
1074
|
-
thoughtLog(
|
|
1075
|
-
`I found ${bomData.bomJson.components.length} ${exactAiInventoryType} component(s). Let's keep looking.`,
|
|
1076
|
-
);
|
|
1077
|
-
} else {
|
|
1078
|
-
thoughtLog(
|
|
1079
|
-
`I found ${bomData.bomJson.components.length} npm packages. Let's keep looking.`,
|
|
1080
|
-
);
|
|
1081
|
-
}
|
|
1082
|
-
if (DEBUG_MODE) {
|
|
1083
|
-
console.log(
|
|
1084
|
-
`Found ${bomData.bomJson.components.length} ${exactAiInventoryType || "npm"} components at ${path}`,
|
|
1085
|
-
);
|
|
1086
|
-
}
|
|
1087
|
-
components = components.concat(bomData.bomJson.components);
|
|
1088
|
-
dependencies = mergeDependencies(
|
|
1089
|
-
dependencies,
|
|
1090
|
-
bomData.bomJson.dependencies,
|
|
1091
|
-
);
|
|
1092
|
-
if (bomData?.bomJson?.services?.length) {
|
|
1093
|
-
options.services = mergeServices(
|
|
1094
|
-
options.services || [],
|
|
1095
|
-
bomData.bomJson.services,
|
|
1096
|
-
);
|
|
1097
|
-
}
|
|
1098
|
-
if (
|
|
1099
|
-
bomData.parentComponent &&
|
|
1100
|
-
Object.keys(bomData.parentComponent).length
|
|
1101
|
-
) {
|
|
1102
|
-
parentSubComponents.push(bomData.parentComponent);
|
|
1103
|
-
}
|
|
1104
|
-
// Retain metadata.component.components
|
|
1105
|
-
if (bomData.parentComponent.components?.length) {
|
|
1106
|
-
parentSubComponents = parentSubComponents.concat(
|
|
1107
|
-
bomData.parentComponent.components,
|
|
1108
|
-
);
|
|
1109
|
-
delete bomData.parentComponent.components;
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
|
-
// Java
|
|
1114
|
-
if (hasAnyProjectType(["oci", "java"], options)) {
|
|
1115
|
-
if (!hasAnyProjectType(["oci"], options, false)) {
|
|
1116
|
-
thoughtLog(
|
|
1117
|
-
"**JAVA**: Looking for Java projects (e.g., Maven, Gradle, SBT). I hope all configurations—from Java version to individual build settings—are correctly aligned.",
|
|
1118
|
-
);
|
|
1119
|
-
}
|
|
1120
|
-
setProjectTypeActivityContext("java", path);
|
|
1121
|
-
bomData = await withOwnParent(() => createJavaBom(path, options));
|
|
1122
|
-
if (bomData?.bomJson?.components?.length) {
|
|
1123
|
-
thoughtLog(
|
|
1124
|
-
`I found ${bomData.bomJson.components.length} java packages.`,
|
|
1125
|
-
);
|
|
1126
|
-
if (DEBUG_MODE) {
|
|
1127
|
-
console.log(
|
|
1128
|
-
`Found ${bomData.bomJson.components.length} java packages at ${path}`,
|
|
1129
|
-
);
|
|
1130
|
-
}
|
|
1131
|
-
components = components.concat(bomData.bomJson.components);
|
|
1132
|
-
dependencies = mergeDependencies(
|
|
1133
|
-
dependencies,
|
|
1134
|
-
bomData.bomJson.dependencies,
|
|
1135
|
-
);
|
|
1136
|
-
if (
|
|
1137
|
-
bomData.parentComponent &&
|
|
1138
|
-
Object.keys(bomData.parentComponent).length
|
|
1139
|
-
) {
|
|
1140
|
-
parentSubComponents.push(bomData.parentComponent);
|
|
1141
|
-
}
|
|
1142
|
-
// Retain metadata.component.components, but add duplicates to the list of current components
|
|
1143
|
-
// and removing them from metadata.component.components -- the components are merged later
|
|
1144
|
-
if (bomData.parentComponent.components?.length) {
|
|
1145
|
-
let bomSubComponents = bomData.parentComponent.components;
|
|
1146
|
-
if (
|
|
1147
|
-
!["false", "0"].includes(
|
|
1148
|
-
readEnvironmentVariable("GRADLE_RESOLVE_FROM_NODE"),
|
|
1149
|
-
)
|
|
1150
|
-
) {
|
|
1151
|
-
thoughtLog(
|
|
1152
|
-
"Wait, the user wants me to resolve gradle projects from npm.",
|
|
1153
|
-
);
|
|
1154
|
-
const allRefs = components.map((c) => c["bom-ref"]);
|
|
1155
|
-
const duplicateComponents = bomSubComponents.filter((c) =>
|
|
1156
|
-
allRefs.includes(c["bom-ref"]),
|
|
1157
|
-
);
|
|
1158
|
-
components = components.concat(duplicateComponents);
|
|
1159
|
-
const duplicateComponentRefs = duplicateComponents.map(
|
|
1160
|
-
(c) => c["bom-ref"],
|
|
1161
|
-
);
|
|
1162
|
-
bomSubComponents = bomSubComponents.filter(
|
|
1163
|
-
(c) => !duplicateComponentRefs.includes(c["bom-ref"]),
|
|
1164
|
-
);
|
|
1165
|
-
}
|
|
1166
|
-
parentSubComponents = parentSubComponents.concat(bomSubComponents);
|
|
1167
|
-
delete bomData.parentComponent.components;
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
if (hasAnyProjectType(["oci", "py"], options)) {
|
|
1172
|
-
if (!hasAnyProjectType(["oci"], options, false)) {
|
|
1173
|
-
thoughtLog(
|
|
1174
|
-
"**PYTHON**: Looking for Python projects with package managers such as pip, poetry, uv, etc. Wish me good luck!",
|
|
1175
|
-
);
|
|
1176
|
-
}
|
|
1177
|
-
if (
|
|
1178
|
-
readEnvironmentVariable("CDXGEN_IN_CONTAINER") !== "true" &&
|
|
1179
|
-
pathList.length <= 2
|
|
1180
|
-
) {
|
|
1181
|
-
thoughtLog(
|
|
1182
|
-
"I'm running in a non-container environment. Let's hope the correct build tools are available ✌️.",
|
|
1183
|
-
);
|
|
1184
|
-
}
|
|
1185
|
-
setProjectTypeActivityContext("py", path);
|
|
1186
|
-
bomData = await withOwnParent(() => createPythonBom(path, options));
|
|
1187
|
-
if (bomData?.bomJson?.components?.length) {
|
|
1188
|
-
thoughtLog(
|
|
1189
|
-
`I found ${bomData.bomJson.components.length} python packages.`,
|
|
1190
|
-
);
|
|
1191
|
-
if (DEBUG_MODE) {
|
|
1192
|
-
console.log(
|
|
1193
|
-
`Found ${bomData.bomJson.components.length} python packages at ${path}`,
|
|
1194
|
-
);
|
|
1195
|
-
}
|
|
1196
|
-
components = components.concat(bomData.bomJson.components);
|
|
1197
|
-
dependencies = mergeDependencies(
|
|
1198
|
-
dependencies,
|
|
1199
|
-
bomData.bomJson.dependencies,
|
|
1200
|
-
);
|
|
1201
|
-
if (
|
|
1202
|
-
bomData.parentComponent &&
|
|
1203
|
-
Object.keys(bomData.parentComponent).length
|
|
1204
|
-
) {
|
|
1205
|
-
parentSubComponents.push(bomData.parentComponent);
|
|
1206
|
-
}
|
|
1207
|
-
// Retain metadata.component.components. uv workspace members are
|
|
1208
|
-
// nested under the python project, so hoisting them keeps them
|
|
1209
|
-
// siblings of the other sub-components instead of riding one level
|
|
1210
|
-
// deeper (or being dropped by the same-name collapse) when another
|
|
1211
|
-
// ecosystem's project becomes the parent.
|
|
1212
|
-
if (bomData.parentComponent?.components?.length) {
|
|
1213
|
-
parentSubComponents = parentSubComponents.concat(
|
|
1214
|
-
bomData.parentComponent.components,
|
|
1215
|
-
);
|
|
1216
|
-
delete bomData.parentComponent.components;
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
if (bomData?.formulationList?.length) {
|
|
1220
|
-
formulationList = formulationList.concat(bomData.formulationList);
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
if (hasAnyProjectType(["oci", "go"], options)) {
|
|
1224
|
-
if (!hasAnyProjectType(["oci"], options, false)) {
|
|
1225
|
-
thoughtLog(
|
|
1226
|
-
"**GO**: Looking for go projects. I need to be cautious about purl namespaces and potential failures with the 'go list' command.",
|
|
1227
|
-
);
|
|
1228
|
-
}
|
|
1229
|
-
setProjectTypeActivityContext("go", path);
|
|
1230
|
-
bomData = await withOwnParent(() => createGoBom(path, options));
|
|
1231
|
-
if (bomData?.bomJson?.components?.length) {
|
|
1232
|
-
thoughtLog(`I found ${bomData.bomJson.components.length} go packages.`);
|
|
1233
|
-
if (DEBUG_MODE) {
|
|
1234
|
-
console.log(
|
|
1235
|
-
`Found ${bomData.bomJson.components.length} go packages at ${path}`,
|
|
1236
|
-
);
|
|
1237
|
-
}
|
|
1238
|
-
components = components.concat(bomData.bomJson.components);
|
|
1239
|
-
dependencies = mergeDependencies(
|
|
1240
|
-
dependencies,
|
|
1241
|
-
bomData.bomJson.dependencies,
|
|
1242
|
-
);
|
|
1243
|
-
if (
|
|
1244
|
-
bomData.parentComponent &&
|
|
1245
|
-
Object.keys(bomData.parentComponent).length
|
|
1246
|
-
) {
|
|
1247
|
-
parentSubComponents.push(bomData.parentComponent);
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
if (hasAnyProjectType(["oci", "rust"], options)) {
|
|
1252
|
-
if (!hasAnyProjectType(["oci"], options, false)) {
|
|
1253
|
-
thoughtLog(
|
|
1254
|
-
"**RUST**: Let's search for Cargo/Rust projects. Should I warn the user that we don't support Cargo 'features' and native dependencies, which may lead to both false positives and false negatives? 🤔?",
|
|
1255
|
-
);
|
|
1256
|
-
}
|
|
1257
|
-
setProjectTypeActivityContext("rust", path);
|
|
1258
|
-
bomData = await withOwnParent(() => createRustBom(path, options));
|
|
1259
|
-
if (bomData?.bomJson?.components?.length) {
|
|
1260
|
-
thoughtLog(
|
|
1261
|
-
`I found ${bomData.bomJson.components.length} rust packages.`,
|
|
1262
|
-
);
|
|
1263
|
-
if (DEBUG_MODE) {
|
|
1264
|
-
console.log(
|
|
1265
|
-
`Found ${bomData.bomJson.components.length} rust packages at ${path}`,
|
|
1266
|
-
);
|
|
1267
|
-
}
|
|
1268
|
-
components = components.concat(bomData.bomJson.components);
|
|
1269
|
-
dependencies = mergeDependencies(
|
|
1270
|
-
dependencies,
|
|
1271
|
-
bomData.bomJson.dependencies,
|
|
1272
|
-
);
|
|
1273
|
-
if (
|
|
1274
|
-
bomData.parentComponent &&
|
|
1275
|
-
Object.keys(bomData.parentComponent).length
|
|
1276
|
-
) {
|
|
1277
|
-
parentSubComponents.push(bomData.parentComponent);
|
|
1278
|
-
}
|
|
1279
|
-
// Retain metadata.component.components
|
|
1280
|
-
if (bomData.parentComponent?.components?.length) {
|
|
1281
|
-
parentSubComponents = parentSubComponents.concat(
|
|
1282
|
-
bomData.parentComponent.components,
|
|
1283
|
-
);
|
|
1284
|
-
delete bomData.parentComponent.components;
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1287
|
-
}
|
|
1288
|
-
if (hasAnyProjectType(["oci", "php"], options)) {
|
|
1289
|
-
if (!hasAnyProjectType(["oci"], options, false)) {
|
|
1290
|
-
thoughtLog(
|
|
1291
|
-
"**PHP**: About to search for Composer-based projects. I hope lock files are available; otherwise, the 'composer install' command might fail for various reasons.",
|
|
1292
|
-
);
|
|
1293
|
-
}
|
|
1294
|
-
setProjectTypeActivityContext("php", path);
|
|
1295
|
-
bomData = await withOwnParent(() => createPHPBom(path, options));
|
|
1296
|
-
// A lock with no packages can still hold the project's platform data.
|
|
1297
|
-
if (bomData?.metadataProperties?.length) {
|
|
1298
|
-
metadataProperties = metadataProperties.concat(
|
|
1299
|
-
bomData.metadataProperties,
|
|
1300
|
-
);
|
|
1301
|
-
}
|
|
1302
|
-
if (bomData?.bomJson?.components?.length) {
|
|
1303
|
-
thoughtLog(
|
|
1304
|
-
`I found ${bomData.bomJson.components.length} php packages.`,
|
|
1305
|
-
);
|
|
1306
|
-
if (DEBUG_MODE) {
|
|
1307
|
-
console.log(
|
|
1308
|
-
`Found ${bomData.bomJson.components.length} php packages at ${path}`,
|
|
1309
|
-
);
|
|
1310
|
-
}
|
|
1311
|
-
components = components.concat(bomData.bomJson.components);
|
|
1312
|
-
dependencies = mergeDependencies(
|
|
1313
|
-
dependencies,
|
|
1314
|
-
bomData.bomJson.dependencies,
|
|
1315
|
-
);
|
|
1316
|
-
if (
|
|
1317
|
-
bomData.parentComponent &&
|
|
1318
|
-
Object.keys(bomData.parentComponent).length
|
|
1319
|
-
) {
|
|
1320
|
-
parentSubComponents.push(bomData.parentComponent);
|
|
1321
|
-
}
|
|
1322
|
-
// Retain metadata.component.components
|
|
1323
|
-
if (bomData.parentComponent?.components?.length) {
|
|
1324
|
-
parentSubComponents = parentSubComponents.concat(
|
|
1325
|
-
bomData.parentComponent.components,
|
|
1326
|
-
);
|
|
1327
|
-
delete bomData.parentComponent.components;
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
if (hasAnyProjectType(["oci", "ruby"], options)) {
|
|
1332
|
-
if (!hasAnyProjectType(["oci"], options, false)) {
|
|
1333
|
-
thoughtLog(
|
|
1334
|
-
"**RUBY**: Are there any Ruby projects in this path? There's only one way to know.",
|
|
1335
|
-
);
|
|
1336
|
-
}
|
|
1337
|
-
setProjectTypeActivityContext("ruby", path);
|
|
1338
|
-
bomData = await withOwnParent(() => createRubyBom(path, options));
|
|
1339
|
-
if (bomData?.bomJson?.components?.length) {
|
|
1340
|
-
thoughtLog(
|
|
1341
|
-
`We got ${bomData.bomJson.components.length} ruby packages.`,
|
|
1342
|
-
);
|
|
1343
|
-
if (DEBUG_MODE) {
|
|
1344
|
-
console.log(
|
|
1345
|
-
`Found ${bomData.bomJson.components.length} ruby packages at ${path}`,
|
|
1346
|
-
);
|
|
1347
|
-
}
|
|
1348
|
-
components = components.concat(bomData.bomJson.components);
|
|
1349
|
-
dependencies = mergeDependencies(
|
|
1350
|
-
dependencies,
|
|
1351
|
-
bomData.bomJson.dependencies,
|
|
1352
|
-
bomData.parentComponent,
|
|
1353
|
-
);
|
|
1354
|
-
if (
|
|
1355
|
-
bomData.parentComponent &&
|
|
1356
|
-
Object.keys(bomData.parentComponent).length
|
|
1357
|
-
) {
|
|
1358
|
-
parentSubComponents.push(bomData.parentComponent);
|
|
1359
|
-
}
|
|
1360
|
-
// Retain metadata.component.components
|
|
1361
|
-
if (bomData.parentComponent?.components?.length) {
|
|
1362
|
-
parentSubComponents = parentSubComponents.concat(
|
|
1363
|
-
bomData.parentComponent.components,
|
|
1364
|
-
);
|
|
1365
|
-
delete bomData.parentComponent.components;
|
|
1366
|
-
}
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1369
|
-
if (hasAnyProjectType(["oci", "csharp"], options)) {
|
|
1370
|
-
if (!hasAnyProjectType(["oci"], options, false)) {
|
|
1371
|
-
thoughtLog("**CSHARP**: What about csharp and fsharp projects?");
|
|
1372
|
-
}
|
|
1373
|
-
setProjectTypeActivityContext("csharp", path);
|
|
1374
|
-
bomData = await withOwnParent(() => createCsharpBom(path, options));
|
|
1375
|
-
// Every project the scan read has a dependency entry of its own, so a
|
|
1376
|
-
// project that uses no packages is kept along with its parent.
|
|
1377
|
-
if (
|
|
1378
|
-
bomData?.bomJson?.components?.length ||
|
|
1379
|
-
bomData?.bomJson?.dependencies?.length
|
|
1380
|
-
) {
|
|
1381
|
-
thoughtLog(
|
|
1382
|
-
`There are ${bomData.bomJson.components.length} csharp packages.`,
|
|
1383
|
-
);
|
|
1384
|
-
if (DEBUG_MODE) {
|
|
1385
|
-
console.log(
|
|
1386
|
-
`Found ${bomData.bomJson.components.length} csharp packages at ${path}`,
|
|
1387
|
-
);
|
|
1388
|
-
}
|
|
1389
|
-
components = components.concat(bomData.bomJson.components);
|
|
1390
|
-
dependencies = mergeDependencies(
|
|
1391
|
-
dependencies,
|
|
1392
|
-
bomData.bomJson.dependencies,
|
|
1393
|
-
);
|
|
1394
|
-
if (
|
|
1395
|
-
bomData.parentComponent &&
|
|
1396
|
-
Object.keys(bomData.parentComponent).length
|
|
1397
|
-
) {
|
|
1398
|
-
parentSubComponents.push(bomData.parentComponent);
|
|
1399
|
-
}
|
|
1400
|
-
// Retain metadata.component.components
|
|
1401
|
-
if (bomData.parentComponent?.components?.length) {
|
|
1402
|
-
parentSubComponents = parentSubComponents.concat(
|
|
1403
|
-
bomData.parentComponent.components,
|
|
1404
|
-
);
|
|
1405
|
-
delete bomData.parentComponent.components;
|
|
1406
|
-
}
|
|
1407
|
-
}
|
|
1408
|
-
}
|
|
1409
|
-
if (hasAnyProjectType(["oci", "dart"], options)) {
|
|
1410
|
-
if (!hasAnyProjectType(["oci"], options, false)) {
|
|
1411
|
-
thoughtLog(
|
|
1412
|
-
"**DART**: Looking for Dart projects. These are rare ones. Should I inform the user that they can pass the types argument via the command-line to speed things up?",
|
|
1413
|
-
);
|
|
1414
|
-
}
|
|
1415
|
-
setProjectTypeActivityContext("dart", path);
|
|
1416
|
-
bomData = await withOwnParent(() => createDartBom(path, options));
|
|
1417
|
-
if (bomData?.bomJson?.components?.length) {
|
|
1418
|
-
thoughtLog(
|
|
1419
|
-
`I found ${bomData.bomJson.components.length} pub packages.`,
|
|
1420
|
-
);
|
|
1421
|
-
if (DEBUG_MODE) {
|
|
1422
|
-
console.log(
|
|
1423
|
-
`Found ${bomData.bomJson.components.length} pub packages at ${path}`,
|
|
1424
|
-
);
|
|
1425
|
-
}
|
|
1426
|
-
components = components.concat(bomData.bomJson.components);
|
|
1427
|
-
dependencies = mergeDependencies(
|
|
1428
|
-
dependencies,
|
|
1429
|
-
bomData.bomJson.dependencies,
|
|
1430
|
-
);
|
|
1431
|
-
if (
|
|
1432
|
-
bomData.parentComponent &&
|
|
1433
|
-
Object.keys(bomData.parentComponent).length
|
|
1434
|
-
) {
|
|
1435
|
-
parentSubComponents.push(bomData.parentComponent);
|
|
1436
|
-
}
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
|
-
if (hasAnyProjectType(["oci", "haskell"], options)) {
|
|
1440
|
-
if (!hasAnyProjectType(["oci"], options, false)) {
|
|
1441
|
-
thoughtLog(
|
|
1442
|
-
"**HASKELL**: Looking for Haskell projects. They're rarely encountered.",
|
|
1443
|
-
);
|
|
1444
|
-
}
|
|
1445
|
-
setProjectTypeActivityContext("haskell", path);
|
|
1446
|
-
bomData = await withOwnParent(() => createHaskellBom(path, options));
|
|
1447
|
-
if (bomData?.bomJson?.components?.length) {
|
|
1448
|
-
thoughtLog(
|
|
1449
|
-
`I found ${bomData.bomJson.components.length} hackage packages.`,
|
|
1450
|
-
);
|
|
1451
|
-
if (DEBUG_MODE) {
|
|
1452
|
-
console.log(
|
|
1453
|
-
`Found ${bomData.bomJson.components.length} hackage packages at ${path}`,
|
|
1454
|
-
);
|
|
1455
|
-
}
|
|
1456
|
-
components = components.concat(bomData.bomJson.components);
|
|
1457
|
-
dependencies = mergeDependencies(
|
|
1458
|
-
dependencies,
|
|
1459
|
-
bomData.bomJson.dependencies,
|
|
1460
|
-
);
|
|
1461
|
-
if (
|
|
1462
|
-
bomData.parentComponent &&
|
|
1463
|
-
Object.keys(bomData.parentComponent).length
|
|
1464
|
-
) {
|
|
1465
|
-
parentSubComponents.push(bomData.parentComponent);
|
|
1466
|
-
}
|
|
1467
|
-
}
|
|
1468
|
-
}
|
|
1469
|
-
if (hasAnyProjectType(["oci", "elixir"], options)) {
|
|
1470
|
-
if (!hasAnyProjectType(["oci"], options, false)) {
|
|
1471
|
-
thoughtLog(
|
|
1472
|
-
"**ELIXIR**: Looking for Elixir projects—they're quite rare as well.",
|
|
1473
|
-
);
|
|
1474
|
-
}
|
|
1475
|
-
setProjectTypeActivityContext("elixir", path);
|
|
1476
|
-
bomData = await withOwnParent(() => createElixirBom(path, options));
|
|
1477
|
-
if (bomData?.bomJson?.components?.length) {
|
|
1478
|
-
thoughtLog(
|
|
1479
|
-
`I found ${bomData.bomJson.components.length} mix packages.`,
|
|
1480
|
-
);
|
|
1481
|
-
if (DEBUG_MODE) {
|
|
1482
|
-
console.log(
|
|
1483
|
-
`Found ${bomData.bomJson.components.length} mix packages at ${path}`,
|
|
1484
|
-
);
|
|
1485
|
-
}
|
|
1486
|
-
components = components.concat(bomData.bomJson.components);
|
|
1487
|
-
dependencies = mergeDependencies(
|
|
1488
|
-
dependencies,
|
|
1489
|
-
bomData.bomJson.dependencies,
|
|
1490
|
-
);
|
|
1491
|
-
if (
|
|
1492
|
-
bomData.parentComponent &&
|
|
1493
|
-
Object.keys(bomData.parentComponent).length
|
|
1494
|
-
) {
|
|
1495
|
-
parentSubComponents.push(bomData.parentComponent);
|
|
1496
|
-
}
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1499
|
-
if (hasAnyProjectType(["oci", "c"], options)) {
|
|
1500
|
-
if (!hasAnyProjectType(["oci"], options, false)) {
|
|
1501
|
-
thoughtLog(
|
|
1502
|
-
"**C/C++**: Looking for C/C++ projects. Should I warn the user that the generated SBOM might have low accuracy and contain errors?",
|
|
1503
|
-
);
|
|
1504
|
-
}
|
|
1505
|
-
setProjectTypeActivityContext("c", path);
|
|
1506
|
-
bomData = await withOwnParent(() => createCppBom(path, options));
|
|
1507
|
-
if (bomData?.bomJson?.components?.length) {
|
|
1508
|
-
thoughtLog(
|
|
1509
|
-
`I found ${bomData.bomJson.components.length} cpp packages.`,
|
|
1510
|
-
);
|
|
1511
|
-
if (DEBUG_MODE) {
|
|
1512
|
-
console.log(
|
|
1513
|
-
`Found ${bomData.bomJson.components.length} cpp packages at ${path}`,
|
|
1514
|
-
);
|
|
1515
|
-
}
|
|
1516
|
-
components = components.concat(bomData.bomJson.components);
|
|
1517
|
-
dependencies = mergeDependencies(
|
|
1518
|
-
dependencies,
|
|
1519
|
-
bomData.bomJson.dependencies,
|
|
1520
|
-
);
|
|
1521
|
-
if (
|
|
1522
|
-
bomData.parentComponent &&
|
|
1523
|
-
Object.keys(bomData.parentComponent).length
|
|
1524
|
-
) {
|
|
1525
|
-
parentSubComponents.push(bomData.parentComponent);
|
|
1526
|
-
}
|
|
1527
|
-
}
|
|
1528
|
-
// presets and compilers describe the build, whatever it depends on
|
|
1529
|
-
if (bomData?.formulationList?.length) {
|
|
1530
|
-
formulationList = formulationList.concat(bomData.formulationList);
|
|
1531
|
-
}
|
|
1532
|
-
}
|
|
1533
|
-
if (hasAnyProjectType(["oci", "clojure"], options)) {
|
|
1534
|
-
if (!hasAnyProjectType(["oci"], options, false)) {
|
|
1535
|
-
thoughtLog(
|
|
1536
|
-
"**CLOJURE**: Looking for Clojure projects. Should I warn the user that the purl namespace 'clojars' isn't widely supported by tools like Dependency-Track?",
|
|
1537
|
-
);
|
|
1538
|
-
}
|
|
1539
|
-
setProjectTypeActivityContext("clojure", path);
|
|
1540
|
-
bomData = await withOwnParent(() => createClojureBom(path, options));
|
|
1541
|
-
if (bomData?.bomJson?.components?.length) {
|
|
1542
|
-
thoughtLog(
|
|
1543
|
-
`I found ${bomData.bomJson.components.length} clojure packages.`,
|
|
1544
|
-
);
|
|
1545
|
-
if (DEBUG_MODE) {
|
|
1546
|
-
console.log(
|
|
1547
|
-
`Found ${bomData.bomJson.components.length} clojure packages at ${path}`,
|
|
1548
|
-
);
|
|
1549
|
-
}
|
|
1550
|
-
components = components.concat(bomData.bomJson.components);
|
|
1551
|
-
dependencies = mergeDependencies(
|
|
1552
|
-
dependencies,
|
|
1553
|
-
bomData.bomJson.dependencies,
|
|
1554
|
-
);
|
|
1555
|
-
if (
|
|
1556
|
-
bomData.parentComponent &&
|
|
1557
|
-
Object.keys(bomData.parentComponent).length
|
|
1558
|
-
) {
|
|
1559
|
-
parentSubComponents.push(bomData.parentComponent);
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
if (hasAnyProjectType(["oci", "github"], options)) {
|
|
1564
|
-
if (!hasAnyProjectType(["oci"], options, false)) {
|
|
1565
|
-
thoughtLog(
|
|
1566
|
-
"**GITHUB**: Looking for any github packages and workflows.",
|
|
1567
|
-
);
|
|
1568
|
-
}
|
|
1569
|
-
setProjectTypeActivityContext("github", path);
|
|
1570
|
-
bomData = await withOwnParent(() => createGitHubBom(path, options));
|
|
1571
|
-
if (bomData?.bomJson?.components?.length) {
|
|
1572
|
-
thoughtLog(
|
|
1573
|
-
`I found ${bomData.bomJson.components.length} github action packages as well. Should I convert these to formulation instead 🤔`,
|
|
1574
|
-
);
|
|
1575
|
-
if (DEBUG_MODE) {
|
|
1576
|
-
console.log(
|
|
1577
|
-
`Found ${bomData.bomJson.components.length} GitHub action packages at ${path}`,
|
|
1578
|
-
);
|
|
1579
|
-
}
|
|
1580
|
-
components = components.concat(bomData.bomJson.components);
|
|
1581
|
-
dependencies = mergeDependencies(
|
|
1582
|
-
dependencies,
|
|
1583
|
-
bomData.bomJson.dependencies,
|
|
1584
|
-
);
|
|
1585
|
-
if (
|
|
1586
|
-
bomData.parentComponent &&
|
|
1587
|
-
Object.keys(bomData.parentComponent).length
|
|
1588
|
-
) {
|
|
1589
|
-
parentSubComponents.push(bomData.parentComponent);
|
|
1590
|
-
}
|
|
1591
|
-
}
|
|
1592
|
-
}
|
|
1593
|
-
if (hasAnyProjectType(["oci", "cloudbuild"], options)) {
|
|
1594
|
-
if (!hasAnyProjectType(["oci"], options, false)) {
|
|
1595
|
-
thoughtLog(
|
|
1596
|
-
"**CLOUDBUILD**: Let's check for CloudBuild configuration files that include package dependencies.",
|
|
1597
|
-
);
|
|
1598
|
-
}
|
|
1599
|
-
setProjectTypeActivityContext("cloudbuild", path);
|
|
1600
|
-
bomData = await withOwnParent(() => createCloudBuildBom(path, options));
|
|
1601
|
-
if (bomData?.bomJson?.components?.length) {
|
|
1602
|
-
thoughtLog(
|
|
1603
|
-
`I found ${bomData.bomJson.components.length} cloudbuild packages.`,
|
|
1604
|
-
);
|
|
1605
|
-
if (DEBUG_MODE) {
|
|
1606
|
-
console.log(
|
|
1607
|
-
`Found ${bomData.bomJson.components.length} CloudBuild configuration at ${path}`,
|
|
1608
|
-
);
|
|
1609
|
-
}
|
|
1610
|
-
components = components.concat(bomData.bomJson.components);
|
|
1611
|
-
dependencies = mergeDependencies(
|
|
1612
|
-
dependencies,
|
|
1613
|
-
bomData.bomJson.dependencies,
|
|
1614
|
-
);
|
|
1615
|
-
if (
|
|
1616
|
-
bomData.parentComponent &&
|
|
1617
|
-
Object.keys(bomData.parentComponent).length
|
|
1618
|
-
) {
|
|
1619
|
-
parentSubComponents.push(bomData.parentComponent);
|
|
1620
|
-
}
|
|
1621
|
-
}
|
|
1622
|
-
}
|
|
1623
|
-
if (hasAnyProjectType(["oci", "swift"], options)) {
|
|
1624
|
-
if (!hasAnyProjectType(["oci"], options, false)) {
|
|
1625
|
-
thoughtLog(
|
|
1626
|
-
"**SWIFT**: Now checking for Swift projects. We don't support CocoaPods, Objective-C, or pure Xcode projects, so the SBOM will be incomplete.",
|
|
1627
|
-
);
|
|
1628
|
-
}
|
|
1629
|
-
setProjectTypeActivityContext("swift", path);
|
|
1630
|
-
bomData = await withOwnParent(() => createSwiftBom(path, options));
|
|
1631
|
-
if (bomData?.bomJson?.components?.length) {
|
|
1632
|
-
thoughtLog(
|
|
1633
|
-
`I found ${bomData.bomJson.components.length} swift packages here.`,
|
|
1634
|
-
);
|
|
1635
|
-
if (DEBUG_MODE) {
|
|
1636
|
-
console.log(
|
|
1637
|
-
`Found ${bomData.bomJson.components.length} Swift packages at ${path}`,
|
|
1638
|
-
);
|
|
1639
|
-
}
|
|
1640
|
-
components = components.concat(bomData.bomJson.components);
|
|
1641
|
-
dependencies = mergeDependencies(
|
|
1642
|
-
dependencies,
|
|
1643
|
-
bomData.bomJson.dependencies,
|
|
1644
|
-
);
|
|
1645
|
-
if (
|
|
1646
|
-
bomData.parentComponent &&
|
|
1647
|
-
Object.keys(bomData.parentComponent).length
|
|
1648
|
-
) {
|
|
1649
|
-
parentSubComponents.push(bomData.parentComponent);
|
|
1650
|
-
}
|
|
1651
|
-
}
|
|
1005
|
+
});
|
|
1652
1006
|
}
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1007
|
+
}
|
|
1008
|
+
if (hasAnyProjectType(["os"], options, false) && options.bomData) {
|
|
1009
|
+
bomData = options.bomData;
|
|
1010
|
+
if (bomData?.bomJson?.components) {
|
|
1011
|
+
if (DEBUG_MODE) {
|
|
1012
|
+
console.log(`Found ${bomData.bomJson.components.length} OS components`);
|
|
1658
1013
|
}
|
|
1659
|
-
|
|
1660
|
-
recordDegradation("jvm.jar.heuristic", {
|
|
1661
|
-
ecosystem: "java",
|
|
1662
|
-
tool: "jar",
|
|
1663
|
-
impact: "versions",
|
|
1664
|
-
path,
|
|
1665
|
-
detail:
|
|
1666
|
-
"Components are being inferred by scanning jar/war/ear files from disk.",
|
|
1667
|
-
});
|
|
1668
|
-
bomData = await withOwnParent(() => createJarBom(path, options));
|
|
1669
|
-
if (bomData?.bomJson?.components?.length) {
|
|
1014
|
+
if (bomData.bomJson.components.length) {
|
|
1670
1015
|
thoughtLog(
|
|
1671
|
-
`I found ${bomData.bomJson.components.length}
|
|
1672
|
-
);
|
|
1673
|
-
if (DEBUG_MODE) {
|
|
1674
|
-
console.log(
|
|
1675
|
-
`Found ${bomData.bomJson.components.length} jar packages at ${path}`,
|
|
1676
|
-
);
|
|
1677
|
-
}
|
|
1678
|
-
components = components.concat(bomData.bomJson.components);
|
|
1679
|
-
dependencies = mergeDependencies(
|
|
1680
|
-
dependencies,
|
|
1681
|
-
bomData.bomJson.dependencies,
|
|
1016
|
+
`I found ${bomData.bomJson.components.length} OS packages 😎.`,
|
|
1682
1017
|
);
|
|
1683
|
-
if (
|
|
1684
|
-
bomData.parentComponent &&
|
|
1685
|
-
Object.keys(bomData.parentComponent).length
|
|
1686
|
-
) {
|
|
1687
|
-
parentSubComponents.push(bomData.parentComponent);
|
|
1688
|
-
}
|
|
1689
1018
|
}
|
|
1019
|
+
components = components.concat(bomData.bomJson.components);
|
|
1690
1020
|
}
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1021
|
+
}
|
|
1022
|
+
const sharedOptions = options;
|
|
1023
|
+
// The exploded image layers are written once by the extractor and only read
|
|
1024
|
+
// from here on, so repeated walks of them can share one view of the tree.
|
|
1025
|
+
setDirWalkCacheRoot(options.allLayersExplodedDir);
|
|
1026
|
+
// The ecosystems every path is scanned for, in the order their builders run.
|
|
1027
|
+
// Each step reads the per-path copy of options that scanOnePathInner passes
|
|
1028
|
+
// in. One list merged by one loop also keeps type checking of this function
|
|
1029
|
+
// linear: a separate block per ecosystem, each reassigning an untyped
|
|
1030
|
+
// evolving array, makes TypeScript's control-flow analysis exponential in
|
|
1031
|
+
// the number of blocks (issue #4705).
|
|
1032
|
+
/**
|
|
1033
|
+
* @param {string} path Scanned path
|
|
1034
|
+
* @returns {boolean} Whether the path is a Chrome extension directory or its manifest.json
|
|
1035
|
+
*/
|
|
1036
|
+
const isChromeExtensionPath = (path) => {
|
|
1037
|
+
if (!safeExistsSync(path)) {
|
|
1038
|
+
return false;
|
|
1709
1039
|
}
|
|
1710
|
-
if (
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1040
|
+
if (basename(path) === "manifest.json") {
|
|
1041
|
+
return true;
|
|
1042
|
+
}
|
|
1043
|
+
try {
|
|
1044
|
+
return (
|
|
1045
|
+
statSync(path).isDirectory() &&
|
|
1046
|
+
safeExistsSync(join(path, "manifest.json"))
|
|
1047
|
+
);
|
|
1048
|
+
} catch (_err) {
|
|
1049
|
+
return false;
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
/**
|
|
1053
|
+
* @param {string} thought Thought log written unless the scan is of a container image
|
|
1054
|
+
* @returns {(path: string, scanOptions: Object) => void} The announce hook
|
|
1055
|
+
*/
|
|
1056
|
+
const ociQuiet = (thought) => (_path, scanOptions) => {
|
|
1057
|
+
if (!hasAnyProjectType(["oci"], scanOptions, false)) {
|
|
1058
|
+
thoughtLog(thought);
|
|
1059
|
+
}
|
|
1060
|
+
};
|
|
1061
|
+
/** @type {PathScanStep[]} */
|
|
1062
|
+
const pathScanSteps = [
|
|
1063
|
+
{
|
|
1064
|
+
// Node.js and AI inventory
|
|
1065
|
+
applies: (path, scanOptions) =>
|
|
1066
|
+
hasAnyProjectType(["oci", "js"], scanOptions) ||
|
|
1067
|
+
hasAnyProjectType(["mcp", "ai-skill", "ai"], scanOptions) ||
|
|
1068
|
+
!!getDirectAiInventoryType(path, scanOptions),
|
|
1069
|
+
prepare: (path, scanOptions) =>
|
|
1070
|
+
getDirectAiInventoryType(path, scanOptions),
|
|
1071
|
+
announce: (_path, scanOptions, exactAiInventoryType) => {
|
|
1072
|
+
if (hasAnyProjectType(["oci"], scanOptions, false)) {
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
if (exactAiInventoryType === "mcp") {
|
|
1076
|
+
thoughtLog(
|
|
1077
|
+
"**MCP**: Looking for MCP services, MCP configs, and related AI control-plane artifacts.",
|
|
1078
|
+
);
|
|
1079
|
+
} else if (exactAiInventoryType === "ai") {
|
|
1080
|
+
thoughtLog(
|
|
1081
|
+
"**AI-BOM**: Looking for AI models, direct model metadata, pedigree, quantization, and operator-facing AI context.",
|
|
1082
|
+
);
|
|
1083
|
+
} else if (exactAiInventoryType === "ai-skill") {
|
|
1084
|
+
thoughtLog(
|
|
1085
|
+
"**AI-SKILL**: Looking for AI instruction, skill, and agent-definition files that can influence build or release flows.",
|
|
1086
|
+
);
|
|
1087
|
+
} else {
|
|
1088
|
+
thoughtLog(
|
|
1089
|
+
"**JS**: Now looking for JavaScript projects (npm, yarn, pnpm) and files.",
|
|
1717
1090
|
);
|
|
1718
1091
|
}
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1092
|
+
},
|
|
1093
|
+
activityType: (exactAiInventoryType) => exactAiInventoryType || "js",
|
|
1094
|
+
build: createNodejsBom,
|
|
1095
|
+
foundThought: (count, _path, exactAiInventoryType) =>
|
|
1096
|
+
exactAiInventoryType
|
|
1097
|
+
? `I found ${count} ${exactAiInventoryType} component(s). Let's keep looking.`
|
|
1098
|
+
: `I found ${count} npm packages. Let's keep looking.`,
|
|
1099
|
+
debugMessage: (count, path, exactAiInventoryType) =>
|
|
1100
|
+
`Found ${count} ${exactAiInventoryType || "npm"} components at ${path}`,
|
|
1101
|
+
mergeServices: true,
|
|
1102
|
+
hoistSubComponents: "plain",
|
|
1103
|
+
},
|
|
1104
|
+
{
|
|
1105
|
+
applies: (_path, scanOptions) =>
|
|
1106
|
+
hasAnyProjectType(["oci", "java"], scanOptions),
|
|
1107
|
+
announce: ociQuiet(
|
|
1108
|
+
"**JAVA**: Looking for Java projects (e.g., Maven, Gradle, SBT). I hope all configurations—from Java version to individual build settings—are correctly aligned.",
|
|
1109
|
+
),
|
|
1110
|
+
activityType: "java",
|
|
1111
|
+
build: createJavaBom,
|
|
1112
|
+
foundThought: (count) => `I found ${count} java packages.`,
|
|
1113
|
+
debugMessage: (count, path) => `Found ${count} java packages at ${path}`,
|
|
1114
|
+
hoistSubComponents: "gradle",
|
|
1115
|
+
},
|
|
1116
|
+
{
|
|
1117
|
+
applies: (_path, scanOptions) =>
|
|
1118
|
+
hasAnyProjectType(["oci", "py"], scanOptions),
|
|
1119
|
+
announce: (path, scanOptions) => {
|
|
1120
|
+
ociQuiet(
|
|
1121
|
+
"**PYTHON**: Looking for Python projects with package managers such as pip, poetry, uv, etc. Wish me good luck!",
|
|
1122
|
+
)(path, scanOptions);
|
|
1724
1123
|
if (
|
|
1725
|
-
|
|
1726
|
-
|
|
1124
|
+
readEnvironmentVariable("CDXGEN_IN_CONTAINER") !== "true" &&
|
|
1125
|
+
pathList.length <= 2
|
|
1727
1126
|
) {
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
}
|
|
1731
|
-
}
|
|
1732
|
-
if (hasAnyProjectType(["caxa"], options, false)) {
|
|
1733
|
-
setProjectTypeActivityContext("caxa", path);
|
|
1734
|
-
bomData = await withOwnParent(() => createCaxaBom(path, options));
|
|
1735
|
-
if (bomData?.bomJson?.components?.length) {
|
|
1736
|
-
if (DEBUG_MODE) {
|
|
1737
|
-
console.log(
|
|
1738
|
-
`Found ${bomData.bomJson.components.length} caxa packages at ${path}`,
|
|
1127
|
+
thoughtLog(
|
|
1128
|
+
"I'm running in a non-container environment. Let's hope the correct build tools are available ✌️.",
|
|
1739
1129
|
);
|
|
1740
1130
|
}
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1131
|
+
},
|
|
1132
|
+
activityType: "py",
|
|
1133
|
+
build: createPythonBom,
|
|
1134
|
+
foundThought: (count) => `I found ${count} python packages.`,
|
|
1135
|
+
debugMessage: (count, path) =>
|
|
1136
|
+
`Found ${count} python packages at ${path}`,
|
|
1137
|
+
// uv workspace members are nested under the python project, so hoisting
|
|
1138
|
+
// them keeps them siblings of the other sub-components instead of riding
|
|
1139
|
+
// one level deeper (or being dropped by the same-name collapse) when
|
|
1140
|
+
// another ecosystem's project becomes the parent.
|
|
1141
|
+
hoistSubComponents: "plain",
|
|
1142
|
+
collectFormulation: true,
|
|
1143
|
+
},
|
|
1144
|
+
{
|
|
1145
|
+
applies: (_path, scanOptions) =>
|
|
1146
|
+
hasAnyProjectType(["oci", "go"], scanOptions),
|
|
1147
|
+
announce: ociQuiet(
|
|
1148
|
+
"**GO**: Looking for go projects. I need to be cautious about purl namespaces and potential failures with the 'go list' command.",
|
|
1149
|
+
),
|
|
1150
|
+
activityType: "go",
|
|
1151
|
+
build: createGoBom,
|
|
1152
|
+
foundThought: (count) => `I found ${count} go packages.`,
|
|
1153
|
+
debugMessage: (count, path) => `Found ${count} go packages at ${path}`,
|
|
1154
|
+
},
|
|
1155
|
+
{
|
|
1156
|
+
applies: (_path, scanOptions) =>
|
|
1157
|
+
hasAnyProjectType(["oci", "rust"], scanOptions),
|
|
1158
|
+
announce: ociQuiet(
|
|
1159
|
+
"**RUST**: Let's search for Cargo/Rust projects. Should I warn the user that we don't support Cargo 'features' and native dependencies, which may lead to both false positives and false negatives? 🤔?",
|
|
1160
|
+
),
|
|
1161
|
+
activityType: "rust",
|
|
1162
|
+
build: createRustBom,
|
|
1163
|
+
foundThought: (count) => `I found ${count} rust packages.`,
|
|
1164
|
+
debugMessage: (count, path) => `Found ${count} rust packages at ${path}`,
|
|
1165
|
+
hoistSubComponents: "plain",
|
|
1166
|
+
},
|
|
1167
|
+
{
|
|
1168
|
+
applies: (_path, scanOptions) =>
|
|
1169
|
+
hasAnyProjectType(["oci", "php"], scanOptions),
|
|
1170
|
+
announce: ociQuiet(
|
|
1171
|
+
"**PHP**: About to search for Composer-based projects. I hope lock files are available; otherwise, the 'composer install' command might fail for various reasons.",
|
|
1172
|
+
),
|
|
1173
|
+
activityType: "php",
|
|
1174
|
+
build: createPHPBom,
|
|
1175
|
+
// A lock with no packages can still hold the project's platform data.
|
|
1176
|
+
collectMetadataProperties: true,
|
|
1177
|
+
foundThought: (count) => `I found ${count} php packages.`,
|
|
1178
|
+
debugMessage: (count, path) => `Found ${count} php packages at ${path}`,
|
|
1179
|
+
hoistSubComponents: "plain",
|
|
1180
|
+
},
|
|
1181
|
+
{
|
|
1182
|
+
applies: (_path, scanOptions) =>
|
|
1183
|
+
hasAnyProjectType(["oci", "ruby"], scanOptions),
|
|
1184
|
+
announce: ociQuiet(
|
|
1185
|
+
"**RUBY**: Are there any Ruby projects in this path? There's only one way to know.",
|
|
1186
|
+
),
|
|
1187
|
+
activityType: "ruby",
|
|
1188
|
+
build: createRubyBom,
|
|
1189
|
+
foundThought: (count) => `We got ${count} ruby packages.`,
|
|
1190
|
+
debugMessage: (count, path) => `Found ${count} ruby packages at ${path}`,
|
|
1191
|
+
dependencyMerge: "mergeWithParent",
|
|
1192
|
+
hoistSubComponents: "plain",
|
|
1193
|
+
},
|
|
1194
|
+
{
|
|
1195
|
+
applies: (_path, scanOptions) =>
|
|
1196
|
+
hasAnyProjectType(["oci", "csharp"], scanOptions),
|
|
1197
|
+
announce: ociQuiet("**CSHARP**: What about csharp and fsharp projects?"),
|
|
1198
|
+
activityType: "csharp",
|
|
1199
|
+
build: createCsharpBom,
|
|
1200
|
+
// Every project the scan read has a dependency entry of its own, so a
|
|
1201
|
+
// project that uses no packages is kept along with its parent.
|
|
1202
|
+
isUseful: (bomData) =>
|
|
1203
|
+
!!(
|
|
1204
|
+
bomData?.bomJson?.components?.length ||
|
|
1205
|
+
bomData?.bomJson?.dependencies?.length
|
|
1206
|
+
),
|
|
1207
|
+
foundThought: (count) => `There are ${count} csharp packages.`,
|
|
1208
|
+
debugMessage: (count, path) =>
|
|
1209
|
+
`Found ${count} csharp packages at ${path}`,
|
|
1210
|
+
hoistSubComponents: "plain",
|
|
1211
|
+
},
|
|
1212
|
+
{
|
|
1213
|
+
applies: (_path, scanOptions) =>
|
|
1214
|
+
hasAnyProjectType(["oci", "dart"], scanOptions),
|
|
1215
|
+
announce: ociQuiet(
|
|
1216
|
+
"**DART**: Looking for Dart projects. These are rare ones. Should I inform the user that they can pass the types argument via the command-line to speed things up?",
|
|
1217
|
+
),
|
|
1218
|
+
activityType: "dart",
|
|
1219
|
+
build: createDartBom,
|
|
1220
|
+
foundThought: (count) => `I found ${count} pub packages.`,
|
|
1221
|
+
debugMessage: (count, path) => `Found ${count} pub packages at ${path}`,
|
|
1222
|
+
},
|
|
1223
|
+
{
|
|
1224
|
+
applies: (_path, scanOptions) =>
|
|
1225
|
+
hasAnyProjectType(["oci", "haskell"], scanOptions),
|
|
1226
|
+
announce: ociQuiet(
|
|
1227
|
+
"**HASKELL**: Looking for Haskell projects. They're rarely encountered.",
|
|
1228
|
+
),
|
|
1229
|
+
activityType: "haskell",
|
|
1230
|
+
build: createHaskellBom,
|
|
1231
|
+
foundThought: (count) => `I found ${count} hackage packages.`,
|
|
1232
|
+
debugMessage: (count, path) =>
|
|
1233
|
+
`Found ${count} hackage packages at ${path}`,
|
|
1234
|
+
},
|
|
1235
|
+
{
|
|
1236
|
+
applies: (_path, scanOptions) =>
|
|
1237
|
+
hasAnyProjectType(["oci", "elixir"], scanOptions),
|
|
1238
|
+
announce: ociQuiet(
|
|
1239
|
+
"**ELIXIR**: Looking for Elixir projects—they're quite rare as well.",
|
|
1240
|
+
),
|
|
1241
|
+
activityType: "elixir",
|
|
1242
|
+
build: createElixirBom,
|
|
1243
|
+
foundThought: (count) => `I found ${count} mix packages.`,
|
|
1244
|
+
debugMessage: (count, path) => `Found ${count} mix packages at ${path}`,
|
|
1245
|
+
},
|
|
1246
|
+
{
|
|
1247
|
+
applies: (_path, scanOptions) =>
|
|
1248
|
+
hasAnyProjectType(["oci", "c"], scanOptions),
|
|
1249
|
+
announce: ociQuiet(
|
|
1250
|
+
"**C/C++**: Looking for C/C++ projects. Should I warn the user that the generated SBOM might have low accuracy and contain errors?",
|
|
1251
|
+
),
|
|
1252
|
+
activityType: "c",
|
|
1253
|
+
build: createCppBom,
|
|
1254
|
+
foundThought: (count) => `I found ${count} cpp packages.`,
|
|
1255
|
+
debugMessage: (count, path) => `Found ${count} cpp packages at ${path}`,
|
|
1256
|
+
// presets and compilers describe the build, whatever it depends on
|
|
1257
|
+
collectFormulation: true,
|
|
1258
|
+
},
|
|
1259
|
+
{
|
|
1260
|
+
applies: (_path, scanOptions) =>
|
|
1261
|
+
hasAnyProjectType(["oci", "clojure"], scanOptions),
|
|
1262
|
+
announce: ociQuiet(
|
|
1263
|
+
"**CLOJURE**: Looking for Clojure projects. Should I warn the user that the purl namespace 'clojars' isn't widely supported by tools like Dependency-Track?",
|
|
1264
|
+
),
|
|
1265
|
+
activityType: "clojure",
|
|
1266
|
+
build: createClojureBom,
|
|
1267
|
+
foundThought: (count) => `I found ${count} clojure packages.`,
|
|
1268
|
+
debugMessage: (count, path) =>
|
|
1269
|
+
`Found ${count} clojure packages at ${path}`,
|
|
1270
|
+
},
|
|
1271
|
+
{
|
|
1272
|
+
applies: (_path, scanOptions) =>
|
|
1273
|
+
hasAnyProjectType(["oci", "github"], scanOptions),
|
|
1274
|
+
announce: ociQuiet(
|
|
1275
|
+
"**GITHUB**: Looking for any github packages and workflows.",
|
|
1276
|
+
),
|
|
1277
|
+
activityType: "github",
|
|
1278
|
+
build: createGitHubBom,
|
|
1279
|
+
foundThought: (count) =>
|
|
1280
|
+
`I found ${count} github action packages as well. Should I convert these to formulation instead 🤔`,
|
|
1281
|
+
debugMessage: (count, path) =>
|
|
1282
|
+
`Found ${count} GitHub action packages at ${path}`,
|
|
1283
|
+
},
|
|
1284
|
+
{
|
|
1285
|
+
applies: (_path, scanOptions) =>
|
|
1286
|
+
hasAnyProjectType(["oci", "cloudbuild"], scanOptions),
|
|
1287
|
+
announce: ociQuiet(
|
|
1288
|
+
"**CLOUDBUILD**: Let's check for CloudBuild configuration files that include package dependencies.",
|
|
1289
|
+
),
|
|
1290
|
+
activityType: "cloudbuild",
|
|
1291
|
+
build: createCloudBuildBom,
|
|
1292
|
+
foundThought: (count) => `I found ${count} cloudbuild packages.`,
|
|
1293
|
+
debugMessage: (count, path) =>
|
|
1294
|
+
`Found ${count} CloudBuild configuration at ${path}`,
|
|
1295
|
+
},
|
|
1296
|
+
{
|
|
1297
|
+
applies: (_path, scanOptions) =>
|
|
1298
|
+
hasAnyProjectType(["oci", "swift"], scanOptions),
|
|
1299
|
+
announce: ociQuiet(
|
|
1300
|
+
"**SWIFT**: Now checking for Swift projects. We don't support CocoaPods, Objective-C, or pure Xcode projects, so the SBOM will be incomplete.",
|
|
1301
|
+
),
|
|
1302
|
+
activityType: "swift",
|
|
1303
|
+
build: createSwiftBom,
|
|
1304
|
+
foundThought: (count) => `I found ${count} swift packages here.`,
|
|
1305
|
+
debugMessage: (count, path) => `Found ${count} Swift packages at ${path}`,
|
|
1306
|
+
},
|
|
1307
|
+
{
|
|
1308
|
+
applies: (_path, scanOptions) =>
|
|
1309
|
+
hasAnyProjectType(["oci", "jar", "war", "ear"], scanOptions),
|
|
1310
|
+
announce: ociQuiet(
|
|
1311
|
+
"**JAR**: Let's check for any bundled jar/war/ear files to improve the SBOM accuracy.",
|
|
1312
|
+
),
|
|
1313
|
+
activityType: "jar",
|
|
1314
|
+
beforeBuild: (path) => {
|
|
1315
|
+
recordDegradation("jvm.jar.heuristic", {
|
|
1316
|
+
ecosystem: "java",
|
|
1317
|
+
tool: "jar",
|
|
1318
|
+
impact: "versions",
|
|
1319
|
+
path,
|
|
1320
|
+
detail:
|
|
1321
|
+
"Components are being inferred by scanning jar/war/ear files from disk.",
|
|
1322
|
+
});
|
|
1323
|
+
},
|
|
1324
|
+
build: createJarBom,
|
|
1325
|
+
foundThought: (count) => `I found ${count} jar packages as well.`,
|
|
1326
|
+
debugMessage: (count, path) => `Found ${count} jar packages at ${path}`,
|
|
1327
|
+
},
|
|
1328
|
+
{
|
|
1329
|
+
applies: (_path, scanOptions) =>
|
|
1330
|
+
hasAnyProjectType(["oci", "cocoa"], scanOptions),
|
|
1331
|
+
activityType: "cocoa",
|
|
1332
|
+
build: createCocoaBom,
|
|
1333
|
+
debugMessage: (count, path) => `Found ${count} Cocoa packages at ${path}`,
|
|
1334
|
+
dependencyMerge: "concat",
|
|
1335
|
+
},
|
|
1336
|
+
{
|
|
1337
|
+
applies: (_path, scanOptions) =>
|
|
1338
|
+
hasAnyProjectType(["oci", "nix"], scanOptions),
|
|
1339
|
+
activityType: "nix",
|
|
1340
|
+
build: createNixBom,
|
|
1341
|
+
debugMessage: (count, path) =>
|
|
1342
|
+
`Found ${count} Nix flake packages at ${path}`,
|
|
1343
|
+
},
|
|
1344
|
+
{
|
|
1345
|
+
applies: (_path, scanOptions) =>
|
|
1346
|
+
hasAnyProjectType(["caxa"], scanOptions, false),
|
|
1347
|
+
activityType: "caxa",
|
|
1348
|
+
build: createCaxaBom,
|
|
1349
|
+
debugMessage: (count, path) => `Found ${count} caxa packages at ${path}`,
|
|
1350
|
+
},
|
|
1351
|
+
{
|
|
1352
|
+
applies: (_path, scanOptions) =>
|
|
1353
|
+
hasAnyProjectType(["asar"], scanOptions, false),
|
|
1354
|
+
activityType: "asar",
|
|
1355
|
+
build: createAsarBom,
|
|
1356
|
+
debugMessage: (count, path) =>
|
|
1357
|
+
`Found ${count} ASAR component(s) at ${path}`,
|
|
1358
|
+
},
|
|
1359
|
+
{
|
|
1360
|
+
applies: (_path, scanOptions) =>
|
|
1361
|
+
hasAnyProjectType(["vscode-extension"], scanOptions, false),
|
|
1362
|
+
activityType: "vscode-extension",
|
|
1363
|
+
build: createVscodeExtensionBom,
|
|
1364
|
+
debugMessage: (count, path) =>
|
|
1365
|
+
`Found ${count} VS Code extension(s) at ${path}`,
|
|
1366
|
+
},
|
|
1367
|
+
{
|
|
1368
|
+
// A path that is a Chrome extension is always scanned. Any other path
|
|
1369
|
+
// scans the extensions installed on this host, which happens once per
|
|
1370
|
+
// run however many paths there are.
|
|
1371
|
+
applies: (path, scanOptions) =>
|
|
1372
|
+
hasAnyProjectType(["chrome-extension"], scanOptions, false) &&
|
|
1373
|
+
(isChromeExtensionPath(path) ||
|
|
1374
|
+
!sharedOptions.__didScanChromeExtensions),
|
|
1375
|
+
beforeBuild: (path) => {
|
|
1376
|
+
if (!isChromeExtensionPath(path)) {
|
|
1377
|
+
sharedOptions.__didScanChromeExtensions = true;
|
|
1751
1378
|
}
|
|
1379
|
+
},
|
|
1380
|
+
activityType: "chrome-extension",
|
|
1381
|
+
build: createChromeExtensionBom,
|
|
1382
|
+
debugMessage: (count) =>
|
|
1383
|
+
`Found ${count} Chrome extension(s) on this host`,
|
|
1384
|
+
},
|
|
1385
|
+
{
|
|
1386
|
+
applies: (_path, scanOptions) =>
|
|
1387
|
+
hasAnyProjectType(["terraform"], scanOptions),
|
|
1388
|
+
announce: () => {
|
|
1389
|
+
thoughtLog(
|
|
1390
|
+
"**TERRAFORM**: Looking for Terraform and OpenTofu modules and providers. The registry metadata fetch is opt-in, so I will rely on the lock file, the module manifest and the configuration files.",
|
|
1391
|
+
);
|
|
1392
|
+
},
|
|
1393
|
+
activityType: "terraform",
|
|
1394
|
+
build: createTerraformBom,
|
|
1395
|
+
foundThought: (count) =>
|
|
1396
|
+
`I found ${count} terraform components. Should I also resolve their licenses from the registry when FETCH_LICENSE is enabled?`,
|
|
1397
|
+
debugMessage: (count, path) =>
|
|
1398
|
+
`Found ${count} terraform components at ${path}`,
|
|
1399
|
+
},
|
|
1400
|
+
{
|
|
1401
|
+
// Collect any crypto keys
|
|
1402
|
+
applies: (_path, scanOptions) =>
|
|
1403
|
+
scanOptions.specVersion >= 1.6 &&
|
|
1404
|
+
!!scanOptions.includeCrypto &&
|
|
1405
|
+
isCycloneDxComponentTypeEnabled("cryptographic-asset", scanOptions),
|
|
1406
|
+
announce: ociQuiet(
|
|
1407
|
+
"**CBOM**: Wait, the user wants me to look for cryptographic assets. Let's check thoroughly.",
|
|
1408
|
+
),
|
|
1409
|
+
activityType: "cbom",
|
|
1410
|
+
build: createCryptoCertsBom,
|
|
1411
|
+
foundThought: (count) => `I found ${count} crypto assets.`,
|
|
1412
|
+
debugMessage: (count, path) => `Found ${count} crypto assets at ${path}`,
|
|
1413
|
+
dependencyMerge: "topLevel",
|
|
1414
|
+
keepParent: false,
|
|
1415
|
+
},
|
|
1416
|
+
];
|
|
1417
|
+
const scanOnePath = async (path) => {
|
|
1418
|
+
try {
|
|
1419
|
+
return await scanOnePathInner(path);
|
|
1420
|
+
} catch (err) {
|
|
1421
|
+
if (!isDeferredFailOnError(err)) {
|
|
1422
|
+
throw err;
|
|
1752
1423
|
}
|
|
1424
|
+
// The failing path contributes nothing, and the remaining paths still
|
|
1425
|
+
// do: fail-on-error scopes the loss to the leg that failed.
|
|
1426
|
+
return {
|
|
1427
|
+
components: [],
|
|
1428
|
+
dependencies: [],
|
|
1429
|
+
options: { ...sharedOptions },
|
|
1430
|
+
parentSubComponents: [],
|
|
1431
|
+
metadataProperties: [],
|
|
1432
|
+
formulationList: [],
|
|
1433
|
+
};
|
|
1753
1434
|
}
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1435
|
+
};
|
|
1436
|
+
const scanOnePathInner = async (path) => {
|
|
1437
|
+
// Each path accumulates into its own lists and its own view of options, so
|
|
1438
|
+
// that legs which record a parent component or a tool cannot overwrite
|
|
1439
|
+
// another path's. The lists are merged below in pathList order.
|
|
1440
|
+
const options = { ...sharedOptions };
|
|
1441
|
+
/** @type {BuilderBomData | undefined} */
|
|
1442
|
+
let bomData;
|
|
1443
|
+
/** @type {Object[]} */
|
|
1444
|
+
let components = [];
|
|
1445
|
+
/** @type {Object[]} */
|
|
1446
|
+
let dependencies = [];
|
|
1447
|
+
/** @type {Object[]} */
|
|
1448
|
+
let parentSubComponents = [];
|
|
1449
|
+
/** @type {Object[]} */
|
|
1450
|
+
let metadataProperties = [];
|
|
1451
|
+
/** @type {Object[]} */
|
|
1452
|
+
let pathFormulationList = [];
|
|
1453
|
+
// Each builder detects the project of its own ecosystem. A builder that sets
|
|
1454
|
+
// options.parentComponent to force its project into its own BOM must not
|
|
1455
|
+
// make that project the next builder's too, or the next ecosystem's project
|
|
1456
|
+
// is lost: every builder starts from the parent the caller gave.
|
|
1457
|
+
const callerParent = options.parentComponent;
|
|
1458
|
+
const withOwnParent = async (build) => {
|
|
1459
|
+
options.parentComponent = callerParent;
|
|
1460
|
+
try {
|
|
1461
|
+
return await build();
|
|
1462
|
+
} finally {
|
|
1463
|
+
options.parentComponent = callerParent;
|
|
1774
1464
|
}
|
|
1465
|
+
};
|
|
1466
|
+
setActivityContext({
|
|
1467
|
+
projectType: Array.isArray(options.projectType)
|
|
1468
|
+
? options.projectType.join(",")
|
|
1469
|
+
: options.projectType,
|
|
1470
|
+
sourcePath: path,
|
|
1471
|
+
});
|
|
1472
|
+
recordActivity({
|
|
1473
|
+
kind: "read",
|
|
1474
|
+
reason:
|
|
1475
|
+
"Scanning project path for supported languages and package managers.",
|
|
1476
|
+
status: "completed",
|
|
1477
|
+
target: path,
|
|
1478
|
+
});
|
|
1479
|
+
if (DEBUG_MODE) {
|
|
1480
|
+
console.log("Scanning", path);
|
|
1481
|
+
}
|
|
1482
|
+
if (pathList.length > 2) {
|
|
1483
|
+
thoughtLog(`Let's thoroughly check the path ${path}.`);
|
|
1775
1484
|
}
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1485
|
+
for (const step of pathScanSteps) {
|
|
1486
|
+
if (!step.applies(path, options)) {
|
|
1487
|
+
continue;
|
|
1488
|
+
}
|
|
1489
|
+
const context = step.prepare?.(path, options);
|
|
1490
|
+
step.announce?.(path, options, context);
|
|
1491
|
+
setProjectTypeActivityContext(
|
|
1492
|
+
typeof step.activityType === "function"
|
|
1493
|
+
? step.activityType(context)
|
|
1494
|
+
: step.activityType,
|
|
1495
|
+
path,
|
|
1780
1496
|
);
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1497
|
+
step.beforeBuild?.(path);
|
|
1498
|
+
const { build } = step;
|
|
1499
|
+
bomData = /** @type {BuilderBomData | undefined} */ (
|
|
1500
|
+
await withOwnParent(() => build(path, options))
|
|
1501
|
+
);
|
|
1502
|
+
if (
|
|
1503
|
+
step.collectMetadataProperties &&
|
|
1504
|
+
bomData?.metadataProperties?.length
|
|
1505
|
+
) {
|
|
1506
|
+
metadataProperties = metadataProperties.concat(
|
|
1507
|
+
bomData.metadataProperties,
|
|
1791
1508
|
);
|
|
1792
|
-
if (
|
|
1793
|
-
bomData.parentComponent &&
|
|
1794
|
-
Object.keys(bomData.parentComponent).length
|
|
1795
|
-
) {
|
|
1796
|
-
parentSubComponents.push(bomData.parentComponent);
|
|
1797
|
-
}
|
|
1798
1509
|
}
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
if (
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
statSync(path).isDirectory() &&
|
|
1809
|
-
safeExistsSync(join(path, "manifest.json"));
|
|
1810
|
-
} catch (_err) {
|
|
1811
|
-
isExplicitChromeExtensionPath = false;
|
|
1812
|
-
}
|
|
1510
|
+
const isUseful = step.isUseful
|
|
1511
|
+
? step.isUseful(bomData)
|
|
1512
|
+
: !!bomData?.bomJson?.components?.length;
|
|
1513
|
+
if (isUseful) {
|
|
1514
|
+
// a csharp result can be kept for its dependencies alone
|
|
1515
|
+
const count = bomData.bomJson.components?.length || 0;
|
|
1516
|
+
const thought = step.foundThought?.(count, path, context);
|
|
1517
|
+
if (thought) {
|
|
1518
|
+
thoughtLog(thought);
|
|
1813
1519
|
}
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
isExplicitChromeExtensionPath ||
|
|
1817
|
-
!sharedOptions.__didScanChromeExtensions
|
|
1818
|
-
) {
|
|
1819
|
-
if (!isExplicitChromeExtensionPath) {
|
|
1820
|
-
sharedOptions.__didScanChromeExtensions = true;
|
|
1520
|
+
if (DEBUG_MODE) {
|
|
1521
|
+
console.log(step.debugMessage(count, path, context));
|
|
1821
1522
|
}
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1523
|
+
components = components.concat(bomData.bomJson.components || []);
|
|
1524
|
+
if (step.dependencyMerge === "concat") {
|
|
1525
|
+
dependencies = dependencies.concat(
|
|
1526
|
+
bomData.bomJson.dependencies || [],
|
|
1527
|
+
);
|
|
1528
|
+
} else if (step.dependencyMerge === "topLevel") {
|
|
1529
|
+
// the crypto scan returns its dependencies beside bomJson
|
|
1530
|
+
if (bomData.dependencies?.length) {
|
|
1531
|
+
dependencies = mergeDependencies(
|
|
1532
|
+
dependencies,
|
|
1533
|
+
bomData.dependencies,
|
|
1830
1534
|
);
|
|
1831
1535
|
}
|
|
1832
|
-
|
|
1536
|
+
} else if (step.dependencyMerge === "mergeWithParent") {
|
|
1537
|
+
dependencies = mergeDependencies(
|
|
1538
|
+
dependencies,
|
|
1539
|
+
bomData.bomJson.dependencies,
|
|
1540
|
+
bomData.parentComponent,
|
|
1541
|
+
);
|
|
1542
|
+
} else {
|
|
1833
1543
|
dependencies = mergeDependencies(
|
|
1834
1544
|
dependencies,
|
|
1835
1545
|
bomData.bomJson.dependencies,
|
|
1836
1546
|
);
|
|
1837
|
-
if (
|
|
1838
|
-
bomData.parentComponent &&
|
|
1839
|
-
Object.keys(bomData.parentComponent).length
|
|
1840
|
-
) {
|
|
1841
|
-
parentSubComponents.push(bomData.parentComponent);
|
|
1842
|
-
}
|
|
1843
1547
|
}
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
"**TERRAFORM**: Looking for Terraform and OpenTofu modules and providers. The registry metadata fetch is opt-in, so I will rely on the lock file, the module manifest and the configuration files.",
|
|
1849
|
-
);
|
|
1850
|
-
setProjectTypeActivityContext("terraform", path);
|
|
1851
|
-
bomData = await withOwnParent(() => createTerraformBom(path, options));
|
|
1852
|
-
if (bomData?.bomJson?.components?.length) {
|
|
1853
|
-
thoughtLog(
|
|
1854
|
-
`I found ${bomData.bomJson.components.length} terraform components. Should I also resolve their licenses from the registry when FETCH_LICENSE is enabled?`,
|
|
1855
|
-
);
|
|
1856
|
-
if (DEBUG_MODE) {
|
|
1857
|
-
console.log(
|
|
1858
|
-
`Found ${bomData.bomJson.components.length} terraform components at ${path}`,
|
|
1548
|
+
if (step.mergeServices && bomData?.bomJson?.services?.length) {
|
|
1549
|
+
options.services = mergeServices(
|
|
1550
|
+
options.services || [],
|
|
1551
|
+
bomData.bomJson.services,
|
|
1859
1552
|
);
|
|
1860
1553
|
}
|
|
1861
|
-
components = components.concat(bomData.bomJson.components);
|
|
1862
|
-
dependencies = mergeDependencies(
|
|
1863
|
-
dependencies,
|
|
1864
|
-
bomData.bomJson.dependencies,
|
|
1865
|
-
);
|
|
1866
1554
|
if (
|
|
1555
|
+
step.keepParent !== false &&
|
|
1867
1556
|
bomData.parentComponent &&
|
|
1868
1557
|
Object.keys(bomData.parentComponent).length
|
|
1869
1558
|
) {
|
|
1870
1559
|
parentSubComponents.push(bomData.parentComponent);
|
|
1871
1560
|
}
|
|
1561
|
+
// Retain metadata.component.components
|
|
1562
|
+
if (
|
|
1563
|
+
step.hoistSubComponents &&
|
|
1564
|
+
bomData.parentComponent?.components?.length
|
|
1565
|
+
) {
|
|
1566
|
+
let bomSubComponents = bomData.parentComponent.components;
|
|
1567
|
+
// For java, sub-components that are already components are added
|
|
1568
|
+
// to the list of current components and removed from
|
|
1569
|
+
// metadata.component.components; the components are merged later.
|
|
1570
|
+
if (
|
|
1571
|
+
step.hoistSubComponents === "gradle" &&
|
|
1572
|
+
!["false", "0"].includes(
|
|
1573
|
+
readEnvironmentVariable("GRADLE_RESOLVE_FROM_NODE"),
|
|
1574
|
+
)
|
|
1575
|
+
) {
|
|
1576
|
+
thoughtLog(
|
|
1577
|
+
"Wait, the user wants me to resolve gradle projects from npm.",
|
|
1578
|
+
);
|
|
1579
|
+
const allRefs = components.map((c) => c["bom-ref"]);
|
|
1580
|
+
const duplicateComponents = bomSubComponents.filter((c) =>
|
|
1581
|
+
allRefs.includes(c["bom-ref"]),
|
|
1582
|
+
);
|
|
1583
|
+
components = components.concat(duplicateComponents);
|
|
1584
|
+
const duplicateComponentRefs = duplicateComponents.map(
|
|
1585
|
+
(c) => c["bom-ref"],
|
|
1586
|
+
);
|
|
1587
|
+
bomSubComponents = bomSubComponents.filter(
|
|
1588
|
+
(c) => !duplicateComponentRefs.includes(c["bom-ref"]),
|
|
1589
|
+
);
|
|
1590
|
+
}
|
|
1591
|
+
parentSubComponents = parentSubComponents.concat(bomSubComponents);
|
|
1592
|
+
delete bomData.parentComponent.components;
|
|
1593
|
+
}
|
|
1872
1594
|
}
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
options.specVersion >= 1.6 &&
|
|
1877
|
-
options.includeCrypto &&
|
|
1878
|
-
isCycloneDxComponentTypeEnabled("cryptographic-asset", options)
|
|
1879
|
-
) {
|
|
1880
|
-
if (!hasAnyProjectType(["oci"], options, false)) {
|
|
1881
|
-
thoughtLog(
|
|
1882
|
-
"**CBOM**: Wait, the user wants me to look for cryptographic assets. Let's check thoroughly.",
|
|
1883
|
-
);
|
|
1884
|
-
}
|
|
1885
|
-
setProjectTypeActivityContext("cbom", path);
|
|
1886
|
-
bomData = await withOwnParent(() => createCryptoCertsBom(path, options));
|
|
1887
|
-
if (bomData?.bomJson?.components?.length) {
|
|
1888
|
-
thoughtLog(
|
|
1889
|
-
`I found ${bomData.bomJson.components.length} crypto assets.`,
|
|
1595
|
+
if (step.collectFormulation && bomData?.formulationList?.length) {
|
|
1596
|
+
pathFormulationList = pathFormulationList.concat(
|
|
1597
|
+
bomData.formulationList,
|
|
1890
1598
|
);
|
|
1891
|
-
if (DEBUG_MODE) {
|
|
1892
|
-
console.log(
|
|
1893
|
-
`Found ${bomData.bomJson.components.length} crypto assets at ${path}`,
|
|
1894
|
-
);
|
|
1895
|
-
}
|
|
1896
|
-
components = components.concat(bomData.bomJson.components);
|
|
1897
|
-
if (bomData.dependencies?.length) {
|
|
1898
|
-
dependencies = mergeDependencies(dependencies, bomData.dependencies);
|
|
1899
|
-
}
|
|
1900
1599
|
}
|
|
1901
1600
|
}
|
|
1902
1601
|
return {
|
|
@@ -1905,6 +1604,7 @@ export async function createMultiXBom(pathList, options) {
|
|
|
1905
1604
|
options,
|
|
1906
1605
|
parentSubComponents,
|
|
1907
1606
|
metadataProperties,
|
|
1607
|
+
formulationList: pathFormulationList,
|
|
1908
1608
|
};
|
|
1909
1609
|
};
|
|
1910
1610
|
const pathResults = [];
|
|
@@ -1971,6 +1671,7 @@ export async function createMultiXBom(pathList, options) {
|
|
|
1971
1671
|
metadataProperties = metadataProperties.concat(
|
|
1972
1672
|
pathResult.metadataProperties,
|
|
1973
1673
|
);
|
|
1674
|
+
formulationList = formulationList.concat(pathResult.formulationList);
|
|
1974
1675
|
if (pathResult.options.services?.length) {
|
|
1975
1676
|
sharedOptions.services = mergeServices(
|
|
1976
1677
|
sharedOptions.services || [],
|
|
@@ -3158,6 +2859,16 @@ async function createBomForPath(path, options) {
|
|
|
3158
2859
|
* @throws {Error} if the request fails
|
|
3159
2860
|
*/
|
|
3160
2861
|
export async function submitBom(args, bomContents) {
|
|
2862
|
+
// Dependency-Track ingests CycloneDX 1.x only. The CLI and the server refuse
|
|
2863
|
+
// a 2.0 upload before generating; this covers library callers.
|
|
2864
|
+
if (
|
|
2865
|
+
isCycloneDx20SpecVersion(bomContents?.specVersion) ||
|
|
2866
|
+
bomContents?.specFormat !== undefined
|
|
2867
|
+
) {
|
|
2868
|
+
console.log(DEPENDENCY_TRACK_CDX20_ERROR);
|
|
2869
|
+
args.failOnError && process.exit(1);
|
|
2870
|
+
return undefined;
|
|
2871
|
+
}
|
|
3161
2872
|
const dependencyTrackApiUrl = getDependencyTrackBomApiUrl(args.serverUrl);
|
|
3162
2873
|
const serverUrl = dependencyTrackApiUrl?.toString();
|
|
3163
2874
|
if (!dependencyTrackApiUrl || !serverUrl) {
|