@cyclonedx/cdxgen 9.10.0 → 9.10.2
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 +3 -1
- package/bin/cdxgen.js +16 -1
- package/index.js +46 -5
- package/package.json +5 -4
- package/protobom.js +36 -0
- package/protobom.test.js +32 -0
- package/utils.js +146 -44
- package/utils.test.js +107 -18
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ Most SBOM tools are like barcode scanners. They can scan a few package manifest
|
|
|
22
22
|
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | -------- |
|
|
23
23
|
| Node.js | npm-shrinkwrap.json, package-lock.json, pnpm-lock.yaml, yarn.lock, rush.js, bower.json, .min.js | Yes except .min.js | Yes |
|
|
24
24
|
| Java | maven (pom.xml [1]), gradle (build.gradle, .kts), scala (sbt), bazel | Yes unless pom.xml is manually parsed due to unavailability of maven or errors | Yes |
|
|
25
|
-
| PHP | composer.lock | Yes |
|
|
25
|
+
| PHP | composer.lock | Yes | Yes |
|
|
26
26
|
| Python | pyproject.toml, setup.py, requirements.txt [2], Pipfile.lock, poetry.lock, pdm.lock, bdist_wheel, .whl, .egg-info | Yes using the automatic pip install/freeze. When disabled, only with Pipfile.lock and poetry.lock | Yes |
|
|
27
27
|
| Go | binary, go.mod, go.sum, Gopkg.lock | Yes except binary | Yes |
|
|
28
28
|
| Ruby | Gemfile.lock, gemspec | Only for Gemfile.lock | |
|
|
@@ -347,6 +347,7 @@ cdxgen can retain the dependency tree under the `dependencies` attribute for a s
|
|
|
347
347
|
- Python (requirements.txt, setup.py, pyproject.toml, poetry.lock)
|
|
348
348
|
- .NET (project.assets.json, paket.lock)
|
|
349
349
|
- Go (go.mod)
|
|
350
|
+
- PHP (composer.lock)
|
|
350
351
|
|
|
351
352
|
## Environment variables
|
|
352
353
|
|
|
@@ -358,6 +359,7 @@ cdxgen can retain the dependency tree under the `dependencies` attribute for a s
|
|
|
358
359
|
| MVN_ARGS | Set to pass additional arguments such as profile or settings to maven |
|
|
359
360
|
| MAVEN_HOME | Specify maven home |
|
|
360
361
|
| MAVEN_CENTRAL_URL | Specify URL of Maven Central for metadata fetching (e.g. when private repo is used) |
|
|
362
|
+
| ANDROID_MAVEN_URL | Specify URL of Android Maven Repository for metadata fetching (e.g. when private repo is used) |
|
|
361
363
|
| BAZEL_TARGET | Bazel target to build. Default :all (Eg: //java-maven) |
|
|
362
364
|
| BAZEL_STRIP_MAVEN_PREFIX | Strip Maven group prefix (e.g. useful when private repo is used, defaults to `/maven2/`) |
|
|
363
365
|
| BAZEL_USE_ACTION_GRAPH | SBOM for specific Bazel target, uses `bazel aquery 'outputs(".*.jar", deps(<BAZEL_TARGET>))'` (defaults to `false`) |
|
package/bin/cdxgen.js
CHANGED
|
@@ -220,6 +220,17 @@ const args = yargs(hideBin(process.argv))
|
|
|
220
220
|
description: "Additional glob pattern(s) to ignore",
|
|
221
221
|
hidden: true
|
|
222
222
|
})
|
|
223
|
+
.option("export-proto", {
|
|
224
|
+
type: "boolean",
|
|
225
|
+
default: false,
|
|
226
|
+
description: "Serialize and export BOM as protobuf binary.",
|
|
227
|
+
hidden: true
|
|
228
|
+
})
|
|
229
|
+
.option("proto-bin-file", {
|
|
230
|
+
description: "Path for the serialized protobuf binary.",
|
|
231
|
+
default: "bom.cdx",
|
|
232
|
+
hidden: true
|
|
233
|
+
})
|
|
223
234
|
.completion("completion", "Generate bash/zsh completion")
|
|
224
235
|
.array("filter")
|
|
225
236
|
.array("only")
|
|
@@ -582,7 +593,11 @@ const checkPermissions = (filePath) => {
|
|
|
582
593
|
console.log(err);
|
|
583
594
|
}
|
|
584
595
|
}
|
|
585
|
-
|
|
596
|
+
// Protobuf serialization
|
|
597
|
+
if (options.exportProto) {
|
|
598
|
+
const protobomModule = await import("../protobom.js");
|
|
599
|
+
protobomModule.writeBinary(bomNSData.bomJson, options.protoBinFile);
|
|
600
|
+
}
|
|
586
601
|
if (options.print && bomNSData.bomJson && bomNSData.bomJson.components) {
|
|
587
602
|
printDependencyTree(bomNSData.bomJson);
|
|
588
603
|
printTable(bomNSData.bomJson);
|
package/index.js
CHANGED
|
@@ -4048,6 +4048,8 @@ export const createContainerSpecLikeBom = async (path, options) => {
|
|
|
4048
4048
|
* @param options Parse options from the cli
|
|
4049
4049
|
*/
|
|
4050
4050
|
export const createPHPBom = (path, options) => {
|
|
4051
|
+
let dependencies = [];
|
|
4052
|
+
let parentComponent = {};
|
|
4051
4053
|
const composerJsonFiles = getAllFiles(
|
|
4052
4054
|
path,
|
|
4053
4055
|
(options.multiProject ? "**/" : "") + "composer.json",
|
|
@@ -4117,17 +4119,56 @@ export const createPHPBom = (path, options) => {
|
|
|
4117
4119
|
);
|
|
4118
4120
|
if (composerLockFiles.length) {
|
|
4119
4121
|
for (const f of composerLockFiles) {
|
|
4122
|
+
const basePath = dirname(f);
|
|
4120
4123
|
if (DEBUG_MODE) {
|
|
4121
4124
|
console.log(`Parsing ${f}`);
|
|
4122
4125
|
}
|
|
4123
|
-
|
|
4124
|
-
if (
|
|
4125
|
-
|
|
4126
|
+
// Is there a composer.json to find the parent component
|
|
4127
|
+
if (
|
|
4128
|
+
!Object.keys(parentComponent).length &&
|
|
4129
|
+
existsSync(join(basePath, "composer.json"))
|
|
4130
|
+
) {
|
|
4131
|
+
const composerData = JSON.parse(
|
|
4132
|
+
readFileSync(join(basePath, "composer.json"), { encoding: "utf-8" })
|
|
4133
|
+
);
|
|
4134
|
+
const pkgName = composerData.name;
|
|
4135
|
+
if (pkgName) {
|
|
4136
|
+
parentComponent.group = dirname(pkgName);
|
|
4137
|
+
if (parentComponent.group === ".") {
|
|
4138
|
+
parentComponent.group = "";
|
|
4139
|
+
}
|
|
4140
|
+
parentComponent.name = basename(pkgName);
|
|
4141
|
+
parentComponent.type = "application";
|
|
4142
|
+
parentComponent.version = composerData.version || "latest";
|
|
4143
|
+
parentComponent["bom-ref"] = decodeURIComponent(
|
|
4144
|
+
new PackageURL(
|
|
4145
|
+
"composer",
|
|
4146
|
+
parentComponent.group,
|
|
4147
|
+
parentComponent.name,
|
|
4148
|
+
parentComponent.version,
|
|
4149
|
+
null,
|
|
4150
|
+
null
|
|
4151
|
+
).toString()
|
|
4152
|
+
);
|
|
4153
|
+
}
|
|
4154
|
+
}
|
|
4155
|
+
const retMap = parseComposerLock(f);
|
|
4156
|
+
if (retMap.pkgList && retMap.pkgList.length) {
|
|
4157
|
+
pkgList = pkgList.concat(retMap.pkgList);
|
|
4158
|
+
}
|
|
4159
|
+
if (retMap.dependenciesList) {
|
|
4160
|
+
dependencies = mergeDependencies(
|
|
4161
|
+
dependencies,
|
|
4162
|
+
retMap.dependenciesList,
|
|
4163
|
+
parentComponent
|
|
4164
|
+
);
|
|
4126
4165
|
}
|
|
4127
4166
|
}
|
|
4128
4167
|
return buildBomNSData(options, pkgList, "composer", {
|
|
4129
4168
|
src: path,
|
|
4130
|
-
filename: composerLockFiles.join(", ")
|
|
4169
|
+
filename: composerLockFiles.join(", "),
|
|
4170
|
+
dependencies,
|
|
4171
|
+
parentComponent
|
|
4131
4172
|
});
|
|
4132
4173
|
}
|
|
4133
4174
|
return {};
|
|
@@ -4331,7 +4372,7 @@ export const createCsharpBom = async (
|
|
|
4331
4372
|
console.log(`Parsing ${f}`);
|
|
4332
4373
|
}
|
|
4333
4374
|
pkgData = readFileSync(f, { encoding: "utf-8" });
|
|
4334
|
-
const results = await parsePaketLockData(pkgData);
|
|
4375
|
+
const results = await parsePaketLockData(pkgData, f);
|
|
4335
4376
|
const dlist = results.pkgList;
|
|
4336
4377
|
const deps = results.dependenciesList;
|
|
4337
4378
|
if (dlist && dlist.length) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyclonedx/cdxgen",
|
|
3
|
-
"version": "9.10.
|
|
3
|
+
"version": "9.10.2",
|
|
4
4
|
"description": "Creates CycloneDX Software Bill of Materials (SBOM) from source or container image",
|
|
5
5
|
"homepage": "http://github.com/cyclonedx/cdxgen",
|
|
6
6
|
"author": "Prabhu Subramanian <prabhu@appthreat.com>",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"@babel/parser": "^7.23.6",
|
|
59
59
|
"@babel/traverse": "^7.23.6",
|
|
60
|
-
"@npmcli/arborist": "7.2.
|
|
60
|
+
"@npmcli/arborist": "7.2.2",
|
|
61
61
|
"ajv": "^8.12.0",
|
|
62
62
|
"ajv-formats": "^2.1.1",
|
|
63
63
|
"cheerio": "^1.0.0-rc.12",
|
|
@@ -83,7 +83,8 @@
|
|
|
83
83
|
"yargs": "^17.7.2"
|
|
84
84
|
},
|
|
85
85
|
"optionalDependencies": {
|
|
86
|
-
"@appthreat/atom": "1.
|
|
86
|
+
"@appthreat/atom": "1.8.0",
|
|
87
|
+
"@appthreat/cdx-proto": "^0.0.4",
|
|
87
88
|
"@cyclonedx/cdxgen-plugins-bin": "^1.5.4",
|
|
88
89
|
"@cyclonedx/cdxgen-plugins-bin-windows-amd64": "^1.5.4",
|
|
89
90
|
"@cyclonedx/cdxgen-plugins-bin-arm64": "^1.5.4",
|
|
@@ -105,7 +106,7 @@
|
|
|
105
106
|
"docsify-cli": "^4.4.4",
|
|
106
107
|
"eslint": "^8.56.0",
|
|
107
108
|
"eslint-config-prettier": "^9.1.0",
|
|
108
|
-
"eslint-plugin-prettier": "^5.
|
|
109
|
+
"eslint-plugin-prettier": "^5.1.2",
|
|
109
110
|
"jest": "^29.7.0",
|
|
110
111
|
"prettier": "3.1.1"
|
|
111
112
|
}
|
package/protobom.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Bom } from "@appthreat/cdx-proto";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
|
|
4
|
+
const stringifyIfNeeded = (bomJson) => {
|
|
5
|
+
if (typeof bomJson === "string" || bomJson instanceof String) {
|
|
6
|
+
return bomJson;
|
|
7
|
+
}
|
|
8
|
+
return JSON.stringify(bomJson);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const writeBinary = (bomJson, binFile) => {
|
|
12
|
+
if (bomJson && binFile) {
|
|
13
|
+
const bomObject = new Bom();
|
|
14
|
+
writeFileSync(
|
|
15
|
+
binFile,
|
|
16
|
+
bomObject
|
|
17
|
+
.fromJsonString(stringifyIfNeeded(bomJson), {
|
|
18
|
+
ignoreUnknownFields: true
|
|
19
|
+
})
|
|
20
|
+
.toBinary({ writeUnknownFields: true })
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const readBinary = (binFile, asJson = true) => {
|
|
26
|
+
if (!existsSync(binFile)) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
const bomObject = new Bom().fromBinary(readFileSync(binFile), {
|
|
30
|
+
readUnknownFields: true
|
|
31
|
+
});
|
|
32
|
+
if (asJson) {
|
|
33
|
+
return bomObject.toJson({ emitDefaultValues: true });
|
|
34
|
+
}
|
|
35
|
+
return bomObject;
|
|
36
|
+
};
|
package/protobom.test.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { expect, test } from "@jest/globals";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { existsSync, rmSync, mkdtempSync, readFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
|
|
6
|
+
import { writeBinary, readBinary } from "./protobom.js";
|
|
7
|
+
|
|
8
|
+
const tempDir = mkdtempSync(join(tmpdir(), "bin-tests-"));
|
|
9
|
+
const testBom = JSON.parse(
|
|
10
|
+
readFileSync("./test/data/bom-java.json", { encoding: "utf-8" })
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
test("proto binary tests", async () => {
|
|
14
|
+
const binFile = join(tempDir, "test.cdx.bin");
|
|
15
|
+
writeBinary({}, binFile);
|
|
16
|
+
expect(existsSync(binFile)).toBeTruthy();
|
|
17
|
+
writeBinary(testBom, binFile);
|
|
18
|
+
expect(existsSync(binFile)).toBeTruthy();
|
|
19
|
+
let bomObject = readBinary(binFile);
|
|
20
|
+
expect(bomObject).toBeDefined();
|
|
21
|
+
expect(bomObject.serialNumber).toEqual(
|
|
22
|
+
"urn:uuid:cc8b5a04-2698-4375-b04c-cedfa4317fee"
|
|
23
|
+
);
|
|
24
|
+
bomObject = readBinary(binFile, false);
|
|
25
|
+
expect(bomObject).toBeDefined();
|
|
26
|
+
expect(bomObject.serialNumber).toEqual(
|
|
27
|
+
"urn:uuid:cc8b5a04-2698-4375-b04c-cedfa4317fee"
|
|
28
|
+
);
|
|
29
|
+
if (tempDir && tempDir.startsWith(tmpdir()) && rmSync) {
|
|
30
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
31
|
+
}
|
|
32
|
+
});
|
package/utils.js
CHANGED
|
@@ -666,6 +666,12 @@ export const parsePkgLock = async (pkgLockFile, options = {}) => {
|
|
|
666
666
|
purl: purlString,
|
|
667
667
|
"bom-ref": decodeURIComponent(purlString)
|
|
668
668
|
};
|
|
669
|
+
if (node.resolved) {
|
|
670
|
+
pkg.properties.push({
|
|
671
|
+
name: "ResolvedUrl",
|
|
672
|
+
value: node.resolved
|
|
673
|
+
});
|
|
674
|
+
}
|
|
669
675
|
}
|
|
670
676
|
const packageLicense = node.package.license;
|
|
671
677
|
if (packageLicense) {
|
|
@@ -694,8 +700,9 @@ export const parsePkgLock = async (pkgLockFile, options = {}) => {
|
|
|
694
700
|
null
|
|
695
701
|
).toString()
|
|
696
702
|
);
|
|
697
|
-
|
|
698
|
-
|
|
703
|
+
if (decodeURIComponent(purlString) !== depWorkspacePurlString) {
|
|
704
|
+
workspaceDependsOn.push(depWorkspacePurlString);
|
|
705
|
+
}
|
|
699
706
|
}
|
|
700
707
|
}
|
|
701
708
|
|
|
@@ -726,7 +733,9 @@ export const parsePkgLock = async (pkgLockFile, options = {}) => {
|
|
|
726
733
|
null
|
|
727
734
|
).toString()
|
|
728
735
|
);
|
|
729
|
-
|
|
736
|
+
if (decodeURIComponent(purlString) !== depChildString) {
|
|
737
|
+
childrenDependsOn.push(depChildString);
|
|
738
|
+
}
|
|
730
739
|
}
|
|
731
740
|
}
|
|
732
741
|
|
|
@@ -735,31 +744,35 @@ export const parsePkgLock = async (pkgLockFile, options = {}) => {
|
|
|
735
744
|
for (const edge of node.edgesOut.values()) {
|
|
736
745
|
let targetVersion;
|
|
737
746
|
let targetName;
|
|
738
|
-
|
|
747
|
+
let foundMatch = false;
|
|
739
748
|
// if the edge doesn't have an integrity, it's likely a peer dependency
|
|
740
749
|
// which isn't installed
|
|
741
|
-
|
|
742
|
-
//
|
|
743
|
-
|
|
750
|
+
// Bug #795. At times, npm loses the integrity node completely and such packages are getting missed out
|
|
751
|
+
// To keep things safe, we include these packages.
|
|
752
|
+
let edgeToIntegrity = edge.to ? edge.to.integrity : undefined;
|
|
744
753
|
if (!edgeToIntegrity) {
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
754
|
+
// This hack is required to fix the package name
|
|
755
|
+
targetName = node.name.replace(/-cjs$/, "");
|
|
756
|
+
targetVersion = node.version;
|
|
757
|
+
foundMatch = true;
|
|
758
|
+
} else {
|
|
759
|
+
// the edges don't actually contain a version, so we need to search the root node
|
|
760
|
+
// children to find the correct version. we check the node children first, then
|
|
761
|
+
// we check the root node children
|
|
762
|
+
for (const child of node.children) {
|
|
763
|
+
if (edgeToIntegrity) {
|
|
764
|
+
if (child[1].integrity == edgeToIntegrity) {
|
|
765
|
+
targetName = child[0].replace(/node_modules\//g, "");
|
|
766
|
+
// The package name could be different from the targetName retrieved
|
|
767
|
+
// Eg: "string-width-cjs": "npm:string-width@^4.2.0",
|
|
768
|
+
if (child[1].packageName && child[1].packageName !== targetName) {
|
|
769
|
+
targetName = child[1].packageName;
|
|
770
|
+
}
|
|
771
|
+
targetVersion = child[1].version;
|
|
772
|
+
foundMatch = true;
|
|
773
|
+
break;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
763
776
|
}
|
|
764
777
|
}
|
|
765
778
|
if (!foundMatch) {
|
|
@@ -792,8 +805,12 @@ export const parsePkgLock = async (pkgLockFile, options = {}) => {
|
|
|
792
805
|
null
|
|
793
806
|
).toString()
|
|
794
807
|
);
|
|
795
|
-
|
|
796
|
-
|
|
808
|
+
if (decodeURIComponent(purlString) !== depPurlString) {
|
|
809
|
+
pkgDependsOn.push(depPurlString);
|
|
810
|
+
}
|
|
811
|
+
if (edge.to == null) {
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
797
814
|
const { pkgList: childPkgList, dependenciesList: childDependenciesList } =
|
|
798
815
|
parseArboristNode(
|
|
799
816
|
edge.to,
|
|
@@ -1351,6 +1368,8 @@ export const parsePnpmLock = async function (pnpmLock, parentComponent = null) {
|
|
|
1351
1368
|
group: group,
|
|
1352
1369
|
name: name,
|
|
1353
1370
|
version: version,
|
|
1371
|
+
purl: purlString,
|
|
1372
|
+
"bom-ref": decodeURIComponent(purlString),
|
|
1354
1373
|
scope,
|
|
1355
1374
|
_integrity: integrity,
|
|
1356
1375
|
properties: [
|
|
@@ -2366,7 +2385,8 @@ export const guessLicenseId = function (content) {
|
|
|
2366
2385
|
export const getMvnMetadata = async function (pkgList, jarNSMapping = {}) {
|
|
2367
2386
|
const MAVEN_CENTRAL_URL =
|
|
2368
2387
|
process.env.MAVEN_CENTRAL_URL || "https://repo1.maven.org/maven2/";
|
|
2369
|
-
const
|
|
2388
|
+
const ANDROID_MAVEN_URL =
|
|
2389
|
+
process.env.ANDROID_MAVEN_URL || "https://maven.google.com/";
|
|
2370
2390
|
const cdepList = [];
|
|
2371
2391
|
if (!pkgList || !pkgList.length) {
|
|
2372
2392
|
return pkgList;
|
|
@@ -2414,7 +2434,7 @@ export const getMvnMetadata = async function (pkgList, jarNSMapping = {}) {
|
|
|
2414
2434
|
let urlPrefix = MAVEN_CENTRAL_URL;
|
|
2415
2435
|
// Ideally we should try one resolver after the other. But it increases the time taken
|
|
2416
2436
|
if (group.indexOf("android") !== -1) {
|
|
2417
|
-
urlPrefix =
|
|
2437
|
+
urlPrefix = ANDROID_MAVEN_URL;
|
|
2418
2438
|
}
|
|
2419
2439
|
// Querying maven requires a valid group name
|
|
2420
2440
|
if (!group || group === "") {
|
|
@@ -5574,7 +5594,7 @@ export const parseCsPkgLockData = async function (csLockData) {
|
|
|
5574
5594
|
return pkgList;
|
|
5575
5595
|
};
|
|
5576
5596
|
|
|
5577
|
-
export const parsePaketLockData = async function (paketLockData) {
|
|
5597
|
+
export const parsePaketLockData = async function (paketLockData, pkgLockFile) {
|
|
5578
5598
|
const pkgList = [];
|
|
5579
5599
|
const dependenciesList = [];
|
|
5580
5600
|
const dependenciesMap = {};
|
|
@@ -5602,14 +5622,39 @@ export const parsePaketLockData = async function (paketLockData) {
|
|
|
5602
5622
|
if (match) {
|
|
5603
5623
|
const name = match[1];
|
|
5604
5624
|
const version = match[2];
|
|
5605
|
-
const purl =
|
|
5606
|
-
|
|
5607
|
-
|
|
5625
|
+
const purl = new PackageURL(
|
|
5626
|
+
"nuget",
|
|
5627
|
+
"",
|
|
5628
|
+
name,
|
|
5629
|
+
version,
|
|
5630
|
+
null,
|
|
5631
|
+
null
|
|
5632
|
+
).toString();
|
|
5608
5633
|
pkg = {
|
|
5609
5634
|
group: "",
|
|
5610
|
-
name
|
|
5611
|
-
version
|
|
5612
|
-
purl
|
|
5635
|
+
name,
|
|
5636
|
+
version,
|
|
5637
|
+
purl,
|
|
5638
|
+
"bom-ref": decodeURIComponent(purl),
|
|
5639
|
+
properties: [
|
|
5640
|
+
{
|
|
5641
|
+
name: "SrcFile",
|
|
5642
|
+
value: pkgLockFile
|
|
5643
|
+
}
|
|
5644
|
+
],
|
|
5645
|
+
evidence: {
|
|
5646
|
+
identity: {
|
|
5647
|
+
field: "purl",
|
|
5648
|
+
confidence: 1,
|
|
5649
|
+
methods: [
|
|
5650
|
+
{
|
|
5651
|
+
technique: "manifest-analysis",
|
|
5652
|
+
confidence: 1,
|
|
5653
|
+
value: pkgLockFile
|
|
5654
|
+
}
|
|
5655
|
+
]
|
|
5656
|
+
}
|
|
5657
|
+
}
|
|
5613
5658
|
};
|
|
5614
5659
|
pkgList.push(pkg);
|
|
5615
5660
|
dependenciesMap[purl] = new Set();
|
|
@@ -5661,6 +5706,7 @@ export const parsePaketLockData = async function (paketLockData) {
|
|
|
5661
5706
|
dependenciesList
|
|
5662
5707
|
};
|
|
5663
5708
|
};
|
|
5709
|
+
|
|
5664
5710
|
/**
|
|
5665
5711
|
* Parse composer lock file
|
|
5666
5712
|
*
|
|
@@ -5668,6 +5714,9 @@ export const parsePaketLockData = async function (paketLockData) {
|
|
|
5668
5714
|
*/
|
|
5669
5715
|
export const parseComposerLock = function (pkgLockFile) {
|
|
5670
5716
|
const pkgList = [];
|
|
5717
|
+
const dependenciesList = [];
|
|
5718
|
+
const dependenciesMap = {};
|
|
5719
|
+
const pkgNamePurlMap = {};
|
|
5671
5720
|
if (existsSync(pkgLockFile)) {
|
|
5672
5721
|
let lockData = {};
|
|
5673
5722
|
try {
|
|
@@ -5684,6 +5733,7 @@ export const parseComposerLock = function (pkgLockFile) {
|
|
|
5684
5733
|
if (lockData["packages-dev"]) {
|
|
5685
5734
|
packages["optional"] = lockData["packages-dev"];
|
|
5686
5735
|
}
|
|
5736
|
+
// Pass 1: Collect all packages
|
|
5687
5737
|
for (const compScope in packages) {
|
|
5688
5738
|
for (const i in packages[compScope]) {
|
|
5689
5739
|
const pkg = packages[compScope][i];
|
|
@@ -5696,14 +5746,20 @@ export const parseComposerLock = function (pkgLockFile) {
|
|
|
5696
5746
|
group = "";
|
|
5697
5747
|
}
|
|
5698
5748
|
const name = basename(pkg.name);
|
|
5699
|
-
|
|
5749
|
+
const purl = new PackageURL(
|
|
5750
|
+
"composer",
|
|
5751
|
+
group,
|
|
5752
|
+
name,
|
|
5753
|
+
pkg.version,
|
|
5754
|
+
null,
|
|
5755
|
+
null
|
|
5756
|
+
).toString();
|
|
5757
|
+
const apkg = {
|
|
5700
5758
|
group: group,
|
|
5701
5759
|
name: name,
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
// which has it's own workaround. Or when the 231 bug is fixed.
|
|
5706
|
-
version: pkg.version.replace(/^v/, ""),
|
|
5760
|
+
purl,
|
|
5761
|
+
"bom-ref": decodeURIComponent(purl),
|
|
5762
|
+
version: pkg.version,
|
|
5707
5763
|
repository: pkg.source,
|
|
5708
5764
|
license: pkg.license,
|
|
5709
5765
|
description: pkg.description,
|
|
@@ -5727,12 +5783,58 @@ export const parseComposerLock = function (pkgLockFile) {
|
|
|
5727
5783
|
]
|
|
5728
5784
|
}
|
|
5729
5785
|
}
|
|
5730
|
-
}
|
|
5786
|
+
};
|
|
5787
|
+
if (pkg.autoload && Object.keys(pkg.autoload).length) {
|
|
5788
|
+
const namespaces = [];
|
|
5789
|
+
for (const aaload of Object.keys(pkg.autoload)) {
|
|
5790
|
+
if (aaload.startsWith("psr")) {
|
|
5791
|
+
for (const ans of Object.keys(pkg.autoload[aaload])) {
|
|
5792
|
+
namespaces.push(ans);
|
|
5793
|
+
}
|
|
5794
|
+
}
|
|
5795
|
+
}
|
|
5796
|
+
if (namespaces.length) {
|
|
5797
|
+
apkg.properties.push({
|
|
5798
|
+
name: "Namespaces",
|
|
5799
|
+
value: namespaces.join(", ")
|
|
5800
|
+
});
|
|
5801
|
+
}
|
|
5802
|
+
}
|
|
5803
|
+
pkgList.push(apkg);
|
|
5804
|
+
dependenciesMap[purl] = new Set();
|
|
5805
|
+
pkgNamePurlMap[pkg.name] = purl;
|
|
5806
|
+
}
|
|
5807
|
+
}
|
|
5808
|
+
// Pass 2: Construct dependency tree
|
|
5809
|
+
for (const compScope in packages) {
|
|
5810
|
+
for (const i in packages[compScope]) {
|
|
5811
|
+
const pkg = packages[compScope][i];
|
|
5812
|
+
if (!pkg || !pkg.name || !pkg.version) {
|
|
5813
|
+
continue;
|
|
5814
|
+
}
|
|
5815
|
+
if (!pkg.require || !Object.keys(pkg.require).length) {
|
|
5816
|
+
continue;
|
|
5817
|
+
}
|
|
5818
|
+
const purl = pkgNamePurlMap[pkg.name];
|
|
5819
|
+
for (const adepName of Object.keys(pkg.require)) {
|
|
5820
|
+
if (pkgNamePurlMap[adepName]) {
|
|
5821
|
+
dependenciesMap[purl].add(pkgNamePurlMap[adepName]);
|
|
5822
|
+
}
|
|
5823
|
+
}
|
|
5731
5824
|
}
|
|
5732
5825
|
}
|
|
5733
5826
|
}
|
|
5734
5827
|
}
|
|
5735
|
-
|
|
5828
|
+
for (const ref in dependenciesMap) {
|
|
5829
|
+
dependenciesList.push({
|
|
5830
|
+
ref: ref,
|
|
5831
|
+
dependsOn: Array.from(dependenciesMap[ref])
|
|
5832
|
+
});
|
|
5833
|
+
}
|
|
5834
|
+
return {
|
|
5835
|
+
pkgList,
|
|
5836
|
+
dependenciesList
|
|
5837
|
+
};
|
|
5736
5838
|
};
|
|
5737
5839
|
|
|
5738
5840
|
export const parseSbtTree = (sbtTreeFile) => {
|
package/utils.test.js
CHANGED
|
@@ -1351,14 +1351,30 @@ test("parse paket.lock", async () => {
|
|
|
1351
1351
|
dependenciesList: []
|
|
1352
1352
|
});
|
|
1353
1353
|
const dep_list = await parsePaketLockData(
|
|
1354
|
-
readFileSync("./test/data/paket.lock", { encoding: "utf-8" })
|
|
1354
|
+
readFileSync("./test/data/paket.lock", { encoding: "utf-8" }),
|
|
1355
|
+
"./test/data/paket.lock"
|
|
1355
1356
|
);
|
|
1356
1357
|
expect(dep_list.pkgList.length).toEqual(13);
|
|
1357
1358
|
expect(dep_list.pkgList[0]).toEqual({
|
|
1358
1359
|
group: "",
|
|
1359
1360
|
name: "0x53A.ReferenceAssemblies.Paket",
|
|
1360
1361
|
version: "0.2",
|
|
1361
|
-
purl: "pkg:nuget/0x53A.ReferenceAssemblies.Paket@0.2"
|
|
1362
|
+
purl: "pkg:nuget/0x53A.ReferenceAssemblies.Paket@0.2",
|
|
1363
|
+
"bom-ref": "pkg:nuget/0x53A.ReferenceAssemblies.Paket@0.2",
|
|
1364
|
+
properties: [{ name: "SrcFile", value: "./test/data/paket.lock" }],
|
|
1365
|
+
evidence: {
|
|
1366
|
+
identity: {
|
|
1367
|
+
field: "purl",
|
|
1368
|
+
confidence: 1,
|
|
1369
|
+
methods: [
|
|
1370
|
+
{
|
|
1371
|
+
technique: "manifest-analysis",
|
|
1372
|
+
confidence: 1,
|
|
1373
|
+
value: "./test/data/paket.lock"
|
|
1374
|
+
}
|
|
1375
|
+
]
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1362
1378
|
});
|
|
1363
1379
|
expect(dep_list.dependenciesList.length).toEqual(13);
|
|
1364
1380
|
expect(dep_list.dependenciesList[2]).toEqual({
|
|
@@ -1694,7 +1710,7 @@ test("parsePkgLock v2 workspace", async () => {
|
|
|
1694
1710
|
);
|
|
1695
1711
|
let pkgs = parsedList.pkgList;
|
|
1696
1712
|
let deps = parsedList.dependenciesList;
|
|
1697
|
-
expect(pkgs.length).toEqual(
|
|
1713
|
+
expect(pkgs.length).toEqual(1034);
|
|
1698
1714
|
expect(pkgs[0].license).toEqual("MIT");
|
|
1699
1715
|
let hasAppWorkspacePkg = pkgs.some(
|
|
1700
1716
|
(obj) => obj["bom-ref"] === "pkg:npm/app@0.0.0"
|
|
@@ -1746,8 +1762,8 @@ test("parsePkgLock v3", async () => {
|
|
|
1746
1762
|
projectName: "cdxgen"
|
|
1747
1763
|
});
|
|
1748
1764
|
deps = parsedList.pkgList;
|
|
1749
|
-
expect(deps.length).toEqual(
|
|
1750
|
-
expect(parsedList.dependenciesList.length).toEqual(
|
|
1765
|
+
expect(deps.length).toEqual(1195);
|
|
1766
|
+
expect(parsedList.dependenciesList.length).toEqual(1195);
|
|
1751
1767
|
});
|
|
1752
1768
|
|
|
1753
1769
|
test("parseBowerJson", async () => {
|
|
@@ -1807,6 +1823,8 @@ test("parsePnpmLock", async () => {
|
|
|
1807
1823
|
"sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==",
|
|
1808
1824
|
group: "@babel",
|
|
1809
1825
|
name: "code-frame",
|
|
1826
|
+
"bom-ref": "pkg:npm/@babel/code-frame@7.10.1",
|
|
1827
|
+
purl: "pkg:npm/%40babel/code-frame@7.10.1",
|
|
1810
1828
|
scope: undefined,
|
|
1811
1829
|
version: "7.10.1",
|
|
1812
1830
|
properties: [
|
|
@@ -1837,6 +1855,8 @@ test("parsePnpmLock", async () => {
|
|
|
1837
1855
|
"sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==",
|
|
1838
1856
|
group: "@babel",
|
|
1839
1857
|
name: "code-frame",
|
|
1858
|
+
"bom-ref": "pkg:npm/@babel/code-frame@7.16.7",
|
|
1859
|
+
purl: "pkg:npm/%40babel/code-frame@7.16.7",
|
|
1840
1860
|
scope: "optional",
|
|
1841
1861
|
version: "7.16.7",
|
|
1842
1862
|
properties: [
|
|
@@ -1866,6 +1886,8 @@ test("parsePnpmLock", async () => {
|
|
|
1866
1886
|
group: "",
|
|
1867
1887
|
name: "ansi-regex",
|
|
1868
1888
|
version: "2.1.1",
|
|
1889
|
+
"bom-ref": "pkg:npm/ansi-regex@2.1.1",
|
|
1890
|
+
purl: "pkg:npm/ansi-regex@2.1.1",
|
|
1869
1891
|
scope: undefined,
|
|
1870
1892
|
_integrity: "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
|
|
1871
1893
|
properties: [{ name: "SrcFile", value: "./test/data/pnpm-lock2.yaml" }],
|
|
@@ -1900,6 +1922,8 @@ test("parsePnpmLock", async () => {
|
|
|
1900
1922
|
group: "@nodelib",
|
|
1901
1923
|
name: "fs.scandir",
|
|
1902
1924
|
version: "2.1.5",
|
|
1925
|
+
"bom-ref": "pkg:npm/@nodelib/fs.scandir@2.1.5",
|
|
1926
|
+
purl: "pkg:npm/%40nodelib/fs.scandir@2.1.5",
|
|
1903
1927
|
scope: undefined,
|
|
1904
1928
|
_integrity:
|
|
1905
1929
|
"sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
|
|
@@ -1933,6 +1957,8 @@ test("parsePnpmLock", async () => {
|
|
|
1933
1957
|
group: "@babel",
|
|
1934
1958
|
name: "code-frame",
|
|
1935
1959
|
version: "7.18.6",
|
|
1960
|
+
"bom-ref": "pkg:npm/@babel/code-frame@7.18.6",
|
|
1961
|
+
purl: "pkg:npm/%40babel/code-frame@7.18.6",
|
|
1936
1962
|
scope: "optional",
|
|
1937
1963
|
_integrity:
|
|
1938
1964
|
"sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
|
|
@@ -1955,6 +1981,8 @@ test("parsePnpmLock", async () => {
|
|
|
1955
1981
|
group: "",
|
|
1956
1982
|
name: "yargs",
|
|
1957
1983
|
version: "17.7.1",
|
|
1984
|
+
"bom-ref": "pkg:npm/yargs@17.7.1",
|
|
1985
|
+
purl: "pkg:npm/yargs@17.7.1",
|
|
1958
1986
|
scope: "optional",
|
|
1959
1987
|
_integrity:
|
|
1960
1988
|
"sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==",
|
|
@@ -1980,6 +2008,8 @@ test("parsePnpmLock", async () => {
|
|
|
1980
2008
|
group: "@babel",
|
|
1981
2009
|
name: "code-frame",
|
|
1982
2010
|
version: "7.18.6",
|
|
2011
|
+
"bom-ref": "pkg:npm/@babel/code-frame@7.18.6",
|
|
2012
|
+
purl: "pkg:npm/%40babel/code-frame@7.18.6",
|
|
1983
2013
|
scope: "optional",
|
|
1984
2014
|
_integrity:
|
|
1985
2015
|
"sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
|
|
@@ -2301,13 +2331,16 @@ test("parseYarnLock", async () => {
|
|
|
2301
2331
|
});
|
|
2302
2332
|
|
|
2303
2333
|
test("parseComposerLock", () => {
|
|
2304
|
-
let
|
|
2305
|
-
expect(
|
|
2306
|
-
expect(
|
|
2334
|
+
let retMap = parseComposerLock("./test/data/composer.lock");
|
|
2335
|
+
expect(retMap.pkgList.length).toEqual(1);
|
|
2336
|
+
expect(retMap.dependenciesList.length).toEqual(1);
|
|
2337
|
+
expect(retMap.pkgList[0]).toEqual({
|
|
2307
2338
|
group: "quickbooks",
|
|
2308
2339
|
name: "v3-php-sdk",
|
|
2309
2340
|
scope: "required",
|
|
2310
|
-
version: "
|
|
2341
|
+
version: "v4.0.6.1",
|
|
2342
|
+
purl: "pkg:composer/quickbooks/v3-php-sdk@v4.0.6.1",
|
|
2343
|
+
"bom-ref": "pkg:composer/quickbooks/v3-php-sdk@v4.0.6.1",
|
|
2311
2344
|
repository: {
|
|
2312
2345
|
type: "git",
|
|
2313
2346
|
url: "https://github.com/intuit/QuickBooks-V3-PHP-SDK.git",
|
|
@@ -2319,6 +2352,10 @@ test("parseComposerLock", () => {
|
|
|
2319
2352
|
{
|
|
2320
2353
|
name: "SrcFile",
|
|
2321
2354
|
value: "./test/data/composer.lock"
|
|
2355
|
+
},
|
|
2356
|
+
{
|
|
2357
|
+
name: "Namespaces",
|
|
2358
|
+
value: "QuickBooksOnline\\API\\"
|
|
2322
2359
|
}
|
|
2323
2360
|
],
|
|
2324
2361
|
evidence: {
|
|
@@ -2336,13 +2373,16 @@ test("parseComposerLock", () => {
|
|
|
2336
2373
|
}
|
|
2337
2374
|
});
|
|
2338
2375
|
|
|
2339
|
-
|
|
2340
|
-
expect(
|
|
2341
|
-
expect(
|
|
2376
|
+
retMap = parseComposerLock("./test/data/composer-2.lock");
|
|
2377
|
+
expect(retMap.pkgList.length).toEqual(73);
|
|
2378
|
+
expect(retMap.dependenciesList.length).toEqual(73);
|
|
2379
|
+
expect(retMap.pkgList[0]).toEqual({
|
|
2342
2380
|
group: "amphp",
|
|
2343
2381
|
name: "amp",
|
|
2344
2382
|
scope: "required",
|
|
2345
|
-
version: "
|
|
2383
|
+
version: "v2.4.4",
|
|
2384
|
+
purl: "pkg:composer/amphp/amp@v2.4.4",
|
|
2385
|
+
"bom-ref": "pkg:composer/amphp/amp@v2.4.4",
|
|
2346
2386
|
repository: {
|
|
2347
2387
|
type: "git",
|
|
2348
2388
|
url: "https://github.com/amphp/amp.git",
|
|
@@ -2354,6 +2394,10 @@ test("parseComposerLock", () => {
|
|
|
2354
2394
|
{
|
|
2355
2395
|
name: "SrcFile",
|
|
2356
2396
|
value: "./test/data/composer-2.lock"
|
|
2397
|
+
},
|
|
2398
|
+
{
|
|
2399
|
+
name: "Namespaces",
|
|
2400
|
+
value: "Amp\\"
|
|
2357
2401
|
}
|
|
2358
2402
|
],
|
|
2359
2403
|
evidence: {
|
|
@@ -2371,12 +2415,15 @@ test("parseComposerLock", () => {
|
|
|
2371
2415
|
}
|
|
2372
2416
|
});
|
|
2373
2417
|
|
|
2374
|
-
|
|
2375
|
-
expect(
|
|
2376
|
-
expect(
|
|
2418
|
+
retMap = parseComposerLock("./test/data/composer-3.lock");
|
|
2419
|
+
expect(retMap.pkgList.length).toEqual(62);
|
|
2420
|
+
expect(retMap.dependenciesList.length).toEqual(62);
|
|
2421
|
+
expect(retMap.pkgList[0]).toEqual({
|
|
2377
2422
|
group: "amphp",
|
|
2378
2423
|
name: "amp",
|
|
2379
|
-
version: "
|
|
2424
|
+
version: "v2.6.2",
|
|
2425
|
+
purl: "pkg:composer/amphp/amp@v2.6.2",
|
|
2426
|
+
"bom-ref": "pkg:composer/amphp/amp@v2.6.2",
|
|
2380
2427
|
repository: {
|
|
2381
2428
|
type: "git",
|
|
2382
2429
|
url: "https://github.com/amphp/amp.git",
|
|
@@ -2385,7 +2432,13 @@ test("parseComposerLock", () => {
|
|
|
2385
2432
|
license: ["MIT"],
|
|
2386
2433
|
description: "A non-blocking concurrency framework for PHP applications.",
|
|
2387
2434
|
scope: "required",
|
|
2388
|
-
properties: [
|
|
2435
|
+
properties: [
|
|
2436
|
+
{ name: "SrcFile", value: "./test/data/composer-3.lock" },
|
|
2437
|
+
{
|
|
2438
|
+
name: "Namespaces",
|
|
2439
|
+
value: "Amp\\"
|
|
2440
|
+
}
|
|
2441
|
+
],
|
|
2389
2442
|
evidence: {
|
|
2390
2443
|
identity: {
|
|
2391
2444
|
field: "purl",
|
|
@@ -2400,6 +2453,42 @@ test("parseComposerLock", () => {
|
|
|
2400
2453
|
}
|
|
2401
2454
|
}
|
|
2402
2455
|
});
|
|
2456
|
+
retMap = parseComposerLock("./test/data/composer-4.lock");
|
|
2457
|
+
expect(retMap.pkgList.length).toEqual(50);
|
|
2458
|
+
expect(retMap.dependenciesList.length).toEqual(50);
|
|
2459
|
+
expect(retMap.pkgList[0]).toEqual({
|
|
2460
|
+
group: "apache",
|
|
2461
|
+
name: "log4php",
|
|
2462
|
+
purl: "pkg:composer/apache/log4php@2.3.0",
|
|
2463
|
+
"bom-ref": "pkg:composer/apache/log4php@2.3.0",
|
|
2464
|
+
version: "2.3.0",
|
|
2465
|
+
repository: {
|
|
2466
|
+
type: "git",
|
|
2467
|
+
url: "https://git-wip-us.apache.org/repos/asf/logging-log4php.git",
|
|
2468
|
+
reference: "8c6df2481cd68d0d211d38f700406c5f0a9de0c2"
|
|
2469
|
+
},
|
|
2470
|
+
license: ["Apache-2.0"],
|
|
2471
|
+
description: "A versatile logging framework for PHP",
|
|
2472
|
+
scope: "required",
|
|
2473
|
+
properties: [{ name: "SrcFile", value: "./test/data/composer-4.lock" }],
|
|
2474
|
+
evidence: {
|
|
2475
|
+
identity: {
|
|
2476
|
+
field: "purl",
|
|
2477
|
+
confidence: 1,
|
|
2478
|
+
methods: [
|
|
2479
|
+
{
|
|
2480
|
+
confidence: 1,
|
|
2481
|
+
technique: "manifest-analysis",
|
|
2482
|
+
value: "./test/data/composer-4.lock"
|
|
2483
|
+
}
|
|
2484
|
+
]
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
});
|
|
2488
|
+
expect(retMap.dependenciesList[1]).toEqual({
|
|
2489
|
+
ref: "pkg:composer/doctrine/annotations@v1.2.1",
|
|
2490
|
+
dependsOn: ["pkg:composer/doctrine/lexer@v1.0"]
|
|
2491
|
+
});
|
|
2403
2492
|
});
|
|
2404
2493
|
|
|
2405
2494
|
test("parseGemfileLockData", async () => {
|