@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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
5
|
import { afterAll, beforeAll, describe, expect, test } from "@jest/globals";
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
buildObjectForGradleModule,
|
|
12
12
|
encodeForPurl,
|
|
13
13
|
findLicenseId,
|
|
14
|
+
findPnpmPackagePath,
|
|
14
15
|
getCratesMetadata,
|
|
15
16
|
getDartMetadata,
|
|
16
17
|
getLicenses,
|
|
@@ -39,6 +40,7 @@ import {
|
|
|
39
40
|
parseCmakeDotFile,
|
|
40
41
|
parseCmakeLikeFile,
|
|
41
42
|
parseCocoaDependency,
|
|
43
|
+
parseComposerJson,
|
|
42
44
|
parseComposerLock,
|
|
43
45
|
parseConanData,
|
|
44
46
|
parseConanLockData,
|
|
@@ -49,6 +51,8 @@ import {
|
|
|
49
51
|
parseCsProjAssetsData,
|
|
50
52
|
parseCsProjData,
|
|
51
53
|
parseEdnData,
|
|
54
|
+
parseFlakeLock,
|
|
55
|
+
parseFlakeNix,
|
|
52
56
|
parseGemfileLockData,
|
|
53
57
|
parseGemspecData,
|
|
54
58
|
parseGitHubWorkflowData,
|
|
@@ -98,6 +102,7 @@ import {
|
|
|
98
102
|
parseSwiftJsonTree,
|
|
99
103
|
parseSwiftResolved,
|
|
100
104
|
parseYarnLock,
|
|
105
|
+
pnpmMetadata,
|
|
101
106
|
readZipEntry,
|
|
102
107
|
splitOutputByGradleProjects,
|
|
103
108
|
toGemModuleNames,
|
|
@@ -1634,6 +1639,64 @@ test("parse go mod why dependencies", () => {
|
|
|
1634
1639
|
expect(pkg_name).toBeUndefined();
|
|
1635
1640
|
});
|
|
1636
1641
|
|
|
1642
|
+
test("multimodule go.mod file ordering", async () => {
|
|
1643
|
+
// Test that simulates the file ordering logic from createGoBom
|
|
1644
|
+
const mockPath = "/workspace/project";
|
|
1645
|
+
const mockGomodFiles = [
|
|
1646
|
+
"/workspace/project/deep/nested/go.mod",
|
|
1647
|
+
"/workspace/project/go.mod",
|
|
1648
|
+
"/workspace/project/submodule/go.mod",
|
|
1649
|
+
];
|
|
1650
|
+
|
|
1651
|
+
// Sort files by depth (shallowest first) - this is the fix we implemented
|
|
1652
|
+
const sortedFiles = mockGomodFiles.sort((a, b) => {
|
|
1653
|
+
const relativePathA = a.replace(`${mockPath}/`, "");
|
|
1654
|
+
const relativePathB = b.replace(`${mockPath}/`, "");
|
|
1655
|
+
const depthA = relativePathA.split("/").length;
|
|
1656
|
+
const depthB = relativePathB.split("/").length;
|
|
1657
|
+
return depthA - depthB;
|
|
1658
|
+
});
|
|
1659
|
+
|
|
1660
|
+
// The root go.mod should be first (shallowest)
|
|
1661
|
+
expect(sortedFiles[0]).toEqual("/workspace/project/go.mod");
|
|
1662
|
+
expect(sortedFiles[1]).toEqual("/workspace/project/submodule/go.mod");
|
|
1663
|
+
expect(sortedFiles[2]).toEqual("/workspace/project/deep/nested/go.mod");
|
|
1664
|
+
});
|
|
1665
|
+
|
|
1666
|
+
test("parseGoModData for multiple modules with root priority", async () => {
|
|
1667
|
+
// Test parsing multiple go.mod files to ensure proper component hierarchy
|
|
1668
|
+
const rootModData = readFileSync("./test/data/multimodule-root.mod", {
|
|
1669
|
+
encoding: "utf-8",
|
|
1670
|
+
});
|
|
1671
|
+
const subModData = readFileSync("./test/data/multimodule-sub.mod", {
|
|
1672
|
+
encoding: "utf-8",
|
|
1673
|
+
});
|
|
1674
|
+
const deepModData = readFileSync("./test/data/multimodule-deep.mod", {
|
|
1675
|
+
encoding: "utf-8",
|
|
1676
|
+
});
|
|
1677
|
+
|
|
1678
|
+
const rootResult = await parseGoModData(rootModData, {});
|
|
1679
|
+
const subResult = await parseGoModData(subModData, {});
|
|
1680
|
+
const deepResult = await parseGoModData(deepModData, {});
|
|
1681
|
+
|
|
1682
|
+
// Root module should be identified correctly
|
|
1683
|
+
expect(rootResult.parentComponent.name).toEqual(
|
|
1684
|
+
"github.com/example/root-project",
|
|
1685
|
+
);
|
|
1686
|
+
expect(rootResult.parentComponent.type).toEqual("application");
|
|
1687
|
+
|
|
1688
|
+
// Sub modules should also be parsed correctly
|
|
1689
|
+
expect(subResult.parentComponent.name).toEqual(
|
|
1690
|
+
"github.com/example/root-project/submodule",
|
|
1691
|
+
);
|
|
1692
|
+
expect(deepResult.parentComponent.name).toEqual(
|
|
1693
|
+
"github.com/example/root-project/deep/nested",
|
|
1694
|
+
);
|
|
1695
|
+
|
|
1696
|
+
// In the fixed logic, the root should take priority over sub-modules
|
|
1697
|
+
// This test verifies the parsing works correctly for each individual module
|
|
1698
|
+
}, 10000);
|
|
1699
|
+
|
|
1637
1700
|
test("parseGopkgData", async () => {
|
|
1638
1701
|
let dep_list = await parseGopkgData(null);
|
|
1639
1702
|
expect(dep_list).toEqual([]);
|
|
@@ -2197,7 +2260,7 @@ test("parse cabal freeze", () => {
|
|
|
2197
2260
|
test("parse conan data", () => {
|
|
2198
2261
|
expect(parseConanLockData(null)).toEqual([]);
|
|
2199
2262
|
let dep_list = parseConanLockData(
|
|
2200
|
-
readFileSync("./test/data/conan.lock", { encoding: "utf-8" }),
|
|
2263
|
+
readFileSync("./test/data/conan-v1.lock", { encoding: "utf-8" }),
|
|
2201
2264
|
);
|
|
2202
2265
|
expect(dep_list.length).toEqual(3);
|
|
2203
2266
|
expect(dep_list[0]).toEqual({
|
|
@@ -2206,6 +2269,16 @@ test("parse conan data", () => {
|
|
|
2206
2269
|
"bom-ref": "pkg:conan/zstd@1.4.4",
|
|
2207
2270
|
purl: "pkg:conan/zstd@1.4.4",
|
|
2208
2271
|
});
|
|
2272
|
+
dep_list = parseConanLockData(
|
|
2273
|
+
readFileSync("./test/data/conan-v2.lock", { encoding: "utf-8" }),
|
|
2274
|
+
);
|
|
2275
|
+
expect(dep_list.length).toEqual(2);
|
|
2276
|
+
expect(dep_list[0]).toEqual({
|
|
2277
|
+
name: "opensta",
|
|
2278
|
+
version: "4.0.0",
|
|
2279
|
+
"bom-ref": "pkg:conan/opensta@4.0.0?rrev=765a7eed989e624c762a73291d712b14",
|
|
2280
|
+
purl: "pkg:conan/opensta@4.0.0?rrev=765a7eed989e624c762a73291d712b14",
|
|
2281
|
+
});
|
|
2209
2282
|
dep_list = parseConanData(
|
|
2210
2283
|
readFileSync("./test/data/conanfile.txt", { encoding: "utf-8" }),
|
|
2211
2284
|
);
|
|
@@ -3844,6 +3917,149 @@ test("parsePnpmLock", async () => {
|
|
|
3844
3917
|
expect(parsedList.dependenciesList.length).toEqual(1189);
|
|
3845
3918
|
});
|
|
3846
3919
|
|
|
3920
|
+
test("findPnpmPackagePath", () => {
|
|
3921
|
+
// Test with non-existent base directory
|
|
3922
|
+
expect(
|
|
3923
|
+
findPnpmPackagePath("/nonexistent", "test-package", "1.0.0"),
|
|
3924
|
+
).toBeNull();
|
|
3925
|
+
|
|
3926
|
+
// Test with null/undefined inputs
|
|
3927
|
+
expect(findPnpmPackagePath(null, "test-package", "1.0.0")).toBeNull();
|
|
3928
|
+
expect(findPnpmPackagePath("/tmp", null, "1.0.0")).toBeNull();
|
|
3929
|
+
expect(findPnpmPackagePath("/tmp", "", "1.0.0")).toBeNull();
|
|
3930
|
+
|
|
3931
|
+
// Test with actual cdxgen project structure - should find packages in node_modules
|
|
3932
|
+
const packagePath = findPnpmPackagePath(".", "chalk", "4.1.2");
|
|
3933
|
+
if (packagePath) {
|
|
3934
|
+
expect(packagePath).toMatch(/node_modules.*chalk/);
|
|
3935
|
+
// Verify package.json exists at the found path
|
|
3936
|
+
expect(existsSync(path.join(packagePath, "package.json"))).toBe(true);
|
|
3937
|
+
}
|
|
3938
|
+
|
|
3939
|
+
// Test with scoped package
|
|
3940
|
+
const scopedPackagePath = findPnpmPackagePath(".", "@babel/core", "7.22.5");
|
|
3941
|
+
if (scopedPackagePath) {
|
|
3942
|
+
expect(scopedPackagePath).toMatch(/node_modules.*@babel.*core/);
|
|
3943
|
+
}
|
|
3944
|
+
});
|
|
3945
|
+
|
|
3946
|
+
test("pnpmMetadata enhancement", async () => {
|
|
3947
|
+
// Test with empty/null inputs
|
|
3948
|
+
expect(await pnpmMetadata([], "./pnpm-lock.yaml")).toEqual([]);
|
|
3949
|
+
expect(await pnpmMetadata(null, "./pnpm-lock.yaml")).toEqual(null);
|
|
3950
|
+
expect(await pnpmMetadata(undefined, "./pnpm-lock.yaml")).toEqual(undefined);
|
|
3951
|
+
|
|
3952
|
+
// Test with non-existent lockfile path
|
|
3953
|
+
const mockPkgList = [
|
|
3954
|
+
{
|
|
3955
|
+
name: "test-package",
|
|
3956
|
+
version: "1.0.0",
|
|
3957
|
+
properties: [],
|
|
3958
|
+
},
|
|
3959
|
+
];
|
|
3960
|
+
const result = await pnpmMetadata(mockPkgList, "/nonexistent/pnpm-lock.yaml");
|
|
3961
|
+
expect(result).toEqual(mockPkgList);
|
|
3962
|
+
expect(result[0].description).toBeUndefined();
|
|
3963
|
+
|
|
3964
|
+
// Test with actual project that has node_modules
|
|
3965
|
+
const testPkgList = [
|
|
3966
|
+
{
|
|
3967
|
+
name: "chalk",
|
|
3968
|
+
version: "4.1.2",
|
|
3969
|
+
properties: [],
|
|
3970
|
+
},
|
|
3971
|
+
{
|
|
3972
|
+
name: "nonexistent-package",
|
|
3973
|
+
version: "1.0.0",
|
|
3974
|
+
properties: [],
|
|
3975
|
+
},
|
|
3976
|
+
];
|
|
3977
|
+
|
|
3978
|
+
const enhancedResult = await pnpmMetadata(testPkgList, "./pnpm-lock.yaml");
|
|
3979
|
+
|
|
3980
|
+
const chalkPkg = enhancedResult.find((p) => p.name === "chalk");
|
|
3981
|
+
if (chalkPkg) {
|
|
3982
|
+
const localPath = chalkPkg.properties?.find(
|
|
3983
|
+
(p) => p.name === "LocalNodeModulesPath",
|
|
3984
|
+
);
|
|
3985
|
+
if (localPath) {
|
|
3986
|
+
expect(localPath.value).toMatch(/node_modules.*chalk/);
|
|
3987
|
+
expect(chalkPkg.description).toBeDefined();
|
|
3988
|
+
expect(chalkPkg.license).toBeDefined();
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3991
|
+
|
|
3992
|
+
// Non-existent package should remain unchanged
|
|
3993
|
+
const nonExistentPkg = enhancedResult.find(
|
|
3994
|
+
(p) => p.name === "nonexistent-package",
|
|
3995
|
+
);
|
|
3996
|
+
expect(nonExistentPkg.description).toBeUndefined();
|
|
3997
|
+
expect(
|
|
3998
|
+
nonExistentPkg.properties.find((p) => p.name === "LocalNodeModulesPath"),
|
|
3999
|
+
).toBeUndefined();
|
|
4000
|
+
});
|
|
4001
|
+
|
|
4002
|
+
test("pnpmMetadata preserves existing metadata", async () => {
|
|
4003
|
+
const testPkgList = [
|
|
4004
|
+
{
|
|
4005
|
+
name: "test-package",
|
|
4006
|
+
version: "1.0.0",
|
|
4007
|
+
description: "Existing description",
|
|
4008
|
+
author: "Existing author",
|
|
4009
|
+
license: "Existing license",
|
|
4010
|
+
properties: [],
|
|
4011
|
+
},
|
|
4012
|
+
];
|
|
4013
|
+
|
|
4014
|
+
const result = await pnpmMetadata(testPkgList, "./pnpm-lock.yaml");
|
|
4015
|
+
|
|
4016
|
+
// Should preserve existing metadata
|
|
4017
|
+
expect(result[0].description).toBe("Existing description");
|
|
4018
|
+
expect(result[0].author).toBe("Existing author");
|
|
4019
|
+
expect(result[0].license).toBe("Existing license");
|
|
4020
|
+
});
|
|
4021
|
+
|
|
4022
|
+
test("pnpmMetadata with scoped packages", async () => {
|
|
4023
|
+
const testPkgList = [
|
|
4024
|
+
{
|
|
4025
|
+
name: "@babel/core",
|
|
4026
|
+
version: "7.22.5",
|
|
4027
|
+
properties: [],
|
|
4028
|
+
},
|
|
4029
|
+
];
|
|
4030
|
+
|
|
4031
|
+
const result = await pnpmMetadata(testPkgList, "./pnpm-lock.yaml");
|
|
4032
|
+
|
|
4033
|
+
// Check if scoped package was processed
|
|
4034
|
+
const babelPkg = result.find((p) => p.name === "@babel/core");
|
|
4035
|
+
expect(babelPkg).toBeDefined();
|
|
4036
|
+
expect(babelPkg.name).toBe("@babel/core");
|
|
4037
|
+
});
|
|
4038
|
+
|
|
4039
|
+
test("pnpmMetadata integration with parsePnpmLock", async () => {
|
|
4040
|
+
// Test that the integration works by parsing a real pnpm lock file
|
|
4041
|
+
const parsedList = await parsePnpmLock("./pnpm-lock.yaml");
|
|
4042
|
+
|
|
4043
|
+
// Check that some packages have been enhanced with LocalNodeModulesPath
|
|
4044
|
+
const enhancedPackages = parsedList.pkgList.filter((pkg) =>
|
|
4045
|
+
pkg.properties?.some((p) => p.name === "LocalNodeModulesPath"),
|
|
4046
|
+
);
|
|
4047
|
+
|
|
4048
|
+
if (enhancedPackages.length > 0) {
|
|
4049
|
+
expect(enhancedPackages.length).toBeGreaterThan(0);
|
|
4050
|
+
|
|
4051
|
+
const examplePkg = enhancedPackages[0];
|
|
4052
|
+
expect(
|
|
4053
|
+
examplePkg.properties.find((p) => p.name === "LocalNodeModulesPath"),
|
|
4054
|
+
).toBeDefined();
|
|
4055
|
+
|
|
4056
|
+
const packagesWithMetadata = enhancedPackages.filter(
|
|
4057
|
+
(pkg) => pkg.description || pkg.license || pkg.author,
|
|
4058
|
+
);
|
|
4059
|
+
expect(packagesWithMetadata.length).toBeGreaterThan(0);
|
|
4060
|
+
}
|
|
4061
|
+
});
|
|
4062
|
+
|
|
3847
4063
|
test("parseYarnLock", async () => {
|
|
3848
4064
|
let identMap = yarnLockToIdentMap(readFileSync("./test/yarn.lock", "utf8"));
|
|
3849
4065
|
expect(Object.keys(identMap).length).toEqual(62);
|
|
@@ -4466,6 +4682,14 @@ test("parseComposerLock", () => {
|
|
|
4466
4682
|
});
|
|
4467
4683
|
});
|
|
4468
4684
|
|
|
4685
|
+
test("parseComposerJson", () => {
|
|
4686
|
+
let retMap = parseComposerJson("./test/data/composer.json");
|
|
4687
|
+
expect(Object.keys(retMap.rootRequires).length).toEqual(1);
|
|
4688
|
+
|
|
4689
|
+
retMap = parseComposerJson("./test/data/composer-2.json");
|
|
4690
|
+
expect(Object.keys(retMap.rootRequires).length).toEqual(31);
|
|
4691
|
+
});
|
|
4692
|
+
|
|
4469
4693
|
test("parseGemfileLockData", async () => {
|
|
4470
4694
|
let retMap = await parseGemfileLockData(
|
|
4471
4695
|
readFileSync("./test/data/Gemfile.lock", { encoding: "utf-8" }),
|
|
@@ -4780,37 +5004,58 @@ test("parseGemspecData", async () => {
|
|
|
4780
5004
|
});
|
|
4781
5005
|
|
|
4782
5006
|
test("parse requirements.txt", async () => {
|
|
4783
|
-
let deps = await parseReqFile(
|
|
4784
|
-
readFileSync("./test/data/requirements.comments.txt", {
|
|
4785
|
-
encoding: "utf-8",
|
|
4786
|
-
}),
|
|
4787
|
-
false,
|
|
4788
|
-
);
|
|
5007
|
+
let deps = await parseReqFile("./test/data/requirements.comments.txt", false);
|
|
4789
5008
|
expect(deps.length).toEqual(31);
|
|
4790
|
-
deps = await parseReqFile(
|
|
4791
|
-
readFileSync("./test/data/requirements.freeze.txt", {
|
|
4792
|
-
encoding: "utf-8",
|
|
4793
|
-
}),
|
|
4794
|
-
false,
|
|
4795
|
-
);
|
|
5009
|
+
deps = await parseReqFile("./test/data/requirements.freeze.txt", false);
|
|
4796
5010
|
expect(deps.length).toEqual(113);
|
|
4797
5011
|
expect(deps[0]).toEqual({
|
|
4798
5012
|
name: "elasticsearch",
|
|
4799
5013
|
version: "8.6.2",
|
|
4800
5014
|
scope: "required",
|
|
5015
|
+
properties: [
|
|
5016
|
+
{
|
|
5017
|
+
name: "SrcFile",
|
|
5018
|
+
value: "./test/data/requirements.freeze.txt",
|
|
5019
|
+
},
|
|
5020
|
+
],
|
|
5021
|
+
evidence: {
|
|
5022
|
+
identity: {
|
|
5023
|
+
field: "purl",
|
|
5024
|
+
confidence: 0.5,
|
|
5025
|
+
methods: [
|
|
5026
|
+
{
|
|
5027
|
+
technique: "manifest-analysis",
|
|
5028
|
+
confidence: 0.5,
|
|
5029
|
+
value: "./test/data/requirements.freeze.txt",
|
|
5030
|
+
},
|
|
5031
|
+
],
|
|
5032
|
+
},
|
|
5033
|
+
},
|
|
4801
5034
|
});
|
|
4802
|
-
deps = await parseReqFile(
|
|
4803
|
-
readFileSync("./test/data/chen-science-requirements.txt", {
|
|
4804
|
-
encoding: "utf-8",
|
|
4805
|
-
}),
|
|
4806
|
-
false,
|
|
4807
|
-
);
|
|
5035
|
+
deps = await parseReqFile("./test/data/chen-science-requirements.txt", false);
|
|
4808
5036
|
expect(deps.length).toEqual(87);
|
|
4809
5037
|
expect(deps[0]).toEqual({
|
|
4810
5038
|
name: "aiofiles",
|
|
4811
5039
|
version: "23.2.1",
|
|
4812
5040
|
scope: undefined,
|
|
5041
|
+
evidence: {
|
|
5042
|
+
identity: {
|
|
5043
|
+
field: "purl",
|
|
5044
|
+
confidence: 0.5,
|
|
5045
|
+
methods: [
|
|
5046
|
+
{
|
|
5047
|
+
technique: "manifest-analysis",
|
|
5048
|
+
confidence: 0.5,
|
|
5049
|
+
value: "./test/data/chen-science-requirements.txt",
|
|
5050
|
+
},
|
|
5051
|
+
],
|
|
5052
|
+
},
|
|
5053
|
+
},
|
|
4813
5054
|
properties: [
|
|
5055
|
+
{
|
|
5056
|
+
name: "SrcFile",
|
|
5057
|
+
value: "./test/data/chen-science-requirements.txt",
|
|
5058
|
+
},
|
|
4814
5059
|
{
|
|
4815
5060
|
name: "cdx:pip:markers",
|
|
4816
5061
|
value:
|
|
@@ -4819,9 +5064,7 @@ test("parse requirements.txt", async () => {
|
|
|
4819
5064
|
],
|
|
4820
5065
|
});
|
|
4821
5066
|
deps = await parseReqFile(
|
|
4822
|
-
|
|
4823
|
-
encoding: "utf-8",
|
|
4824
|
-
}),
|
|
5067
|
+
"./test/data/requirements-lock.linux_py3.txt",
|
|
4825
5068
|
false,
|
|
4826
5069
|
);
|
|
4827
5070
|
expect(deps.length).toEqual(375);
|
|
@@ -4829,11 +5072,49 @@ test("parse requirements.txt", async () => {
|
|
|
4829
5072
|
name: "adal",
|
|
4830
5073
|
scope: undefined,
|
|
4831
5074
|
version: "1.2.2",
|
|
5075
|
+
properties: [
|
|
5076
|
+
{
|
|
5077
|
+
name: "SrcFile",
|
|
5078
|
+
value: "./test/data/requirements-lock.linux_py3.txt",
|
|
5079
|
+
},
|
|
5080
|
+
],
|
|
5081
|
+
evidence: {
|
|
5082
|
+
identity: {
|
|
5083
|
+
field: "purl",
|
|
5084
|
+
confidence: 0.5,
|
|
5085
|
+
methods: [
|
|
5086
|
+
{
|
|
5087
|
+
technique: "manifest-analysis",
|
|
5088
|
+
confidence: 0.5,
|
|
5089
|
+
value: "./test/data/requirements-lock.linux_py3.txt",
|
|
5090
|
+
},
|
|
5091
|
+
],
|
|
5092
|
+
},
|
|
5093
|
+
},
|
|
4832
5094
|
});
|
|
4833
5095
|
expect(deps[deps.length - 1]).toEqual({
|
|
4834
5096
|
name: "zipp",
|
|
4835
5097
|
scope: undefined,
|
|
4836
5098
|
version: "0.6.0",
|
|
5099
|
+
properties: [
|
|
5100
|
+
{
|
|
5101
|
+
name: "SrcFile",
|
|
5102
|
+
value: "./test/data/requirements-lock.linux_py3.txt",
|
|
5103
|
+
},
|
|
5104
|
+
],
|
|
5105
|
+
evidence: {
|
|
5106
|
+
identity: {
|
|
5107
|
+
field: "purl",
|
|
5108
|
+
confidence: 0.5,
|
|
5109
|
+
methods: [
|
|
5110
|
+
{
|
|
5111
|
+
technique: "manifest-analysis",
|
|
5112
|
+
confidence: 0.5,
|
|
5113
|
+
value: "./test/data/requirements-lock.linux_py3.txt",
|
|
5114
|
+
},
|
|
5115
|
+
],
|
|
5116
|
+
},
|
|
5117
|
+
},
|
|
4837
5118
|
});
|
|
4838
5119
|
});
|
|
4839
5120
|
|
|
@@ -6266,3 +6547,143 @@ test("parseMillDependency test", () => {
|
|
|
6266
6547
|
expect(dependencies.size).toEqual(15);
|
|
6267
6548
|
expect(relations.size).toEqual(15);
|
|
6268
6549
|
});
|
|
6550
|
+
|
|
6551
|
+
test("parse flake.nix file", () => {
|
|
6552
|
+
const result = parseFlakeNix("./test/data/test-flake.nix");
|
|
6553
|
+
expect(result.pkgList).toBeDefined();
|
|
6554
|
+
expect(result.dependencies).toBeDefined();
|
|
6555
|
+
expect(result.pkgList.length).toEqual(3);
|
|
6556
|
+
|
|
6557
|
+
// Check nixpkgs input
|
|
6558
|
+
const nixpkgs = result.pkgList.find((pkg) => pkg.name === "nixpkgs");
|
|
6559
|
+
expect(nixpkgs).toBeDefined();
|
|
6560
|
+
expect(nixpkgs.version).toEqual("latest");
|
|
6561
|
+
expect(nixpkgs.purl).toEqual("pkg:nix/nixpkgs@latest");
|
|
6562
|
+
expect(nixpkgs["bom-ref"]).toEqual("pkg:nix/nixpkgs@latest");
|
|
6563
|
+
expect(nixpkgs.scope).toEqual("required");
|
|
6564
|
+
expect(nixpkgs.description).toEqual("Nix flake input: nixpkgs");
|
|
6565
|
+
expect(nixpkgs.properties).toBeDefined();
|
|
6566
|
+
|
|
6567
|
+
// Check properties
|
|
6568
|
+
const srcFileProperty = nixpkgs.properties.find((p) => p.name === "SrcFile");
|
|
6569
|
+
expect(srcFileProperty.value).toEqual("./test/data/test-flake.nix");
|
|
6570
|
+
|
|
6571
|
+
const urlProperty = nixpkgs.properties.find(
|
|
6572
|
+
(p) => p.name === "cdx:nix:input_url",
|
|
6573
|
+
);
|
|
6574
|
+
expect(urlProperty.value).toEqual("github:NixOS/nixpkgs/release-23.11");
|
|
6575
|
+
|
|
6576
|
+
// Check flake-utils input
|
|
6577
|
+
const flakeUtils = result.pkgList.find((pkg) => pkg.name === "flake-utils");
|
|
6578
|
+
expect(flakeUtils).toBeDefined();
|
|
6579
|
+
expect(flakeUtils.version).toEqual("latest");
|
|
6580
|
+
|
|
6581
|
+
// Check rust-overlay input
|
|
6582
|
+
const rustOverlay = result.pkgList.find((pkg) => pkg.name === "rust-overlay");
|
|
6583
|
+
expect(rustOverlay).toBeDefined();
|
|
6584
|
+
expect(rustOverlay.version).toEqual("latest");
|
|
6585
|
+
|
|
6586
|
+
const rustOverlayUrlProperty = rustOverlay.properties.find(
|
|
6587
|
+
(p) => p.name === "cdx:nix:input_url",
|
|
6588
|
+
);
|
|
6589
|
+
expect(rustOverlayUrlProperty.value).toEqual("github:oxalica/rust-overlay");
|
|
6590
|
+
|
|
6591
|
+
// Check evidence
|
|
6592
|
+
expect(nixpkgs.evidence).toBeDefined();
|
|
6593
|
+
expect(nixpkgs.evidence.identity.field).toEqual("purl");
|
|
6594
|
+
expect(nixpkgs.evidence.identity.confidence).toEqual(0.8);
|
|
6595
|
+
expect(nixpkgs.evidence.identity.methods[0].technique).toEqual(
|
|
6596
|
+
"manifest-analysis",
|
|
6597
|
+
);
|
|
6598
|
+
});
|
|
6599
|
+
|
|
6600
|
+
test("parse flake.lock file", () => {
|
|
6601
|
+
const result = parseFlakeLock("./test/data/test-flake.lock");
|
|
6602
|
+
expect(result.pkgList).toBeDefined();
|
|
6603
|
+
expect(result.dependencies).toBeDefined();
|
|
6604
|
+
expect(result.pkgList.length).toEqual(4);
|
|
6605
|
+
|
|
6606
|
+
// Check nixpkgs package
|
|
6607
|
+
const nixpkgs = result.pkgList.find((pkg) => pkg.name === "nixpkgs");
|
|
6608
|
+
expect(nixpkgs).toBeDefined();
|
|
6609
|
+
expect(nixpkgs.version).toEqual("bd645e8"); // Short commit hash
|
|
6610
|
+
expect(nixpkgs.purl).toEqual("pkg:nix/nixpkgs@bd645e8");
|
|
6611
|
+
expect(nixpkgs["bom-ref"]).toEqual("pkg:nix/nixpkgs@bd645e8");
|
|
6612
|
+
expect(nixpkgs.scope).toEqual("required");
|
|
6613
|
+
expect(nixpkgs.description).toEqual("Nix flake dependency: nixpkgs");
|
|
6614
|
+
|
|
6615
|
+
// Check properties for nixpkgs
|
|
6616
|
+
const nixpkgsProperties = nixpkgs.properties;
|
|
6617
|
+
expect(nixpkgsProperties).toBeDefined();
|
|
6618
|
+
|
|
6619
|
+
const srcFileProperty = nixpkgsProperties.find((p) => p.name === "SrcFile");
|
|
6620
|
+
expect(srcFileProperty.value).toEqual("./test/data/test-flake.lock");
|
|
6621
|
+
|
|
6622
|
+
const narHashProperty = nixpkgsProperties.find(
|
|
6623
|
+
(p) => p.name === "cdx:nix:nar_hash",
|
|
6624
|
+
);
|
|
6625
|
+
expect(narHashProperty.value).toEqual(
|
|
6626
|
+
"sha256-RtDKd8Mynhe5CFnVT8s0/0yqtWFMM9LmCzXv/YKxnq4=",
|
|
6627
|
+
);
|
|
6628
|
+
|
|
6629
|
+
const lastModifiedProperty = nixpkgsProperties.find(
|
|
6630
|
+
(p) => p.name === "cdx:nix:last_modified",
|
|
6631
|
+
);
|
|
6632
|
+
expect(lastModifiedProperty.value).toEqual("1704194953");
|
|
6633
|
+
|
|
6634
|
+
const revisionProperty = nixpkgsProperties.find(
|
|
6635
|
+
(p) => p.name === "cdx:nix:revision",
|
|
6636
|
+
);
|
|
6637
|
+
expect(revisionProperty.value).toEqual(
|
|
6638
|
+
"bd645e8668ec6612439a9ee7e71f7eac4099d4f6",
|
|
6639
|
+
);
|
|
6640
|
+
|
|
6641
|
+
// Check flake-utils package
|
|
6642
|
+
const flakeUtils = result.pkgList.find((pkg) => pkg.name === "flake-utils");
|
|
6643
|
+
expect(flakeUtils).toBeDefined();
|
|
6644
|
+
expect(flakeUtils.version).toEqual("1ef2e67");
|
|
6645
|
+
|
|
6646
|
+
// Check rust-overlay package
|
|
6647
|
+
const rustOverlay = result.pkgList.find((pkg) => pkg.name === "rust-overlay");
|
|
6648
|
+
expect(rustOverlay).toBeDefined();
|
|
6649
|
+
expect(rustOverlay.version).toEqual("9a8a835");
|
|
6650
|
+
|
|
6651
|
+
// Check systems package
|
|
6652
|
+
const systems = result.pkgList.find((pkg) => pkg.name === "systems");
|
|
6653
|
+
expect(systems).toBeDefined();
|
|
6654
|
+
expect(systems.version).toEqual("da67096");
|
|
6655
|
+
|
|
6656
|
+
// Check dependencies
|
|
6657
|
+
expect(result.dependencies.length).toEqual(1);
|
|
6658
|
+
const rootDep = result.dependencies[0];
|
|
6659
|
+
expect(rootDep.ref).toEqual("pkg:nix/flake@latest");
|
|
6660
|
+
expect(rootDep.dependsOn).toBeDefined();
|
|
6661
|
+
expect(rootDep.dependsOn.length).toEqual(3); // flake-utils, nixpkgs, rust-overlay
|
|
6662
|
+
expect(rootDep.dependsOn).toContain("pkg:nix/flake-utils@1ef2e67");
|
|
6663
|
+
expect(rootDep.dependsOn).toContain("pkg:nix/nixpkgs@bd645e8");
|
|
6664
|
+
expect(rootDep.dependsOn).toContain("pkg:nix/rust-overlay@9a8a835");
|
|
6665
|
+
|
|
6666
|
+
// Check evidence
|
|
6667
|
+
expect(nixpkgs.evidence).toBeDefined();
|
|
6668
|
+
expect(nixpkgs.evidence.identity.field).toEqual("purl");
|
|
6669
|
+
expect(nixpkgs.evidence.identity.confidence).toEqual(1.0);
|
|
6670
|
+
expect(nixpkgs.evidence.identity.methods[0].technique).toEqual(
|
|
6671
|
+
"manifest-analysis",
|
|
6672
|
+
);
|
|
6673
|
+
});
|
|
6674
|
+
|
|
6675
|
+
test("parse flake.nix file with missing file", () => {
|
|
6676
|
+
const result = parseFlakeNix("./test/data/missing-flake.nix");
|
|
6677
|
+
expect(result.pkgList).toBeDefined();
|
|
6678
|
+
expect(result.dependencies).toBeDefined();
|
|
6679
|
+
expect(result.pkgList.length).toEqual(0);
|
|
6680
|
+
expect(result.dependencies.length).toEqual(0);
|
|
6681
|
+
});
|
|
6682
|
+
|
|
6683
|
+
test("parse flake.lock file with missing file", () => {
|
|
6684
|
+
const result = parseFlakeLock("./test/data/missing-flake.lock");
|
|
6685
|
+
expect(result.pkgList).toBeDefined();
|
|
6686
|
+
expect(result.dependencies).toBeDefined();
|
|
6687
|
+
expect(result.pkgList.length).toEqual(0);
|
|
6688
|
+
expect(result.dependencies.length).toEqual(0);
|
|
6689
|
+
});
|
package/lib/helpers/validator.js
CHANGED
|
@@ -185,6 +185,16 @@ export const validatePurls = (bomJson) => {
|
|
|
185
185
|
`purl does not include namespace but includes encoded slash in name for npm type. ${comp.purl}`,
|
|
186
186
|
);
|
|
187
187
|
}
|
|
188
|
+
// Catch the trivy version hack that removes the epoch from version
|
|
189
|
+
const qualifiers = purlObj.qualifiers || {};
|
|
190
|
+
if (
|
|
191
|
+
qualifiers.epoch &&
|
|
192
|
+
!comp.version.startsWith(`${qualifiers.epoch}:`)
|
|
193
|
+
) {
|
|
194
|
+
errorList.push(
|
|
195
|
+
`'${comp.name}' version '${comp.version}' doesn't include epoch '${qualifiers.epoch}'.`,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
188
198
|
} catch (_ex) {
|
|
189
199
|
errorList.push(`Invalid purl ${comp.purl}`);
|
|
190
200
|
}
|