@cyclonedx/cdxgen 12.3.1 → 12.3.3
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 +6 -0
- package/bin/cdxgen.js +1 -2
- package/data/rules/ai-agent-governance.yaml +43 -0
- package/data/rules/ci-permissions.yaml +132 -0
- package/data/rules/dependency-sources.yaml +65 -5
- package/data/rules/mcp-servers.yaml +36 -2
- package/data/rules/package-integrity.yaml +22 -0
- package/lib/cli/index.js +436 -56
- package/lib/cli/index.poku.js +875 -2
- package/lib/helpers/agentFormulationParser.js +10 -3
- package/lib/helpers/agentFormulationParser.poku.js +42 -0
- package/lib/helpers/aiInventory.js +262 -0
- package/lib/helpers/aiInventory.poku.js +111 -0
- package/lib/helpers/analyzer.js +413 -54
- package/lib/helpers/analyzer.poku.js +117 -0
- package/lib/helpers/auditCategories.js +76 -0
- package/lib/helpers/chromextutils.js +25 -3
- package/lib/helpers/chromextutils.poku.js +68 -0
- package/lib/helpers/ciParsers/githubActions.js +79 -0
- package/lib/helpers/ciParsers/githubActions.poku.js +103 -0
- package/lib/helpers/communityAiConfigParser.js +15 -5
- package/lib/helpers/communityAiConfigParser.poku.js +71 -0
- package/lib/helpers/depsUtils.js +5 -0
- package/lib/helpers/depsUtils.poku.js +55 -0
- package/lib/helpers/display.js +50 -24
- package/lib/helpers/display.poku.js +70 -58
- package/lib/helpers/formulationParsers.js +26 -6
- package/lib/helpers/jsonLike.js +21 -20
- package/lib/helpers/jsonLike.poku.js +34 -0
- package/lib/helpers/mcpConfigParser.js +32 -16
- package/lib/helpers/mcpConfigParser.poku.js +104 -0
- package/lib/helpers/mcpDiscovery.js +13 -23
- package/lib/helpers/mcpDiscovery.poku.js +21 -0
- package/lib/helpers/propertySanitizer.js +121 -0
- package/lib/helpers/utils.js +953 -41
- package/lib/helpers/utils.poku.js +901 -1
- package/lib/managers/binary.js +16 -0
- package/lib/managers/binary.poku.js +1 -0
- package/lib/managers/docker.js +240 -16
- package/lib/managers/docker.poku.js +1142 -2
- package/lib/server/server.js +7 -4
- package/lib/server/server.poku.js +36 -1
- package/lib/stages/postgen/annotator.js +2 -1
- package/lib/stages/postgen/annotator.poku.js +15 -0
- package/lib/stages/postgen/auditBom.js +12 -6
- package/lib/stages/postgen/auditBom.poku.js +755 -6
- package/lib/stages/postgen/postgen.js +229 -6
- package/lib/stages/postgen/postgen.poku.js +180 -0
- package/package.json +2 -1
- package/types/lib/cli/index.d.ts +1 -0
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/helpers/agentFormulationParser.d.ts +19 -0
- package/types/lib/helpers/agentFormulationParser.d.ts.map +1 -0
- package/types/lib/helpers/aiInventory.d.ts +23 -0
- package/types/lib/helpers/aiInventory.d.ts.map +1 -0
- package/types/lib/helpers/analyzer.d.ts +5 -0
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/auditCategories.d.ts +12 -0
- package/types/lib/helpers/auditCategories.d.ts.map +1 -0
- package/types/lib/helpers/chromextutils.d.ts.map +1 -1
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
- package/types/lib/helpers/communityAiConfigParser.d.ts +29 -0
- package/types/lib/helpers/communityAiConfigParser.d.ts.map +1 -0
- package/types/lib/helpers/depsUtils.d.ts.map +1 -1
- package/types/lib/helpers/display.d.ts +1 -0
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/formulationParsers.d.ts.map +1 -1
- package/types/lib/helpers/jsonLike.d.ts +4 -0
- package/types/lib/helpers/jsonLike.d.ts.map +1 -0
- package/types/lib/helpers/mcp.d.ts +29 -0
- package/types/lib/helpers/mcp.d.ts.map +1 -0
- package/types/lib/helpers/mcpConfigParser.d.ts +30 -0
- package/types/lib/helpers/mcpConfigParser.d.ts.map +1 -0
- package/types/lib/helpers/mcpDiscovery.d.ts +5 -0
- package/types/lib/helpers/mcpDiscovery.d.ts.map +1 -0
- package/types/lib/helpers/propertySanitizer.d.ts +3 -0
- package/types/lib/helpers/propertySanitizer.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts +31 -0
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +3 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +1 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
2
|
import {
|
|
3
|
+
chmodSync,
|
|
3
4
|
existsSync,
|
|
4
5
|
mkdirSync,
|
|
5
6
|
mkdtempSync,
|
|
6
7
|
readFileSync,
|
|
7
8
|
rmSync,
|
|
9
|
+
symlinkSync,
|
|
8
10
|
unlinkSync,
|
|
9
11
|
writeFileSync,
|
|
10
12
|
} from "node:fs";
|
|
@@ -21,10 +23,13 @@ import { parse as loadYaml } from "yaml";
|
|
|
21
23
|
|
|
22
24
|
import { validateRefs } from "../validator/bomValidator.js";
|
|
23
25
|
import {
|
|
26
|
+
attachIdentityTools,
|
|
24
27
|
buildObjectForCocoaPod,
|
|
25
28
|
buildObjectForGradleModule,
|
|
29
|
+
collectExecutables,
|
|
26
30
|
convertOSQueryResults,
|
|
27
31
|
encodeForPurl,
|
|
32
|
+
extractToolRefs,
|
|
28
33
|
findLicenseId,
|
|
29
34
|
findPnpmPackagePath,
|
|
30
35
|
getCratesMetadata,
|
|
@@ -59,6 +64,7 @@ import {
|
|
|
59
64
|
parseCmakeDotFile,
|
|
60
65
|
parseCmakeLikeFile,
|
|
61
66
|
parseCocoaDependency,
|
|
67
|
+
parseColliderLockData,
|
|
62
68
|
parseComposerJson,
|
|
63
69
|
parseComposerLock,
|
|
64
70
|
parseConanData,
|
|
@@ -1276,6 +1282,84 @@ it("get py metadata", async () => {
|
|
|
1276
1282
|
]);
|
|
1277
1283
|
}, 240000);
|
|
1278
1284
|
|
|
1285
|
+
it("get py metadata adds distribution external references", async () => {
|
|
1286
|
+
const agentGetStub = sinon.stub().resolves({
|
|
1287
|
+
body: {
|
|
1288
|
+
info: {
|
|
1289
|
+
author: "",
|
|
1290
|
+
author_email: "",
|
|
1291
|
+
classifiers: [],
|
|
1292
|
+
license: "",
|
|
1293
|
+
license_expression: "",
|
|
1294
|
+
name: "requests",
|
|
1295
|
+
summary: "HTTP client",
|
|
1296
|
+
version: "2.31.0",
|
|
1297
|
+
},
|
|
1298
|
+
releases: {
|
|
1299
|
+
"2.31.0": [
|
|
1300
|
+
{
|
|
1301
|
+
digests: { sha256: "abc123" },
|
|
1302
|
+
filename: "requests-2.31.0-py3-none-any.whl",
|
|
1303
|
+
packagetype: "bdist_wheel",
|
|
1304
|
+
url: "https://files.pythonhosted.org/packages/example/requests-2.31.0-py3-none-any.whl",
|
|
1305
|
+
},
|
|
1306
|
+
{
|
|
1307
|
+
digests: { sha256: "def456" },
|
|
1308
|
+
filename: "requests-2.31.0.tar.gz",
|
|
1309
|
+
packagetype: "sdist",
|
|
1310
|
+
url: "https://files.pythonhosted.org/packages/example/requests-2.31.0.tar.gz",
|
|
1311
|
+
},
|
|
1312
|
+
],
|
|
1313
|
+
},
|
|
1314
|
+
},
|
|
1315
|
+
});
|
|
1316
|
+
const { getPyMetadata: mockedGetPyMetadata } = await esmock("./utils.js", {
|
|
1317
|
+
got: {
|
|
1318
|
+
default: {
|
|
1319
|
+
extend: sinon.stub().returns({ get: agentGetStub }),
|
|
1320
|
+
},
|
|
1321
|
+
},
|
|
1322
|
+
});
|
|
1323
|
+
const data = await mockedGetPyMetadata(
|
|
1324
|
+
[
|
|
1325
|
+
{
|
|
1326
|
+
externalReferences: [
|
|
1327
|
+
{
|
|
1328
|
+
type: "website",
|
|
1329
|
+
url: "https://example.com/requests",
|
|
1330
|
+
},
|
|
1331
|
+
],
|
|
1332
|
+
group: "",
|
|
1333
|
+
name: "requests",
|
|
1334
|
+
version: "2.31.0",
|
|
1335
|
+
},
|
|
1336
|
+
],
|
|
1337
|
+
true,
|
|
1338
|
+
);
|
|
1339
|
+
assert.strictEqual(data.length, 1);
|
|
1340
|
+
assert.ok(
|
|
1341
|
+
data[0].externalReferences?.some(
|
|
1342
|
+
(reference) => reference.type === "website",
|
|
1343
|
+
),
|
|
1344
|
+
);
|
|
1345
|
+
assert.ok(
|
|
1346
|
+
data[0].externalReferences?.some(
|
|
1347
|
+
(reference) =>
|
|
1348
|
+
reference.type === "distribution" &&
|
|
1349
|
+
reference.url.endsWith(".whl") &&
|
|
1350
|
+
reference.comment === "requests-2.31.0-py3-none-any.whl",
|
|
1351
|
+
),
|
|
1352
|
+
);
|
|
1353
|
+
assert.ok(
|
|
1354
|
+
data[0].externalReferences?.some(
|
|
1355
|
+
(reference) =>
|
|
1356
|
+
reference.type === "distribution" &&
|
|
1357
|
+
reference.url.endsWith(".tar.gz") &&
|
|
1358
|
+
reference.comment === "requests-2.31.0.tar.gz",
|
|
1359
|
+
),
|
|
1360
|
+
);
|
|
1361
|
+
});
|
|
1362
|
+
|
|
1279
1363
|
it("parseGoModData", async () => {
|
|
1280
1364
|
let retMap = await parseGoModData(null);
|
|
1281
1365
|
assert.deepStrictEqual(retMap, {});
|
|
@@ -2305,6 +2389,49 @@ bindgen = { version = "0.70.0", default-features = false }
|
|
|
2305
2389
|
}
|
|
2306
2390
|
});
|
|
2307
2391
|
|
|
2392
|
+
it("parse cargo toml captures git dependency metadata", async () => {
|
|
2393
|
+
const tmpDir = mkdtempSync(path.join(tmpdir(), "cdxgen-cargo-git-"));
|
|
2394
|
+
const cargoTomlFile = path.join(tmpDir, "Cargo.toml");
|
|
2395
|
+
writeFileSync(
|
|
2396
|
+
cargoTomlFile,
|
|
2397
|
+
`[package]
|
|
2398
|
+
name = "demo"
|
|
2399
|
+
version = "1.0.0"
|
|
2400
|
+
|
|
2401
|
+
[dependencies]
|
|
2402
|
+
git-crate = { git = "https://github.com/acme/git-crate.git", branch = "main" }
|
|
2403
|
+
`,
|
|
2404
|
+
);
|
|
2405
|
+
try {
|
|
2406
|
+
const depList = await parseCargoTomlData(cargoTomlFile);
|
|
2407
|
+
const gitDep = depList.find((pkg) => pkg.name === "git-crate");
|
|
2408
|
+
assert.ok(gitDep);
|
|
2409
|
+
assert.strictEqual(
|
|
2410
|
+
gitDep.version,
|
|
2411
|
+
"git+https://github.com/acme/git-crate.git",
|
|
2412
|
+
);
|
|
2413
|
+
assert.strictEqual(
|
|
2414
|
+
gitDep.properties.find((property) => property.name === "cdx:cargo:git")
|
|
2415
|
+
?.value,
|
|
2416
|
+
"https://github.com/acme/git-crate.git",
|
|
2417
|
+
);
|
|
2418
|
+
assert.strictEqual(
|
|
2419
|
+
gitDep.properties.find(
|
|
2420
|
+
(property) => property.name === "cdx:cargo:gitBranch",
|
|
2421
|
+
)?.value,
|
|
2422
|
+
"main",
|
|
2423
|
+
);
|
|
2424
|
+
assert.strictEqual(
|
|
2425
|
+
gitDep.properties.find(
|
|
2426
|
+
(property) => property.name === "cdx:cargo:dependencyKind",
|
|
2427
|
+
)?.value,
|
|
2428
|
+
"runtime",
|
|
2429
|
+
);
|
|
2430
|
+
} finally {
|
|
2431
|
+
rmSync(tmpDir, { force: true, recursive: true });
|
|
2432
|
+
}
|
|
2433
|
+
});
|
|
2434
|
+
|
|
2308
2435
|
it("parse cargo virtual workspace with inherited package and dependency metadata", async () => {
|
|
2309
2436
|
const workspaceDir = "./test/data/cargo-workspace-repotest";
|
|
2310
2437
|
const workspaceToml = path.join(workspaceDir, "Cargo.toml");
|
|
@@ -2825,6 +2952,221 @@ it("parse conan data", () => {
|
|
|
2825
2952
|
});
|
|
2826
2953
|
});
|
|
2827
2954
|
|
|
2955
|
+
it("parse collider lock data", () => {
|
|
2956
|
+
let colliderLockData = parseColliderLockData(null);
|
|
2957
|
+
assert.deepStrictEqual(colliderLockData.pkgList.length, 0);
|
|
2958
|
+
assert.deepStrictEqual(Object.keys(colliderLockData.dependencies).length, 0);
|
|
2959
|
+
assert.deepStrictEqual(
|
|
2960
|
+
colliderLockData.parentComponentDependencies.length,
|
|
2961
|
+
0,
|
|
2962
|
+
);
|
|
2963
|
+
|
|
2964
|
+
colliderLockData = parseColliderLockData(
|
|
2965
|
+
readFileSync("./test/data/collider.lock", { encoding: "utf-8" }),
|
|
2966
|
+
"./test/data/collider.lock",
|
|
2967
|
+
);
|
|
2968
|
+
assert.deepStrictEqual(colliderLockData.pkgList.length, 3);
|
|
2969
|
+
assert.deepStrictEqual(colliderLockData.pkgList[0], {
|
|
2970
|
+
name: "fmt",
|
|
2971
|
+
version: "11.0.2",
|
|
2972
|
+
"bom-ref": "pkg:generic/fmt@11.0.2",
|
|
2973
|
+
externalReferences: [
|
|
2974
|
+
{
|
|
2975
|
+
type: "distribution",
|
|
2976
|
+
url: "https://packages.example.com/collider/v2/",
|
|
2977
|
+
},
|
|
2978
|
+
],
|
|
2979
|
+
hashes: [
|
|
2980
|
+
{
|
|
2981
|
+
alg: "SHA-256",
|
|
2982
|
+
content:
|
|
2983
|
+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
2984
|
+
},
|
|
2985
|
+
],
|
|
2986
|
+
properties: [
|
|
2987
|
+
{ name: "SrcFile", value: "./test/data/collider.lock" },
|
|
2988
|
+
{ name: "cdx:collider:dependencyKind", value: "direct" },
|
|
2989
|
+
{
|
|
2990
|
+
name: "cdx:collider:wrapHash",
|
|
2991
|
+
value:
|
|
2992
|
+
"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
2993
|
+
},
|
|
2994
|
+
{ name: "cdx:collider:hasWrapHash", value: "true" },
|
|
2995
|
+
{
|
|
2996
|
+
name: "cdx:collider:origin",
|
|
2997
|
+
value: "https://packages.example.com/collider/v2/",
|
|
2998
|
+
},
|
|
2999
|
+
{ name: "cdx:collider:originScheme", value: "https" },
|
|
3000
|
+
{ name: "cdx:collider:originHost", value: "packages.example.com" },
|
|
3001
|
+
],
|
|
3002
|
+
purl: "pkg:generic/fmt@11.0.2",
|
|
3003
|
+
scope: "required",
|
|
3004
|
+
});
|
|
3005
|
+
assert.deepStrictEqual(colliderLockData.pkgList[1], {
|
|
3006
|
+
name: "spdlog",
|
|
3007
|
+
version: "1.15.0",
|
|
3008
|
+
"bom-ref": "pkg:generic/spdlog@1.15.0",
|
|
3009
|
+
externalReferences: [
|
|
3010
|
+
{
|
|
3011
|
+
type: "distribution",
|
|
3012
|
+
url: "https://packages.example.com/collider/v2/",
|
|
3013
|
+
},
|
|
3014
|
+
],
|
|
3015
|
+
hashes: [
|
|
3016
|
+
{
|
|
3017
|
+
alg: "SHA-256",
|
|
3018
|
+
content:
|
|
3019
|
+
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
|
3020
|
+
},
|
|
3021
|
+
],
|
|
3022
|
+
properties: [
|
|
3023
|
+
{ name: "SrcFile", value: "./test/data/collider.lock" },
|
|
3024
|
+
{ name: "cdx:collider:dependencyKind", value: "direct" },
|
|
3025
|
+
{
|
|
3026
|
+
name: "cdx:collider:wrapHash",
|
|
3027
|
+
value:
|
|
3028
|
+
"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
|
3029
|
+
},
|
|
3030
|
+
{ name: "cdx:collider:hasWrapHash", value: "true" },
|
|
3031
|
+
{
|
|
3032
|
+
name: "cdx:collider:origin",
|
|
3033
|
+
value: "https://packages.example.com/collider/v2/",
|
|
3034
|
+
},
|
|
3035
|
+
{ name: "cdx:collider:originScheme", value: "https" },
|
|
3036
|
+
{ name: "cdx:collider:originHost", value: "packages.example.com" },
|
|
3037
|
+
],
|
|
3038
|
+
purl: "pkg:generic/spdlog@1.15.0",
|
|
3039
|
+
scope: "required",
|
|
3040
|
+
});
|
|
3041
|
+
assert.deepStrictEqual(colliderLockData.pkgList[2], {
|
|
3042
|
+
name: "fast_float",
|
|
3043
|
+
version: "8.0.2",
|
|
3044
|
+
"bom-ref": "pkg:generic/fast_float@8.0.2",
|
|
3045
|
+
externalReferences: [
|
|
3046
|
+
{
|
|
3047
|
+
type: "distribution",
|
|
3048
|
+
url: "https://wrapdb.mesonbuild.com/v2/",
|
|
3049
|
+
},
|
|
3050
|
+
],
|
|
3051
|
+
hashes: [
|
|
3052
|
+
{
|
|
3053
|
+
alg: "SHA-256",
|
|
3054
|
+
content:
|
|
3055
|
+
"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
|
|
3056
|
+
},
|
|
3057
|
+
],
|
|
3058
|
+
properties: [
|
|
3059
|
+
{ name: "SrcFile", value: "./test/data/collider.lock" },
|
|
3060
|
+
{ name: "cdx:collider:dependencyKind", value: "transitive" },
|
|
3061
|
+
{
|
|
3062
|
+
name: "cdx:collider:wrapHash",
|
|
3063
|
+
value:
|
|
3064
|
+
"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
|
|
3065
|
+
},
|
|
3066
|
+
{ name: "cdx:collider:hasWrapHash", value: "true" },
|
|
3067
|
+
{
|
|
3068
|
+
name: "cdx:collider:origin",
|
|
3069
|
+
value: "https://wrapdb.mesonbuild.com/v2/",
|
|
3070
|
+
},
|
|
3071
|
+
{ name: "cdx:collider:originScheme", value: "https" },
|
|
3072
|
+
{ name: "cdx:collider:originHost", value: "wrapdb.mesonbuild.com" },
|
|
3073
|
+
],
|
|
3074
|
+
purl: "pkg:generic/fast_float@8.0.2",
|
|
3075
|
+
});
|
|
3076
|
+
assert.deepStrictEqual(colliderLockData.dependencies, {
|
|
3077
|
+
"pkg:generic/fmt@11.0.2": [],
|
|
3078
|
+
"pkg:generic/spdlog@1.15.0": [],
|
|
3079
|
+
"pkg:generic/fast_float@8.0.2": [],
|
|
3080
|
+
});
|
|
3081
|
+
assert.deepStrictEqual(colliderLockData.parentComponentDependencies, [
|
|
3082
|
+
"pkg:generic/fmt@11.0.2",
|
|
3083
|
+
"pkg:generic/spdlog@1.15.0",
|
|
3084
|
+
]);
|
|
3085
|
+
});
|
|
3086
|
+
|
|
3087
|
+
it("parse collider lock data sanitizes origin metadata and tracks invalid wrap hashes", () => {
|
|
3088
|
+
const colliderLockData = parseColliderLockData(
|
|
3089
|
+
JSON.stringify({
|
|
3090
|
+
version: 1,
|
|
3091
|
+
dependencies: {
|
|
3092
|
+
"unsafe-origin": {
|
|
3093
|
+
version: "1.0.0",
|
|
3094
|
+
wrap_hash:
|
|
3095
|
+
"sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
|
|
3096
|
+
origin: "https://user:pass@example.com/private/v2/?token=secret#frag",
|
|
3097
|
+
},
|
|
3098
|
+
},
|
|
3099
|
+
packages: {
|
|
3100
|
+
malformed: {
|
|
3101
|
+
version: "2.0.0",
|
|
3102
|
+
wrap_hash: "not-a-sha256",
|
|
3103
|
+
origin: "http://mirror.example.com/collider/v2/?sig=123",
|
|
3104
|
+
},
|
|
3105
|
+
"bad-origin": {
|
|
3106
|
+
version: "3.0.0",
|
|
3107
|
+
wrap_hash:
|
|
3108
|
+
"sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
|
3109
|
+
origin: "://not a url",
|
|
3110
|
+
},
|
|
3111
|
+
},
|
|
3112
|
+
}),
|
|
3113
|
+
"/repo/collider.lock",
|
|
3114
|
+
);
|
|
3115
|
+
assert.strictEqual(colliderLockData.pkgList.length, 3);
|
|
3116
|
+
const unsafeOrigin = colliderLockData.pkgList.find(
|
|
3117
|
+
(pkg) => pkg.name === "unsafe-origin",
|
|
3118
|
+
);
|
|
3119
|
+
const malformed = colliderLockData.pkgList.find(
|
|
3120
|
+
(pkg) => pkg.name === "malformed",
|
|
3121
|
+
);
|
|
3122
|
+
const badOrigin = colliderLockData.pkgList.find(
|
|
3123
|
+
(pkg) => pkg.name === "bad-origin",
|
|
3124
|
+
);
|
|
3125
|
+
assert.deepStrictEqual(
|
|
3126
|
+
unsafeOrigin.properties.find(
|
|
3127
|
+
(property) => property.name === "cdx:collider:origin",
|
|
3128
|
+
)?.value,
|
|
3129
|
+
"https://example.com/private/v2/",
|
|
3130
|
+
);
|
|
3131
|
+
assert.deepStrictEqual(
|
|
3132
|
+
unsafeOrigin.properties.find(
|
|
3133
|
+
(property) => property.name === "cdx:collider:originSanitized",
|
|
3134
|
+
)?.value,
|
|
3135
|
+
"true",
|
|
3136
|
+
);
|
|
3137
|
+
assert.deepStrictEqual(unsafeOrigin.externalReferences, [
|
|
3138
|
+
{
|
|
3139
|
+
type: "distribution",
|
|
3140
|
+
url: "https://example.com/private/v2/",
|
|
3141
|
+
},
|
|
3142
|
+
]);
|
|
3143
|
+
assert.deepStrictEqual(
|
|
3144
|
+
malformed.properties.find(
|
|
3145
|
+
(property) => property.name === "cdx:collider:hasWrapHash",
|
|
3146
|
+
)?.value,
|
|
3147
|
+
"false",
|
|
3148
|
+
);
|
|
3149
|
+
assert.deepStrictEqual(
|
|
3150
|
+
malformed.properties.find(
|
|
3151
|
+
(property) => property.name === "cdx:collider:wrapHashInvalid",
|
|
3152
|
+
)?.value,
|
|
3153
|
+
"true",
|
|
3154
|
+
);
|
|
3155
|
+
assert.deepStrictEqual(
|
|
3156
|
+
malformed.properties.find(
|
|
3157
|
+
(property) => property.name === "cdx:collider:origin",
|
|
3158
|
+
)?.value,
|
|
3159
|
+
"http://mirror.example.com/collider/v2/",
|
|
3160
|
+
);
|
|
3161
|
+
assert.ok(!malformed.hashes);
|
|
3162
|
+
assert.ok(
|
|
3163
|
+
!badOrigin.properties.some(
|
|
3164
|
+
(property) => property.name === "cdx:collider:origin",
|
|
3165
|
+
),
|
|
3166
|
+
);
|
|
3167
|
+
assert.ok(!badOrigin.externalReferences);
|
|
3168
|
+
});
|
|
3169
|
+
|
|
2828
3170
|
it("conan package reference mapper to pURL", () => {
|
|
2829
3171
|
const checkParseResult = (inputPkgRef, expectedPurl) => {
|
|
2830
3172
|
const [purl, name, version] =
|
|
@@ -3133,7 +3475,25 @@ it("parse github actions workflow data", () => {
|
|
|
3133
3475
|
dep_list = parseGitHubWorkflowData("./test/data/github-actions-tj.yaml");
|
|
3134
3476
|
assert.deepStrictEqual(dep_list.length, 4);
|
|
3135
3477
|
dep_list = parseGitHubWorkflowData("./.github/workflows/repotests.yml");
|
|
3136
|
-
assert.
|
|
3478
|
+
assert.ok(dep_list.length > 0);
|
|
3479
|
+
assert.ok(
|
|
3480
|
+
dep_list.every((component) =>
|
|
3481
|
+
component.properties?.some(
|
|
3482
|
+
(property) =>
|
|
3483
|
+
property.name === "cdx:github:workflow:file" &&
|
|
3484
|
+
property.value === "./.github/workflows/repotests.yml",
|
|
3485
|
+
),
|
|
3486
|
+
),
|
|
3487
|
+
);
|
|
3488
|
+
assert.ok(
|
|
3489
|
+
dep_list.some((component) =>
|
|
3490
|
+
component.properties?.some(
|
|
3491
|
+
(property) =>
|
|
3492
|
+
property.name === "cdx:github:checkout:repository" &&
|
|
3493
|
+
property.value === "AppThreat/vulnerability-db",
|
|
3494
|
+
),
|
|
3495
|
+
),
|
|
3496
|
+
);
|
|
3137
3497
|
});
|
|
3138
3498
|
// biome-ignore-end lint/suspicious/noTemplateCurlyInString: fp
|
|
3139
3499
|
|
|
@@ -4805,6 +5165,181 @@ it("parsePkgLock marks devOptional entries as development", async () => {
|
|
|
4805
5165
|
);
|
|
4806
5166
|
});
|
|
4807
5167
|
|
|
5168
|
+
it("parsePkgLock captures manifest-declared npm direct sources", async () => {
|
|
5169
|
+
const rootNode = {
|
|
5170
|
+
path: "/virtual/project",
|
|
5171
|
+
package: {
|
|
5172
|
+
author: "",
|
|
5173
|
+
license: "MIT",
|
|
5174
|
+
},
|
|
5175
|
+
packageName: "virtual-project",
|
|
5176
|
+
version: "1.0.0",
|
|
5177
|
+
edgesOut: new Map(),
|
|
5178
|
+
fsChildren: new Set(),
|
|
5179
|
+
children: new Map(),
|
|
5180
|
+
};
|
|
5181
|
+
const gitNode = {
|
|
5182
|
+
path: "/virtual/project/node_modules/git-dep",
|
|
5183
|
+
package: {
|
|
5184
|
+
author: "",
|
|
5185
|
+
license: "MIT",
|
|
5186
|
+
},
|
|
5187
|
+
packageName: "git-dep",
|
|
5188
|
+
version: "2.0.0",
|
|
5189
|
+
hasInstallScript: true,
|
|
5190
|
+
integrity: "sha512-gitdep",
|
|
5191
|
+
edgesIn: new Set([
|
|
5192
|
+
{
|
|
5193
|
+
name: "git-dep",
|
|
5194
|
+
spec: "git+https://github.com/acme/git-dep.git",
|
|
5195
|
+
},
|
|
5196
|
+
]),
|
|
5197
|
+
edgesOut: new Map(),
|
|
5198
|
+
fsChildren: new Set(),
|
|
5199
|
+
children: new Map(),
|
|
5200
|
+
};
|
|
5201
|
+
rootNode.children.set("node_modules/git-dep", gitNode);
|
|
5202
|
+
rootNode.edgesOut.set("git-dep", {
|
|
5203
|
+
name: "git-dep",
|
|
5204
|
+
spec: "git+https://github.com/acme/git-dep.git",
|
|
5205
|
+
to: gitNode,
|
|
5206
|
+
});
|
|
5207
|
+
const { parsePkgLock: parsePkgLockWithMockedArborist } = await esmock(
|
|
5208
|
+
"./utils.js",
|
|
5209
|
+
{
|
|
5210
|
+
"../third-party/arborist/lib/index.js": {
|
|
5211
|
+
default: class MockArborist {
|
|
5212
|
+
async loadVirtual() {
|
|
5213
|
+
return rootNode;
|
|
5214
|
+
}
|
|
5215
|
+
},
|
|
5216
|
+
},
|
|
5217
|
+
},
|
|
5218
|
+
);
|
|
5219
|
+
const parsedList = await parsePkgLockWithMockedArborist(
|
|
5220
|
+
"./test/data/package-json/v3/package-lock.json",
|
|
5221
|
+
{},
|
|
5222
|
+
);
|
|
5223
|
+
const gitDepPkg = parsedList.pkgList.find(
|
|
5224
|
+
(pkg) => pkg["bom-ref"] === "pkg:npm/git-dep@2.0.0",
|
|
5225
|
+
);
|
|
5226
|
+
assert.ok(gitDepPkg);
|
|
5227
|
+
assert.ok(
|
|
5228
|
+
gitDepPkg.properties.some(
|
|
5229
|
+
(property) =>
|
|
5230
|
+
property.name === "cdx:npm:manifestSourceType" &&
|
|
5231
|
+
property.value === "git",
|
|
5232
|
+
),
|
|
5233
|
+
);
|
|
5234
|
+
assert.ok(
|
|
5235
|
+
gitDepPkg.properties.some(
|
|
5236
|
+
(property) =>
|
|
5237
|
+
property.name === "cdx:npm:manifestSource" &&
|
|
5238
|
+
property.value === "git+https://github.com/acme/git-dep.git",
|
|
5239
|
+
),
|
|
5240
|
+
);
|
|
5241
|
+
});
|
|
5242
|
+
|
|
5243
|
+
it("parsePkgLock captures supported npm manifest source syntaxes", async () => {
|
|
5244
|
+
const rootNode = {
|
|
5245
|
+
path: "/virtual/project",
|
|
5246
|
+
package: {
|
|
5247
|
+
author: "",
|
|
5248
|
+
license: "MIT",
|
|
5249
|
+
},
|
|
5250
|
+
packageName: "virtual-project",
|
|
5251
|
+
version: "1.0.0",
|
|
5252
|
+
edgesOut: new Map(),
|
|
5253
|
+
fsChildren: new Set(),
|
|
5254
|
+
children: new Map(),
|
|
5255
|
+
};
|
|
5256
|
+
const sourceCases = [
|
|
5257
|
+
["git-plus", "git+https://github.com/acme/git-plus.git", "git"],
|
|
5258
|
+
["git-protocol", "git://github.com/acme/git-protocol.git", "git"],
|
|
5259
|
+
["github-shortcut", "github:acme/github-shortcut", "git"],
|
|
5260
|
+
["gitlab-shortcut", "gitlab:acme/gitlab-shortcut", "git"],
|
|
5261
|
+
["bitbucket-shortcut", "bitbucket:acme/bitbucket-shortcut", "git"],
|
|
5262
|
+
["gist-shortcut", "gist:1234567890abcdef", "git"],
|
|
5263
|
+
["http-archive", "http://example.com/http-archive.tgz", "url"],
|
|
5264
|
+
["https-archive", "https://example.com/https-archive.tgz", "url"],
|
|
5265
|
+
["file-source", "file:../libs/file-source", "path"],
|
|
5266
|
+
["link-source", "link:../libs/link-source", "path"],
|
|
5267
|
+
["workspace-source", "workspace:*", "path"],
|
|
5268
|
+
["relative-source", "./libs/relative-source", "path"],
|
|
5269
|
+
["parent-source", "../libs/parent-source", "path"],
|
|
5270
|
+
["absolute-source", "/opt/libs/absolute-source", "path"],
|
|
5271
|
+
["windows-source", "C:\\libs\\windows-source", "path"],
|
|
5272
|
+
];
|
|
5273
|
+
|
|
5274
|
+
for (const [packageName, spec] of sourceCases) {
|
|
5275
|
+
const childPath = `/virtual/project/node_modules/${packageName}`;
|
|
5276
|
+
const childNode = {
|
|
5277
|
+
path: childPath,
|
|
5278
|
+
package: {
|
|
5279
|
+
author: "",
|
|
5280
|
+
license: "MIT",
|
|
5281
|
+
},
|
|
5282
|
+
packageName,
|
|
5283
|
+
version: "1.0.0",
|
|
5284
|
+
integrity: `sha512-${packageName}`,
|
|
5285
|
+
edgesIn: new Set([
|
|
5286
|
+
{
|
|
5287
|
+
name: packageName,
|
|
5288
|
+
spec,
|
|
5289
|
+
},
|
|
5290
|
+
]),
|
|
5291
|
+
edgesOut: new Map(),
|
|
5292
|
+
fsChildren: new Set(),
|
|
5293
|
+
children: new Map(),
|
|
5294
|
+
};
|
|
5295
|
+
rootNode.children.set(`node_modules/${packageName}`, childNode);
|
|
5296
|
+
rootNode.edgesOut.set(packageName, {
|
|
5297
|
+
name: packageName,
|
|
5298
|
+
spec,
|
|
5299
|
+
to: childNode,
|
|
5300
|
+
});
|
|
5301
|
+
}
|
|
5302
|
+
|
|
5303
|
+
const { parsePkgLock: parsePkgLockWithMockedArborist } = await esmock(
|
|
5304
|
+
"./utils.js",
|
|
5305
|
+
{
|
|
5306
|
+
"../third-party/arborist/lib/index.js": {
|
|
5307
|
+
default: class MockArborist {
|
|
5308
|
+
async loadVirtual() {
|
|
5309
|
+
return rootNode;
|
|
5310
|
+
}
|
|
5311
|
+
},
|
|
5312
|
+
},
|
|
5313
|
+
},
|
|
5314
|
+
);
|
|
5315
|
+
const parsedList = await parsePkgLockWithMockedArborist(
|
|
5316
|
+
"./test/data/package-json/v3/package-lock.json",
|
|
5317
|
+
{},
|
|
5318
|
+
);
|
|
5319
|
+
|
|
5320
|
+
for (const [packageName, spec, expectedType] of sourceCases) {
|
|
5321
|
+
const pkg = parsedList.pkgList.find(
|
|
5322
|
+
(parsedPkg) => parsedPkg.name === packageName,
|
|
5323
|
+
);
|
|
5324
|
+
assert.ok(pkg, `expected ${packageName} to be parsed`);
|
|
5325
|
+
assert.ok(
|
|
5326
|
+
pkg.properties.some(
|
|
5327
|
+
(property) =>
|
|
5328
|
+
property.name === "cdx:npm:manifestSourceType" &&
|
|
5329
|
+
property.value === expectedType,
|
|
5330
|
+
),
|
|
5331
|
+
`expected ${packageName} manifest source type ${expectedType}`,
|
|
5332
|
+
);
|
|
5333
|
+
assert.ok(
|
|
5334
|
+
pkg.properties.some(
|
|
5335
|
+
(property) =>
|
|
5336
|
+
property.name === "cdx:npm:manifestSource" && property.value === spec,
|
|
5337
|
+
),
|
|
5338
|
+
`expected ${packageName} manifest source ${spec}`,
|
|
5339
|
+
);
|
|
5340
|
+
}
|
|
5341
|
+
});
|
|
5342
|
+
|
|
4808
5343
|
it("parsePkgLock theia", async () => {
|
|
4809
5344
|
const parsedList = await parsePkgLock(
|
|
4810
5345
|
"./test/data/package-json/theia/package-lock.json",
|
|
@@ -7447,6 +7982,112 @@ it("parse requirements.txt", async () => {
|
|
|
7447
7982
|
}
|
|
7448
7983
|
});
|
|
7449
7984
|
|
|
7985
|
+
it("parse requirements.txt enriches distribution references when package metadata fetch is enabled", async () => {
|
|
7986
|
+
const tempDir = mkdtempSync(path.join(tmpdir(), "cdxgen-req-pypi-"));
|
|
7987
|
+
const reqFile = path.join(tempDir, "requirements.txt");
|
|
7988
|
+
const agentGetStub = sinon.stub().resolves({
|
|
7989
|
+
body: {
|
|
7990
|
+
info: {
|
|
7991
|
+
author: "",
|
|
7992
|
+
author_email: "",
|
|
7993
|
+
classifiers: [],
|
|
7994
|
+
license: "",
|
|
7995
|
+
license_expression: "",
|
|
7996
|
+
name: "requests",
|
|
7997
|
+
summary: "HTTP client",
|
|
7998
|
+
version: "2.31.0",
|
|
7999
|
+
},
|
|
8000
|
+
releases: {
|
|
8001
|
+
"2.31.0": [
|
|
8002
|
+
{
|
|
8003
|
+
digests: { sha256: "abc123" },
|
|
8004
|
+
filename: "requests-2.31.0-py3-none-any.whl",
|
|
8005
|
+
packagetype: "bdist_wheel",
|
|
8006
|
+
url: "https://files.pythonhosted.org/packages/example/requests-2.31.0-py3-none-any.whl",
|
|
8007
|
+
},
|
|
8008
|
+
],
|
|
8009
|
+
},
|
|
8010
|
+
},
|
|
8011
|
+
});
|
|
8012
|
+
writeFileSync(reqFile, "requests==2.31.0\n", "utf-8");
|
|
8013
|
+
const { parseReqFile: mockedParseReqFile } = await esmock("./utils.js", {
|
|
8014
|
+
got: {
|
|
8015
|
+
default: {
|
|
8016
|
+
extend: sinon.stub().returns({ get: agentGetStub }),
|
|
8017
|
+
},
|
|
8018
|
+
},
|
|
8019
|
+
});
|
|
8020
|
+
try {
|
|
8021
|
+
const deps = await mockedParseReqFile(reqFile, true);
|
|
8022
|
+
assert.strictEqual(deps.length, 1);
|
|
8023
|
+
assert.ok(
|
|
8024
|
+
deps[0].externalReferences?.some(
|
|
8025
|
+
(reference) =>
|
|
8026
|
+
reference.type === "distribution" && reference.url.endsWith(".whl"),
|
|
8027
|
+
),
|
|
8028
|
+
);
|
|
8029
|
+
} finally {
|
|
8030
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
8031
|
+
}
|
|
8032
|
+
});
|
|
8033
|
+
|
|
8034
|
+
it("parse requirements.txt captures direct manifest sources", async () => {
|
|
8035
|
+
const tempDir = mkdtempSync(path.join(tmpdir(), "cdxgen-req-source-"));
|
|
8036
|
+
const reqFile = path.join(tempDir, "requirements.txt");
|
|
8037
|
+
writeFileSync(
|
|
8038
|
+
reqFile,
|
|
8039
|
+
[
|
|
8040
|
+
"requests @ https://example.com/packages/requests-2.31.0.whl # MIT",
|
|
8041
|
+
"-e git+https://github.com/acme/private-lib.git#egg=private-lib",
|
|
8042
|
+
"",
|
|
8043
|
+
].join("\n"),
|
|
8044
|
+
"utf-8",
|
|
8045
|
+
);
|
|
8046
|
+
try {
|
|
8047
|
+
const deps = await parseReqFile(reqFile, false);
|
|
8048
|
+
const requestsPkg = deps.find((pkg) => pkg.name === "requests");
|
|
8049
|
+
assert.ok(requestsPkg);
|
|
8050
|
+
assert.ok(
|
|
8051
|
+
requestsPkg.properties.some(
|
|
8052
|
+
(property) =>
|
|
8053
|
+
property.name === "cdx:pypi:manifestSourceType" &&
|
|
8054
|
+
property.value === "url",
|
|
8055
|
+
),
|
|
8056
|
+
);
|
|
8057
|
+
assert.ok(
|
|
8058
|
+
requestsPkg.properties.some(
|
|
8059
|
+
(property) =>
|
|
8060
|
+
property.name === "cdx:pypi:manifestSource" &&
|
|
8061
|
+
property.value === "https://example.com/packages/requests-2.31.0.whl",
|
|
8062
|
+
),
|
|
8063
|
+
);
|
|
8064
|
+
assert.deepStrictEqual(requestsPkg.licenses, [
|
|
8065
|
+
{
|
|
8066
|
+
license: {
|
|
8067
|
+
id: "MIT",
|
|
8068
|
+
},
|
|
8069
|
+
},
|
|
8070
|
+
]);
|
|
8071
|
+
const privateLibPkg = deps.find((pkg) => pkg.name === "private-lib");
|
|
8072
|
+
assert.ok(privateLibPkg);
|
|
8073
|
+
assert.ok(
|
|
8074
|
+
privateLibPkg.properties.some(
|
|
8075
|
+
(property) =>
|
|
8076
|
+
property.name === "cdx:pypi:manifestSourceType" &&
|
|
8077
|
+
property.value === "git",
|
|
8078
|
+
),
|
|
8079
|
+
);
|
|
8080
|
+
assert.ok(
|
|
8081
|
+
privateLibPkg.properties.some(
|
|
8082
|
+
(property) =>
|
|
8083
|
+
property.name === "cdx:pypi:editable" && property.value === "true",
|
|
8084
|
+
),
|
|
8085
|
+
);
|
|
8086
|
+
} finally {
|
|
8087
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
8088
|
+
}
|
|
8089
|
+
});
|
|
8090
|
+
|
|
7450
8091
|
it("parse pyproject.toml", () => {
|
|
7451
8092
|
let retMap = parsePyProjectTomlFile("./test/data/pyproject.toml");
|
|
7452
8093
|
assert.deepStrictEqual(retMap.parentComponent, {
|
|
@@ -7651,6 +8292,120 @@ it("parse pyproject.toml with custom poetry source", () => {
|
|
|
7651
8292
|
assert.deepStrictEqual(Object.keys(retMap.directDepsKeys).length, 6);
|
|
7652
8293
|
});
|
|
7653
8294
|
|
|
8295
|
+
it("parse pyproject.toml captures dependency manifest sources", async () => {
|
|
8296
|
+
const tempDir = mkdtempSync(path.join(tmpdir(), "cdxgen-pyproject-source-"));
|
|
8297
|
+
const pyProjectFile = path.join(tempDir, "pyproject.toml");
|
|
8298
|
+
writeFileSync(
|
|
8299
|
+
pyProjectFile,
|
|
8300
|
+
`
|
|
8301
|
+
[project]
|
|
8302
|
+
name = "demo-app"
|
|
8303
|
+
version = "0.1.0"
|
|
8304
|
+
dependencies = ["anyio[http2] @ https://example.com/packages/anyio.whl"]
|
|
8305
|
+
|
|
8306
|
+
[tool.poetry.dependencies]
|
|
8307
|
+
python = ">=3.11"
|
|
8308
|
+
poetry-git = { git = "https://github.com/acme/poetry-git.git" }
|
|
8309
|
+
|
|
8310
|
+
[tool.uv.sources]
|
|
8311
|
+
uv-path = { path = "../libs/uv-path" }
|
|
8312
|
+
`.trim(),
|
|
8313
|
+
"utf-8",
|
|
8314
|
+
);
|
|
8315
|
+
try {
|
|
8316
|
+
const pyProjectData = parsePyProjectTomlFile(pyProjectFile);
|
|
8317
|
+
assert.deepStrictEqual(pyProjectData.dependencySourceMap.anyio, {
|
|
8318
|
+
type: "url",
|
|
8319
|
+
value: "https://example.com/packages/anyio.whl",
|
|
8320
|
+
});
|
|
8321
|
+
assert.strictEqual(pyProjectData.directDepsKeys.anyio, true);
|
|
8322
|
+
assert.deepStrictEqual(pyProjectData.dependencySourceMap["poetry-git"], {
|
|
8323
|
+
type: "git",
|
|
8324
|
+
value: "https://github.com/acme/poetry-git.git",
|
|
8325
|
+
});
|
|
8326
|
+
assert.deepStrictEqual(pyProjectData.dependencySourceMap["uv-path"], {
|
|
8327
|
+
type: "path",
|
|
8328
|
+
value: "../libs/uv-path",
|
|
8329
|
+
});
|
|
8330
|
+
|
|
8331
|
+
const retMap = await parsePyLockData(
|
|
8332
|
+
readFileSync("./test/data/uv.lock", { encoding: "utf-8" }),
|
|
8333
|
+
"./test/data/uv.lock",
|
|
8334
|
+
pyProjectFile,
|
|
8335
|
+
);
|
|
8336
|
+
const anyioPkg = retMap.pkgList.find((pkg) => pkg.name === "anyio");
|
|
8337
|
+
assert.ok(anyioPkg);
|
|
8338
|
+
assert.ok(
|
|
8339
|
+
anyioPkg.properties.some(
|
|
8340
|
+
(property) =>
|
|
8341
|
+
property.name === "cdx:pypi:manifestSourceType" &&
|
|
8342
|
+
property.value === "url",
|
|
8343
|
+
),
|
|
8344
|
+
);
|
|
8345
|
+
assert.ok(
|
|
8346
|
+
anyioPkg.properties.some(
|
|
8347
|
+
(property) =>
|
|
8348
|
+
property.name === "cdx:pypi:manifestSource" &&
|
|
8349
|
+
property.value === "https://example.com/packages/anyio.whl",
|
|
8350
|
+
),
|
|
8351
|
+
);
|
|
8352
|
+
} finally {
|
|
8353
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
8354
|
+
}
|
|
8355
|
+
});
|
|
8356
|
+
|
|
8357
|
+
it("normalizes pyproject direct dependency keys when matching pylock packages", async () => {
|
|
8358
|
+
const tempDir = mkdtempSync(path.join(tmpdir(), "cdxgen-pylock-normalize-"));
|
|
8359
|
+
const pyProjectFile = path.join(tempDir, "pyproject.toml");
|
|
8360
|
+
const pyLockFile = path.join(tempDir, "pylock.toml");
|
|
8361
|
+
writeFileSync(
|
|
8362
|
+
pyProjectFile,
|
|
8363
|
+
`
|
|
8364
|
+
[project]
|
|
8365
|
+
name = "normalize-demo"
|
|
8366
|
+
version = "1.0.0"
|
|
8367
|
+
dependencies = [
|
|
8368
|
+
"demo_pkg @ https://example.com/packages/demo-pkg-1.0.0.whl",
|
|
8369
|
+
]
|
|
8370
|
+
`.trim(),
|
|
8371
|
+
"utf-8",
|
|
8372
|
+
);
|
|
8373
|
+
writeFileSync(
|
|
8374
|
+
pyLockFile,
|
|
8375
|
+
`
|
|
8376
|
+
lock-version = "1.0"
|
|
8377
|
+
created-by = "poku"
|
|
8378
|
+
|
|
8379
|
+
[[packages]]
|
|
8380
|
+
name = "demo-pkg"
|
|
8381
|
+
version = "1.0.0"
|
|
8382
|
+
index = "https://pypi.org/simple/"
|
|
8383
|
+
wheels = [
|
|
8384
|
+
{ name = "demo_pkg-1.0.0-py3-none-any.whl", url = "https://example.com/packages/demo-pkg-1.0.0.whl", size = 1234, upload-time = 2026-01-01T00:00:00+00:00, hashes = { sha256 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" } },
|
|
8385
|
+
]
|
|
8386
|
+
`.trim(),
|
|
8387
|
+
"utf-8",
|
|
8388
|
+
);
|
|
8389
|
+
try {
|
|
8390
|
+
const retMap = await parsePyLockData(
|
|
8391
|
+
readFileSync(pyLockFile, { encoding: "utf-8" }),
|
|
8392
|
+
pyLockFile,
|
|
8393
|
+
pyProjectFile,
|
|
8394
|
+
);
|
|
8395
|
+
assert.strictEqual(retMap.rootList.length, 1);
|
|
8396
|
+
assert.strictEqual(retMap.rootList[0].name, "demo-pkg");
|
|
8397
|
+
assert.ok(
|
|
8398
|
+
retMap.rootList[0].properties.some(
|
|
8399
|
+
(property) =>
|
|
8400
|
+
property.name === "cdx:pypi:manifestSourceType" &&
|
|
8401
|
+
property.value === "url",
|
|
8402
|
+
),
|
|
8403
|
+
);
|
|
8404
|
+
} finally {
|
|
8405
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
8406
|
+
}
|
|
8407
|
+
});
|
|
8408
|
+
|
|
7654
8409
|
it("parse python lock files", async () => {
|
|
7655
8410
|
let retMap = await parsePyLockData(
|
|
7656
8411
|
readFileSync("./test/data/poetry.lock", { encoding: "utf-8" }),
|
|
@@ -7677,12 +8432,28 @@ it("parse python lock files", async () => {
|
|
|
7677
8432
|
);
|
|
7678
8433
|
assert.deepStrictEqual(retMap.pkgList.length, 39);
|
|
7679
8434
|
assert.deepStrictEqual(retMap.dependenciesList.length, 37);
|
|
8435
|
+
const pdmBlinkerPkg = retMap.pkgList.find((p) => p.name === "blinker");
|
|
8436
|
+
assert.ok(
|
|
8437
|
+
pdmBlinkerPkg.externalReferences?.some(
|
|
8438
|
+
(reference) =>
|
|
8439
|
+
reference.type === "distribution" && reference.url.endsWith(".whl"),
|
|
8440
|
+
),
|
|
8441
|
+
"Expected pdm.lock metadata files to populate distribution externalReferences",
|
|
8442
|
+
);
|
|
7680
8443
|
retMap = await parsePyLockData(
|
|
7681
8444
|
readFileSync("./test/data/uv.lock", { encoding: "utf-8" }),
|
|
7682
8445
|
"./test/data/uv.lock",
|
|
7683
8446
|
);
|
|
7684
8447
|
assert.deepStrictEqual(retMap.pkgList.length, 63);
|
|
7685
8448
|
assert.deepStrictEqual(retMap.dependenciesList.length, 63);
|
|
8449
|
+
const uvAnyioPkg = retMap.pkgList.find((p) => p.name === "anyio");
|
|
8450
|
+
assert.ok(
|
|
8451
|
+
uvAnyioPkg.externalReferences?.some(
|
|
8452
|
+
(reference) =>
|
|
8453
|
+
reference.type === "distribution" && reference.url.endsWith(".whl"),
|
|
8454
|
+
),
|
|
8455
|
+
"Expected uv.lock packages to populate distribution externalReferences",
|
|
8456
|
+
);
|
|
7686
8457
|
retMap = await parsePyLockData(
|
|
7687
8458
|
readFileSync("./test/data/uv-workspace.lock", { encoding: "utf-8" }),
|
|
7688
8459
|
"./test/data/uv-workspace.lock",
|
|
@@ -7709,6 +8480,13 @@ it("parse python lock files", async () => {
|
|
|
7709
8480
|
attrsPkg.components?.length,
|
|
7710
8481
|
"Expected pylock wheel entry to produce file component",
|
|
7711
8482
|
);
|
|
8483
|
+
assert.ok(
|
|
8484
|
+
attrsPkg.externalReferences?.some(
|
|
8485
|
+
(reference) =>
|
|
8486
|
+
reference.type === "distribution" && reference.url.endsWith(".whl"),
|
|
8487
|
+
),
|
|
8488
|
+
"Expected pylock package to retain distribution externalReferences",
|
|
8489
|
+
);
|
|
7712
8490
|
const cattrsPkg = retMap.pkgList.find((p) => p.name === "cattrs");
|
|
7713
8491
|
assert.ok(
|
|
7714
8492
|
cattrsPkg.properties.some(
|
|
@@ -8863,6 +9641,80 @@ it("parsePackageJsonName tests", () => {
|
|
|
8863
9641
|
});
|
|
8864
9642
|
});
|
|
8865
9643
|
|
|
9644
|
+
it("extractToolRefs collects unique bom-refs from metadata.tools", () => {
|
|
9645
|
+
assert.deepStrictEqual(
|
|
9646
|
+
extractToolRefs(
|
|
9647
|
+
{
|
|
9648
|
+
components: [
|
|
9649
|
+
{ name: "trivy", "bom-ref": "pkg:generic/trivy@0.1.0" },
|
|
9650
|
+
{ name: "trivy", "bom-ref": "pkg:generic/trivy@0.1.0" },
|
|
9651
|
+
{ name: "cdxgen" },
|
|
9652
|
+
],
|
|
9653
|
+
services: [{ name: "blint", "bom-ref": "urn:tool:blint" }],
|
|
9654
|
+
},
|
|
9655
|
+
(tool) => tool.name !== "cdxgen",
|
|
9656
|
+
),
|
|
9657
|
+
["pkg:generic/trivy@0.1.0", "urn:tool:blint"],
|
|
9658
|
+
);
|
|
9659
|
+
});
|
|
9660
|
+
|
|
9661
|
+
it("extractToolRefs derives and persists bom-refs for external tools", () => {
|
|
9662
|
+
const tools = {
|
|
9663
|
+
components: [
|
|
9664
|
+
{
|
|
9665
|
+
group: "aquasecurity",
|
|
9666
|
+
name: "trivy",
|
|
9667
|
+
version: "dev",
|
|
9668
|
+
},
|
|
9669
|
+
],
|
|
9670
|
+
};
|
|
9671
|
+
assert.deepStrictEqual(extractToolRefs(tools), [
|
|
9672
|
+
"pkg:generic/aquasecurity/trivy@dev",
|
|
9673
|
+
]);
|
|
9674
|
+
assert.strictEqual(
|
|
9675
|
+
tools.components[0]["bom-ref"],
|
|
9676
|
+
"pkg:generic/aquasecurity/trivy@dev",
|
|
9677
|
+
);
|
|
9678
|
+
});
|
|
9679
|
+
|
|
9680
|
+
it("attachIdentityTools adds tool references to object and array identities", () => {
|
|
9681
|
+
const subjects = [
|
|
9682
|
+
{
|
|
9683
|
+
evidence: {
|
|
9684
|
+
identity: {
|
|
9685
|
+
field: "purl",
|
|
9686
|
+
tools: ["pkg:generic/existing-tool@1.0.0"],
|
|
9687
|
+
},
|
|
9688
|
+
},
|
|
9689
|
+
},
|
|
9690
|
+
{
|
|
9691
|
+
evidence: {
|
|
9692
|
+
identity: [
|
|
9693
|
+
{ field: "purl" },
|
|
9694
|
+
{ field: "hash", tools: ["urn:tool:hash"] },
|
|
9695
|
+
],
|
|
9696
|
+
},
|
|
9697
|
+
},
|
|
9698
|
+
];
|
|
9699
|
+
attachIdentityTools(subjects, [
|
|
9700
|
+
"pkg:generic/existing-tool@1.0.0",
|
|
9701
|
+
"pkg:generic/trivy@0.1.0",
|
|
9702
|
+
]);
|
|
9703
|
+
assert.deepStrictEqual(subjects[0].evidence.identity.tools, [
|
|
9704
|
+
"pkg:generic/existing-tool@1.0.0",
|
|
9705
|
+
"pkg:generic/trivy@0.1.0",
|
|
9706
|
+
]);
|
|
9707
|
+
assert.deepStrictEqual(subjects[1].evidence.identity[0].tools, [
|
|
9708
|
+
"pkg:generic/existing-tool@1.0.0",
|
|
9709
|
+
"pkg:generic/trivy@0.1.0",
|
|
9710
|
+
]);
|
|
9711
|
+
assert.deepStrictEqual(subjects[1].evidence.identity[1].tools, [
|
|
9712
|
+
"urn:tool:hash",
|
|
9713
|
+
"pkg:generic/existing-tool@1.0.0",
|
|
9714
|
+
"pkg:generic/trivy@0.1.0",
|
|
9715
|
+
]);
|
|
9716
|
+
});
|
|
9717
|
+
|
|
8866
9718
|
it("parseDot tests", () => {
|
|
8867
9719
|
const retMap = parseCmakeDotFile("./test/data/tslite.dot", "conan");
|
|
8868
9720
|
assert.deepStrictEqual(retMap.parentComponent, {
|
|
@@ -9037,6 +9889,20 @@ it("hasAnyProjectType tests", () => {
|
|
|
9037
9889
|
}),
|
|
9038
9890
|
true,
|
|
9039
9891
|
);
|
|
9892
|
+
assert.deepStrictEqual(
|
|
9893
|
+
hasAnyProjectType(["oci"], {
|
|
9894
|
+
projectType: ["rootfs"],
|
|
9895
|
+
excludeType: undefined,
|
|
9896
|
+
}),
|
|
9897
|
+
true,
|
|
9898
|
+
);
|
|
9899
|
+
assert.deepStrictEqual(
|
|
9900
|
+
hasAnyProjectType(["docker"], {
|
|
9901
|
+
projectType: ["rootfs"],
|
|
9902
|
+
excludeType: undefined,
|
|
9903
|
+
}),
|
|
9904
|
+
true,
|
|
9905
|
+
);
|
|
9040
9906
|
|
|
9041
9907
|
assert.deepStrictEqual(
|
|
9042
9908
|
hasAnyProjectType(["js"], {
|
|
@@ -9837,4 +10703,38 @@ describe("convertOSQueryResults", () => {
|
|
|
9837
10703
|
assert.ok(propertyMap["cdx:lolbas:functions"].includes("download"));
|
|
9838
10704
|
assert.ok(propertyMap["cdx:lolbas:attackTechniques"].includes("T1059.001"));
|
|
9839
10705
|
});
|
|
10706
|
+
|
|
10707
|
+
it("collectExecutables() prefers usr-merged executable paths", () => {
|
|
10708
|
+
if (process.platform === "win32") {
|
|
10709
|
+
return;
|
|
10710
|
+
}
|
|
10711
|
+
const tempDir = mkdtempSync(path.join(tmpdir(), "cdxgen-executables-"));
|
|
10712
|
+
try {
|
|
10713
|
+
mkdirSync(path.join(tempDir, "usr", "bin"), { recursive: true });
|
|
10714
|
+
mkdirSync(path.join(tempDir, "usr", "sbin"), { recursive: true });
|
|
10715
|
+
writeFileSync(path.join(tempDir, "usr", "bin", "which"), "#!/bin/sh\n");
|
|
10716
|
+
writeFileSync(
|
|
10717
|
+
path.join(tempDir, "usr", "sbin", "zramctl"),
|
|
10718
|
+
"#!/bin/sh\n",
|
|
10719
|
+
);
|
|
10720
|
+
chmodSync(path.join(tempDir, "usr", "bin", "which"), 0o755);
|
|
10721
|
+
chmodSync(path.join(tempDir, "usr", "sbin", "zramctl"), 0o755);
|
|
10722
|
+
symlinkSync(path.join(tempDir, "usr", "bin"), path.join(tempDir, "bin"));
|
|
10723
|
+
symlinkSync(
|
|
10724
|
+
path.join(tempDir, "usr", "sbin"),
|
|
10725
|
+
path.join(tempDir, "sbin"),
|
|
10726
|
+
);
|
|
10727
|
+
|
|
10728
|
+
const result = collectExecutables(tempDir, [
|
|
10729
|
+
"/bin",
|
|
10730
|
+
"/usr/bin",
|
|
10731
|
+
"/sbin",
|
|
10732
|
+
"/usr/sbin",
|
|
10733
|
+
]);
|
|
10734
|
+
|
|
10735
|
+
assert.deepStrictEqual(result, ["usr/bin/which", "usr/sbin/zramctl"]);
|
|
10736
|
+
} finally {
|
|
10737
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
10738
|
+
}
|
|
10739
|
+
});
|
|
9840
10740
|
});
|