@cyclonedx/cdxgen 11.4.4 → 11.5.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 +63 -63
- package/bin/cdxgen.js +31 -9
- package/lib/cli/index.js +334 -95
- package/lib/helpers/envcontext.js +20 -18
- package/lib/helpers/logger.js +0 -2
- package/lib/helpers/utils.js +743 -58
- package/lib/helpers/utils.test.js +444 -23
- package/lib/helpers/validator.js +10 -0
- package/lib/managers/binary.js +89 -11
- package/lib/managers/docker.js +47 -37
- package/lib/server/server.js +120 -6
- package/lib/server/server.test.js +235 -0
- package/package.json +30 -11
- package/types/lib/cli/index.d.ts +8 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/helpers/envcontext.d.ts +3 -8
- package/types/lib/helpers/envcontext.d.ts.map +1 -1
- package/types/lib/helpers/logger.d.ts +0 -1
- package/types/lib/helpers/logger.d.ts.map +1 -1
- package/types/lib/helpers/utils.d.ts +67 -66
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/helpers/validator.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +22 -2
- package/types/lib/server/server.d.ts.map +1 -1
package/lib/cli/index.js
CHANGED
|
@@ -111,6 +111,8 @@ import {
|
|
|
111
111
|
parseCsProjAssetsData,
|
|
112
112
|
parseCsProjData,
|
|
113
113
|
parseEdnData,
|
|
114
|
+
parseFlakeLock,
|
|
115
|
+
parseFlakeNix,
|
|
114
116
|
parseGemfileLockData,
|
|
115
117
|
parseGemspecData,
|
|
116
118
|
parseGitHubWorkflowData,
|
|
@@ -524,29 +526,47 @@ const addFormulationSection = (options, context) => {
|
|
|
524
526
|
let environmentVars = gitBranch?.length
|
|
525
527
|
? [{ name: "GIT_BRANCH", value: gitBranch }]
|
|
526
528
|
: [];
|
|
529
|
+
const envPrefixes = [
|
|
530
|
+
"GIT",
|
|
531
|
+
"ANDROID",
|
|
532
|
+
"DENO",
|
|
533
|
+
"DOTNET",
|
|
534
|
+
"JAVA_",
|
|
535
|
+
"SDKMAN",
|
|
536
|
+
"CARGO",
|
|
537
|
+
"CONDA",
|
|
538
|
+
"RUST",
|
|
539
|
+
"GEM_",
|
|
540
|
+
"SCALA_",
|
|
541
|
+
"MAVEN_",
|
|
542
|
+
"GRADLE_",
|
|
543
|
+
"NODE_",
|
|
544
|
+
];
|
|
545
|
+
const envBlocklist = [
|
|
546
|
+
"key",
|
|
547
|
+
"token",
|
|
548
|
+
"pass",
|
|
549
|
+
"secret",
|
|
550
|
+
"user",
|
|
551
|
+
"email",
|
|
552
|
+
"auth",
|
|
553
|
+
"session",
|
|
554
|
+
"proxy",
|
|
555
|
+
"cred",
|
|
556
|
+
];
|
|
557
|
+
|
|
527
558
|
for (const aevar of Object.keys(process.env)) {
|
|
559
|
+
const lower = aevar.toLowerCase();
|
|
560
|
+
const value = process.env[aevar] ?? "";
|
|
528
561
|
if (
|
|
529
|
-
(aevar.startsWith(
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
aevar.startsWith("JAVA_") ||
|
|
534
|
-
aevar.startsWith("SDKMAN") ||
|
|
535
|
-
aevar.startsWith("CARGO") ||
|
|
536
|
-
aevar.startsWith("CONDA") ||
|
|
537
|
-
aevar.startsWith("RUST")) &&
|
|
538
|
-
!aevar.toLowerCase().includes("key") &&
|
|
539
|
-
!aevar.toLowerCase().includes("token") &&
|
|
540
|
-
!aevar.toLowerCase().includes("pass") &&
|
|
541
|
-
!aevar.toLowerCase().includes("secret") &&
|
|
542
|
-
!aevar.toLowerCase().includes("user") &&
|
|
543
|
-
!aevar.toLowerCase().includes("email") &&
|
|
544
|
-
process.env[aevar] &&
|
|
545
|
-
process.env[aevar].length
|
|
562
|
+
envPrefixes.some((p) => aevar.startsWith(p)) &&
|
|
563
|
+
!envBlocklist.some((b) => lower.includes(b)) &&
|
|
564
|
+
!envBlocklist.some((b) => value.includes(b)) &&
|
|
565
|
+
value.length
|
|
546
566
|
) {
|
|
547
567
|
environmentVars.push({
|
|
548
568
|
name: aevar,
|
|
549
|
-
value
|
|
569
|
+
value,
|
|
550
570
|
});
|
|
551
571
|
}
|
|
552
572
|
}
|
|
@@ -1385,6 +1405,10 @@ export async function createJarBom(path, options) {
|
|
|
1385
1405
|
if (!options.exclude) {
|
|
1386
1406
|
options.exclude = [];
|
|
1387
1407
|
}
|
|
1408
|
+
// We can look for jar files within node_modules directory
|
|
1409
|
+
if (typeof options.includeNodeModulesDir === "undefined" || options.deep) {
|
|
1410
|
+
options.includeNodeModulesDir = true;
|
|
1411
|
+
}
|
|
1388
1412
|
// Exclude certain directories during oci sbom generation
|
|
1389
1413
|
if (hasAnyProjectType(["oci"], options, false)) {
|
|
1390
1414
|
options.exclude.push("**/android-sdk*/**");
|
|
@@ -2305,7 +2329,7 @@ export async function createJavaBom(path, options) {
|
|
|
2305
2329
|
) {
|
|
2306
2330
|
bArgs = ["--bazelrc=.bazelrc", "build", bazelTarget];
|
|
2307
2331
|
}
|
|
2308
|
-
console.log("Executing", BAZEL_CMD,
|
|
2332
|
+
console.log("Executing", BAZEL_CMD, "in", basePath);
|
|
2309
2333
|
let result = safeSpawnSync(BAZEL_CMD, bArgs, {
|
|
2310
2334
|
cwd: basePath,
|
|
2311
2335
|
shell: true,
|
|
@@ -2639,7 +2663,7 @@ export async function createJavaBom(path, options) {
|
|
|
2639
2663
|
}
|
|
2640
2664
|
const millArgs = [...millCommonArgs, "__.ivyDepsTree"];
|
|
2641
2665
|
if (DEBUG_MODE) {
|
|
2642
|
-
console.log("Executing", millCmd,
|
|
2666
|
+
console.log("Executing", millCmd, "in", millRootPath);
|
|
2643
2667
|
}
|
|
2644
2668
|
let sresult = safeSpawnSync(millCmd, millArgs, {
|
|
2645
2669
|
cwd: millRootPath,
|
|
@@ -3761,7 +3785,7 @@ export async function createPythonBom(path, options) {
|
|
|
3761
3785
|
// Retrieve the tree using virtualenv in deep mode and as a fallback
|
|
3762
3786
|
// This is a slow operation
|
|
3763
3787
|
if ((options.deep || !dependencies.length) && !f.endsWith("uv.lock")) {
|
|
3764
|
-
retMap = getPipFrozenTree(basePath, f, tempDir, parentComponent);
|
|
3788
|
+
retMap = await getPipFrozenTree(basePath, f, tempDir, parentComponent);
|
|
3765
3789
|
if (retMap.pkgList?.length) {
|
|
3766
3790
|
pkgList = pkgList.concat(retMap.pkgList);
|
|
3767
3791
|
}
|
|
@@ -3839,9 +3863,49 @@ export async function createPythonBom(path, options) {
|
|
|
3839
3863
|
console.error("Pipfile.lock not found at", path);
|
|
3840
3864
|
options.failOnError && process.exit(1);
|
|
3841
3865
|
}
|
|
3842
|
-
} else if (
|
|
3843
|
-
|
|
3844
|
-
|
|
3866
|
+
} else if (reqDirFiles?.length) {
|
|
3867
|
+
for (const j in reqDirFiles) {
|
|
3868
|
+
const f = reqDirFiles[j];
|
|
3869
|
+
const dlist = await parseReqFile(f, false);
|
|
3870
|
+
if (dlist?.length) {
|
|
3871
|
+
pkgList = pkgList.concat(dlist);
|
|
3872
|
+
}
|
|
3873
|
+
}
|
|
3874
|
+
metadataFilename = reqDirFiles.join(", ");
|
|
3875
|
+
} else if (reqFiles?.length) {
|
|
3876
|
+
for (const f of reqFiles) {
|
|
3877
|
+
const dlist = await parseReqFile(f, true);
|
|
3878
|
+
if (dlist?.length) {
|
|
3879
|
+
pkgList = pkgList.concat(dlist);
|
|
3880
|
+
}
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
3884
|
+
|
|
3885
|
+
const parentDependsOn = new Set();
|
|
3886
|
+
|
|
3887
|
+
// Use atom in requirements, setup.py and pyproject.toml mode
|
|
3888
|
+
if (requirementsMode || setupPyMode || pyProjectMode || options.deep) {
|
|
3889
|
+
/**
|
|
3890
|
+
* The order of preference is pyproject.toml (newer) and then setup.py
|
|
3891
|
+
*/
|
|
3892
|
+
if (options.installDeps) {
|
|
3893
|
+
let pkgMap;
|
|
3894
|
+
if (pyProjectMode && !poetryMode) {
|
|
3895
|
+
pkgMap = await getPipFrozenTree(
|
|
3896
|
+
path,
|
|
3897
|
+
pyProjectFile,
|
|
3898
|
+
tempDir,
|
|
3899
|
+
parentComponent,
|
|
3900
|
+
);
|
|
3901
|
+
} else if (setupPyMode) {
|
|
3902
|
+
pkgMap = await getPipFrozenTree(
|
|
3903
|
+
path,
|
|
3904
|
+
setupPy,
|
|
3905
|
+
tempDir,
|
|
3906
|
+
parentComponent,
|
|
3907
|
+
);
|
|
3908
|
+
} else if (requirementsMode && reqFiles?.length) {
|
|
3845
3909
|
if (options.installDeps && DEBUG_MODE) {
|
|
3846
3910
|
console.log(
|
|
3847
3911
|
"cdxgen will now attempt to generate an SBOM for 'build' lifecycle phase for Python. This would take some time ...\nTo speed up this step, invoke cdxgen from within a virtual environment with all the dependencies installed.\nAlternatively, pass the argument '--lifecycle pre-build' to generate a faster but less precise SBOM.",
|
|
@@ -3849,13 +3913,8 @@ export async function createPythonBom(path, options) {
|
|
|
3849
3913
|
}
|
|
3850
3914
|
for (const f of reqFiles) {
|
|
3851
3915
|
const basePath = dirname(f);
|
|
3852
|
-
let reqData;
|
|
3853
|
-
let frozen = false;
|
|
3854
|
-
// Attempt to pip freeze in a virtualenv to improve precision
|
|
3855
3916
|
if (options.installDeps) {
|
|
3856
|
-
|
|
3857
|
-
// adding to the delay.
|
|
3858
|
-
const pkgMap = getPipFrozenTree(
|
|
3917
|
+
const pkgMap = await getPipFrozenTree(
|
|
3859
3918
|
basePath,
|
|
3860
3919
|
f,
|
|
3861
3920
|
tempDir,
|
|
@@ -3863,10 +3922,11 @@ export async function createPythonBom(path, options) {
|
|
|
3863
3922
|
);
|
|
3864
3923
|
if (pkgMap.pkgList?.length) {
|
|
3865
3924
|
pkgList = pkgList.concat(pkgMap.pkgList);
|
|
3866
|
-
|
|
3925
|
+
pkgList = trimComponents(pkgList);
|
|
3867
3926
|
}
|
|
3868
3927
|
if (pkgMap.formulationList?.length) {
|
|
3869
3928
|
formulationList = formulationList.concat(pkgMap.formulationList);
|
|
3929
|
+
formulationList = trimComponents(formulationList);
|
|
3870
3930
|
}
|
|
3871
3931
|
if (pkgMap.dependenciesList) {
|
|
3872
3932
|
dependencies = mergeDependencies(
|
|
@@ -3875,61 +3935,31 @@ export async function createPythonBom(path, options) {
|
|
|
3875
3935
|
parentComponent,
|
|
3876
3936
|
);
|
|
3877
3937
|
}
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3938
|
+
// Add the root packages from this file to the parent's dependencies
|
|
3939
|
+
for (const p of pkgMap.rootList) {
|
|
3940
|
+
if (
|
|
3941
|
+
parentComponent &&
|
|
3942
|
+
p.name === parentComponent.name &&
|
|
3943
|
+
(p.version === parentComponent.version ||
|
|
3944
|
+
p.version === "latest")
|
|
3945
|
+
) {
|
|
3946
|
+
continue;
|
|
3947
|
+
}
|
|
3948
|
+
parentDependsOn.add(
|
|
3949
|
+
`pkg:pypi/${p.name.toLowerCase()}@${p.version}`,
|
|
3887
3950
|
);
|
|
3888
3951
|
}
|
|
3889
|
-
reqData = readFileSync(f, { encoding: "utf-8" });
|
|
3890
|
-
const dlist = await parseReqFile(reqData, true);
|
|
3891
|
-
if (dlist?.length) {
|
|
3892
|
-
pkgList = pkgList.concat(dlist);
|
|
3893
|
-
}
|
|
3894
|
-
}
|
|
3895
|
-
} // for
|
|
3896
|
-
metadataFilename = reqFiles.join(", ");
|
|
3897
|
-
} else if (reqDirFiles?.length) {
|
|
3898
|
-
for (const j in reqDirFiles) {
|
|
3899
|
-
const f = reqDirFiles[j];
|
|
3900
|
-
const reqData = readFileSync(f, { encoding: "utf-8" });
|
|
3901
|
-
const dlist = await parseReqFile(reqData, false);
|
|
3902
|
-
if (dlist?.length) {
|
|
3903
|
-
pkgList = pkgList.concat(dlist);
|
|
3904
3952
|
}
|
|
3905
3953
|
}
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
}
|
|
3909
|
-
}
|
|
3910
|
-
// Use atom in requirements, setup.py and pyproject.toml mode
|
|
3911
|
-
if (requirementsMode || setupPyMode || pyProjectMode || options.deep) {
|
|
3912
|
-
/**
|
|
3913
|
-
* The order of preference is pyproject.toml (newer) and then setup.py
|
|
3914
|
-
*/
|
|
3915
|
-
if (options.installDeps) {
|
|
3916
|
-
let pkgMap;
|
|
3917
|
-
if (pyProjectMode && !poetryMode) {
|
|
3918
|
-
pkgMap = getPipFrozenTree(
|
|
3954
|
+
} else if (!poetryMode) {
|
|
3955
|
+
pkgMap = await getPipFrozenTree(
|
|
3919
3956
|
path,
|
|
3920
|
-
|
|
3957
|
+
undefined,
|
|
3921
3958
|
tempDir,
|
|
3922
3959
|
parentComponent,
|
|
3923
3960
|
);
|
|
3924
|
-
} else if (setupPyMode) {
|
|
3925
|
-
pkgMap = getPipFrozenTree(path, setupPy, tempDir, parentComponent);
|
|
3926
|
-
} else if (!poetryMode) {
|
|
3927
|
-
pkgMap = getPipFrozenTree(path, undefined, tempDir, parentComponent);
|
|
3928
3961
|
}
|
|
3929
3962
|
|
|
3930
|
-
// Get the imported modules and a dedupe list of packages
|
|
3931
|
-
const parentDependsOn = new Set();
|
|
3932
|
-
|
|
3933
3963
|
// ATOM parsedeps block
|
|
3934
3964
|
// Atom parsedeps slices can be used to identify packages that are not declared in manifests
|
|
3935
3965
|
// Since it is a slow operation, we only use it as a fallback or in deep mode
|
|
@@ -4253,11 +4283,21 @@ export async function createGoBom(path, options) {
|
|
|
4253
4283
|
}
|
|
4254
4284
|
if (gomodFiles.length) {
|
|
4255
4285
|
let shouldManuallyParse = false;
|
|
4286
|
+
// Sort go.mod files by depth (shallowest first) to prioritize root modules
|
|
4287
|
+
const sortedGomodFiles = gomodFiles.sort((a, b) => {
|
|
4288
|
+
const relativePathA = relative(path, a);
|
|
4289
|
+
const relativePathB = relative(path, b);
|
|
4290
|
+
const depthA = relativePathA.split("/").length;
|
|
4291
|
+
const depthB = relativePathB.split("/").length;
|
|
4292
|
+
return depthA - depthB;
|
|
4293
|
+
});
|
|
4294
|
+
|
|
4295
|
+
let rootParentComponent = null;
|
|
4256
4296
|
// Use the go list -deps and go mod why commands to generate a good quality BOM for non-docker invocations
|
|
4257
4297
|
if (
|
|
4258
4298
|
!hasAnyProjectType(["docker", "oci", "container", "os"], options, false)
|
|
4259
4299
|
) {
|
|
4260
|
-
for (const f of
|
|
4300
|
+
for (const f of sortedGomodFiles) {
|
|
4261
4301
|
const basePath = dirname(f);
|
|
4262
4302
|
// Ignore vendor packages
|
|
4263
4303
|
if (
|
|
@@ -4307,13 +4347,23 @@ export async function createGoBom(path, options) {
|
|
|
4307
4347
|
if (retMap.pkgList?.length) {
|
|
4308
4348
|
pkgList = pkgList.concat(retMap.pkgList);
|
|
4309
4349
|
}
|
|
4310
|
-
//
|
|
4350
|
+
// Prioritize the shallowest module as the root component
|
|
4311
4351
|
if (
|
|
4312
4352
|
retMap.parentComponent &&
|
|
4313
4353
|
Object.keys(retMap.parentComponent).length
|
|
4314
4354
|
) {
|
|
4315
|
-
|
|
4316
|
-
|
|
4355
|
+
if (!rootParentComponent) {
|
|
4356
|
+
// First (shallowest) module becomes the root
|
|
4357
|
+
rootParentComponent = retMap.parentComponent;
|
|
4358
|
+
rootParentComponent.type = "application";
|
|
4359
|
+
parentComponent = rootParentComponent;
|
|
4360
|
+
} else {
|
|
4361
|
+
// Subsequent modules become subcomponents
|
|
4362
|
+
if (!parentComponent.components) {
|
|
4363
|
+
parentComponent.components = [];
|
|
4364
|
+
}
|
|
4365
|
+
parentComponent.components.push(retMap.parentComponent);
|
|
4366
|
+
}
|
|
4317
4367
|
}
|
|
4318
4368
|
if (DEBUG_MODE) {
|
|
4319
4369
|
console.log("Executing go mod graph in", basePath);
|
|
@@ -4399,11 +4449,14 @@ export async function createGoBom(path, options) {
|
|
|
4399
4449
|
parentComponent,
|
|
4400
4450
|
);
|
|
4401
4451
|
}
|
|
4402
|
-
// Retain the parent component hierarchy
|
|
4452
|
+
// Retain the parent component hierarchy, prioritizing the shallowest module
|
|
4403
4453
|
if (Object.keys(retMap.parentComponent).length) {
|
|
4404
|
-
if (
|
|
4405
|
-
|
|
4454
|
+
if (!rootParentComponent) {
|
|
4455
|
+
// First (shallowest) module becomes the root
|
|
4456
|
+
rootParentComponent = retMap.parentComponent;
|
|
4457
|
+
parentComponent = rootParentComponent;
|
|
4406
4458
|
} else {
|
|
4459
|
+
// Subsequent modules become subcomponents
|
|
4407
4460
|
parentComponent.components = parentComponent.components || [];
|
|
4408
4461
|
parentComponent.components.push(retMap.parentComponent);
|
|
4409
4462
|
}
|
|
@@ -4429,7 +4482,7 @@ export async function createGoBom(path, options) {
|
|
|
4429
4482
|
dependencies,
|
|
4430
4483
|
parentComponent,
|
|
4431
4484
|
src: path,
|
|
4432
|
-
filename:
|
|
4485
|
+
filename: sortedGomodFiles.join(", "),
|
|
4433
4486
|
});
|
|
4434
4487
|
}
|
|
4435
4488
|
}
|
|
@@ -4441,7 +4494,7 @@ export async function createGoBom(path, options) {
|
|
|
4441
4494
|
"Manually parsing go.mod files. The resultant BOM would be incomplete.",
|
|
4442
4495
|
);
|
|
4443
4496
|
}
|
|
4444
|
-
for (const f of
|
|
4497
|
+
for (const f of sortedGomodFiles) {
|
|
4445
4498
|
if (DEBUG_MODE) {
|
|
4446
4499
|
console.log(`Parsing ${f}`);
|
|
4447
4500
|
}
|
|
@@ -4450,11 +4503,14 @@ export async function createGoBom(path, options) {
|
|
|
4450
4503
|
if (retMap?.pkgList?.length) {
|
|
4451
4504
|
pkgList = pkgList.concat(retMap.pkgList);
|
|
4452
4505
|
}
|
|
4453
|
-
// Retain the parent component hierarchy
|
|
4506
|
+
// Retain the parent component hierarchy, prioritizing the shallowest module
|
|
4454
4507
|
if (Object.keys(retMap.parentComponent).length) {
|
|
4455
|
-
if (
|
|
4456
|
-
|
|
4508
|
+
if (!rootParentComponent) {
|
|
4509
|
+
// First (shallowest) module becomes the root
|
|
4510
|
+
rootParentComponent = retMap.parentComponent;
|
|
4511
|
+
parentComponent = rootParentComponent;
|
|
4457
4512
|
} else {
|
|
4513
|
+
// Subsequent modules become subcomponents
|
|
4458
4514
|
parentComponent.components = parentComponent.components || [];
|
|
4459
4515
|
parentComponent.components.push(retMap.parentComponent);
|
|
4460
4516
|
}
|
|
@@ -4479,7 +4535,7 @@ export async function createGoBom(path, options) {
|
|
|
4479
4535
|
src: path,
|
|
4480
4536
|
dependencies,
|
|
4481
4537
|
parentComponent,
|
|
4482
|
-
filename:
|
|
4538
|
+
filename: sortedGomodFiles.join(", "),
|
|
4483
4539
|
});
|
|
4484
4540
|
}
|
|
4485
4541
|
if (gopkgLockFiles.length) {
|
|
@@ -4927,7 +4983,7 @@ export function createClojureBom(path, options) {
|
|
|
4927
4983
|
console.log(`Parsing ${f}`);
|
|
4928
4984
|
}
|
|
4929
4985
|
const basePath = dirname(f);
|
|
4930
|
-
console.log("Executing", LEIN_CMD,
|
|
4986
|
+
console.log("Executing", LEIN_CMD, "in", basePath);
|
|
4931
4987
|
const result = safeSpawnSync(LEIN_CMD, LEIN_ARGS, {
|
|
4932
4988
|
cwd: basePath,
|
|
4933
4989
|
encoding: "utf-8",
|
|
@@ -4976,7 +5032,7 @@ export function createClojureBom(path, options) {
|
|
|
4976
5032
|
}
|
|
4977
5033
|
for (const f of ednFiles) {
|
|
4978
5034
|
const basePath = dirname(f);
|
|
4979
|
-
console.log("Executing", CLJ_CMD,
|
|
5035
|
+
console.log("Executing", CLJ_CMD, "in", basePath);
|
|
4980
5036
|
const result = safeSpawnSync(CLJ_CMD, CLJ_ARGS, {
|
|
4981
5037
|
cwd: basePath,
|
|
4982
5038
|
encoding: "utf-8",
|
|
@@ -5584,6 +5640,128 @@ export async function createCocoaBom(path, options) {
|
|
|
5584
5640
|
}
|
|
5585
5641
|
}
|
|
5586
5642
|
|
|
5643
|
+
/**
|
|
5644
|
+
* Function to create bom string for Nix flakes
|
|
5645
|
+
*
|
|
5646
|
+
* @param {string} path to the project
|
|
5647
|
+
* @param {Object} options Parse options from the cli
|
|
5648
|
+
*/
|
|
5649
|
+
export async function createNixBom(path, options) {
|
|
5650
|
+
let pkgList = [];
|
|
5651
|
+
let dependencies = [];
|
|
5652
|
+
let parentComponent = {};
|
|
5653
|
+
|
|
5654
|
+
const flakeNixFiles = getAllFiles(
|
|
5655
|
+
path,
|
|
5656
|
+
`${options.multiProject ? "**/" : ""}flake.nix`,
|
|
5657
|
+
options,
|
|
5658
|
+
);
|
|
5659
|
+
const flakeLockFiles = getAllFiles(
|
|
5660
|
+
path,
|
|
5661
|
+
`${options.multiProject ? "**/" : ""}flake.lock`,
|
|
5662
|
+
options,
|
|
5663
|
+
);
|
|
5664
|
+
|
|
5665
|
+
for (const flakeNixFile of flakeNixFiles) {
|
|
5666
|
+
if (DEBUG_MODE) {
|
|
5667
|
+
console.log(`Parsing ${flakeNixFile}`);
|
|
5668
|
+
}
|
|
5669
|
+
const { pkgList: nixPkgs, dependencies: nixDeps } =
|
|
5670
|
+
parseFlakeNix(flakeNixFile);
|
|
5671
|
+
if (nixPkgs?.length) {
|
|
5672
|
+
pkgList = pkgList.concat(nixPkgs);
|
|
5673
|
+
}
|
|
5674
|
+
if (nixDeps?.length) {
|
|
5675
|
+
dependencies = dependencies.concat(nixDeps);
|
|
5676
|
+
}
|
|
5677
|
+
}
|
|
5678
|
+
|
|
5679
|
+
for (const flakeLockFile of flakeLockFiles) {
|
|
5680
|
+
if (DEBUG_MODE) {
|
|
5681
|
+
console.log(`Parsing ${flakeLockFile}`);
|
|
5682
|
+
}
|
|
5683
|
+
const { pkgList: lockPkgs, dependencies: lockDeps } =
|
|
5684
|
+
parseFlakeLock(flakeLockFile);
|
|
5685
|
+
if (lockPkgs?.length) {
|
|
5686
|
+
const mergedPkgs = [];
|
|
5687
|
+
const existingNames = new Set();
|
|
5688
|
+
|
|
5689
|
+
for (const lockPkg of lockPkgs) {
|
|
5690
|
+
mergedPkgs.push(lockPkg);
|
|
5691
|
+
existingNames.add(lockPkg.name);
|
|
5692
|
+
}
|
|
5693
|
+
|
|
5694
|
+
for (const nixPkg of pkgList) {
|
|
5695
|
+
if (!existingNames.has(nixPkg.name)) {
|
|
5696
|
+
mergedPkgs.push(nixPkg);
|
|
5697
|
+
}
|
|
5698
|
+
}
|
|
5699
|
+
|
|
5700
|
+
pkgList = mergedPkgs;
|
|
5701
|
+
}
|
|
5702
|
+
if (lockDeps?.length) {
|
|
5703
|
+
dependencies = dependencies.concat(lockDeps);
|
|
5704
|
+
}
|
|
5705
|
+
|
|
5706
|
+
// Create parent component from flake.lock if found
|
|
5707
|
+
if (!Object.keys(parentComponent).length) {
|
|
5708
|
+
const flakeDir = dirname(flakeLockFile);
|
|
5709
|
+
const projectName = basename(flakeDir);
|
|
5710
|
+
parentComponent = {
|
|
5711
|
+
type: "application",
|
|
5712
|
+
name: projectName,
|
|
5713
|
+
version: "latest",
|
|
5714
|
+
description: `Nix flake project: ${projectName}`,
|
|
5715
|
+
"bom-ref": `pkg:nix/${projectName}@latest`,
|
|
5716
|
+
properties: [
|
|
5717
|
+
{
|
|
5718
|
+
name: "SrcFile",
|
|
5719
|
+
value: flakeLockFile,
|
|
5720
|
+
},
|
|
5721
|
+
{
|
|
5722
|
+
name: "cdx:nix:flake_dir",
|
|
5723
|
+
value: flakeDir,
|
|
5724
|
+
},
|
|
5725
|
+
],
|
|
5726
|
+
};
|
|
5727
|
+
}
|
|
5728
|
+
}
|
|
5729
|
+
|
|
5730
|
+
// If no parent component was created from flake.lock, create one from flake.nix
|
|
5731
|
+
if (!Object.keys(parentComponent).length && flakeNixFiles.length > 0) {
|
|
5732
|
+
const flakeDir = dirname(flakeNixFiles[0]);
|
|
5733
|
+
const projectName = basename(flakeDir);
|
|
5734
|
+
parentComponent = {
|
|
5735
|
+
type: "application",
|
|
5736
|
+
name: projectName,
|
|
5737
|
+
version: "latest",
|
|
5738
|
+
description: `Nix flake project: ${projectName}`,
|
|
5739
|
+
"bom-ref": `pkg:nix/${projectName}@latest`,
|
|
5740
|
+
properties: [
|
|
5741
|
+
{
|
|
5742
|
+
name: "SrcFile",
|
|
5743
|
+
value: flakeNixFiles[0],
|
|
5744
|
+
},
|
|
5745
|
+
{
|
|
5746
|
+
name: "cdx:nix:flake_dir",
|
|
5747
|
+
value: flakeDir,
|
|
5748
|
+
},
|
|
5749
|
+
],
|
|
5750
|
+
};
|
|
5751
|
+
}
|
|
5752
|
+
|
|
5753
|
+
if (pkgList.length || Object.keys(parentComponent).length) {
|
|
5754
|
+
return buildBomNSData(options, pkgList, "nix", {
|
|
5755
|
+
src: path,
|
|
5756
|
+
filename: [...flakeNixFiles, ...flakeLockFiles].join(", "),
|
|
5757
|
+
dependencies,
|
|
5758
|
+
parentComponent,
|
|
5759
|
+
});
|
|
5760
|
+
}
|
|
5761
|
+
|
|
5762
|
+
return {};
|
|
5763
|
+
}
|
|
5764
|
+
|
|
5587
5765
|
/**
|
|
5588
5766
|
* Function to create bom string for docker compose
|
|
5589
5767
|
*
|
|
@@ -5921,6 +6099,10 @@ export async function createContainerSpecLikeBom(path, options) {
|
|
|
5921
6099
|
export function createPHPBom(path, options) {
|
|
5922
6100
|
let dependencies = [];
|
|
5923
6101
|
let parentComponent = {};
|
|
6102
|
+
// We can look for composer files within node_modules directory
|
|
6103
|
+
if (typeof options.includeNodeModulesDir === "undefined" || options.deep) {
|
|
6104
|
+
options.includeNodeModulesDir = true;
|
|
6105
|
+
}
|
|
5924
6106
|
const composerJsonFiles = getAllFiles(
|
|
5925
6107
|
path,
|
|
5926
6108
|
`${options.multiProject ? "**/" : ""}composer.json`,
|
|
@@ -6069,6 +6251,10 @@ export function createPHPBom(path, options) {
|
|
|
6069
6251
|
* @param {Object} options Parse options from the cli
|
|
6070
6252
|
*/
|
|
6071
6253
|
export async function createRubyBom(path, options) {
|
|
6254
|
+
// We can look for gem files within node_modules directory
|
|
6255
|
+
if (typeof options.includeNodeModulesDir === "undefined" || options.deep) {
|
|
6256
|
+
options.includeNodeModulesDir = true;
|
|
6257
|
+
}
|
|
6072
6258
|
const excludeList = (options.exclude || []).concat(["**/vendor/cache/**"]);
|
|
6073
6259
|
const gemLockExcludeList = (options.exclude || []).concat([
|
|
6074
6260
|
"**/vendor/bundle/ruby/**/Gemfile.lock",
|
|
@@ -6861,8 +7047,8 @@ export function trimComponents(components) {
|
|
|
6861
7047
|
}
|
|
6862
7048
|
// comp.evidence.identity can be an array or object
|
|
6863
7049
|
// Merge the evidence.identity based on methods or objects
|
|
6864
|
-
const
|
|
6865
|
-
const identities =
|
|
7050
|
+
const isIdentityArray = Array.isArray(comp.evidence.identity);
|
|
7051
|
+
const identities = isIdentityArray
|
|
6866
7052
|
? comp.evidence.identity
|
|
6867
7053
|
: [comp.evidence.identity];
|
|
6868
7054
|
for (const aident of identities) {
|
|
@@ -6893,9 +7079,23 @@ export function trimComponents(components) {
|
|
|
6893
7079
|
existingComponent.evidence.identity.push(aident);
|
|
6894
7080
|
}
|
|
6895
7081
|
}
|
|
6896
|
-
if (!
|
|
7082
|
+
if (!isIdentityArray) {
|
|
7083
|
+
const firstIdentity = existingComponent.evidence.identity[0];
|
|
7084
|
+
let identConfidence = firstIdentity?.confidence;
|
|
7085
|
+
// We need to set the confidence to the max of all confidences
|
|
7086
|
+
if (firstIdentity?.methods?.length > 1) {
|
|
7087
|
+
for (const aidentMethod of firstIdentity.methods) {
|
|
7088
|
+
if (
|
|
7089
|
+
aidentMethod?.confidence &&
|
|
7090
|
+
aidentMethod.confidence > identConfidence
|
|
7091
|
+
) {
|
|
7092
|
+
identConfidence = aidentMethod.confidence;
|
|
7093
|
+
}
|
|
7094
|
+
}
|
|
7095
|
+
}
|
|
7096
|
+
firstIdentity.confidence = identConfidence;
|
|
6897
7097
|
existingComponent.evidence = {
|
|
6898
|
-
identity:
|
|
7098
|
+
identity: firstIdentity,
|
|
6899
7099
|
};
|
|
6900
7100
|
}
|
|
6901
7101
|
}
|
|
@@ -7635,6 +7835,27 @@ export async function createMultiXBom(pathList, options) {
|
|
|
7635
7835
|
}
|
|
7636
7836
|
}
|
|
7637
7837
|
}
|
|
7838
|
+
if (hasAnyProjectType(["oci", "nix"], options)) {
|
|
7839
|
+
bomData = await createNixBom(path, options);
|
|
7840
|
+
if (bomData?.bomJson?.components?.length) {
|
|
7841
|
+
if (DEBUG_MODE) {
|
|
7842
|
+
console.log(
|
|
7843
|
+
`Found ${bomData.bomJson.components.length} Nix flake packages at ${path}`,
|
|
7844
|
+
);
|
|
7845
|
+
}
|
|
7846
|
+
components = components.concat(bomData.bomJson.components);
|
|
7847
|
+
dependencies = mergeDependencies(
|
|
7848
|
+
dependencies,
|
|
7849
|
+
bomData.bomJson.dependencies,
|
|
7850
|
+
);
|
|
7851
|
+
if (
|
|
7852
|
+
bomData.parentComponent &&
|
|
7853
|
+
Object.keys(bomData.parentComponent).length
|
|
7854
|
+
) {
|
|
7855
|
+
parentSubComponents.push(bomData.parentComponent);
|
|
7856
|
+
}
|
|
7857
|
+
}
|
|
7858
|
+
}
|
|
7638
7859
|
// Collect any crypto keys
|
|
7639
7860
|
if (options.specVersion >= 1.6 && options.includeCrypto) {
|
|
7640
7861
|
if (!hasAnyProjectType(["oci"], options, false)) {
|
|
@@ -8063,6 +8284,21 @@ export async function createXBom(path, options) {
|
|
|
8063
8284
|
if (cocoaFiles.length) {
|
|
8064
8285
|
return await createCocoaBom(path, options);
|
|
8065
8286
|
}
|
|
8287
|
+
|
|
8288
|
+
// Nix flakes
|
|
8289
|
+
const flakeNixFiles = getAllFiles(
|
|
8290
|
+
path,
|
|
8291
|
+
`${options.multiProject ? "**/" : ""}flake.nix`,
|
|
8292
|
+
options,
|
|
8293
|
+
);
|
|
8294
|
+
const flakeLockFiles = getAllFiles(
|
|
8295
|
+
path,
|
|
8296
|
+
`${options.multiProject ? "**/" : ""}flake.lock`,
|
|
8297
|
+
options,
|
|
8298
|
+
);
|
|
8299
|
+
if (flakeNixFiles.length || flakeLockFiles.length) {
|
|
8300
|
+
return await createNixBom(path, options);
|
|
8301
|
+
}
|
|
8066
8302
|
}
|
|
8067
8303
|
|
|
8068
8304
|
/**
|
|
@@ -8308,6 +8544,9 @@ export async function createBom(path, options) {
|
|
|
8308
8544
|
if (PROJECT_TYPE_ALIASES["cocoa"].includes(projectType[0])) {
|
|
8309
8545
|
return await createCocoaBom(path, options);
|
|
8310
8546
|
}
|
|
8547
|
+
if (PROJECT_TYPE_ALIASES["nix"].includes(projectType[0])) {
|
|
8548
|
+
return await createNixBom(path, options);
|
|
8549
|
+
}
|
|
8311
8550
|
switch (projectType[0]) {
|
|
8312
8551
|
case "jar":
|
|
8313
8552
|
return createJarBom(path, options);
|