@cyclonedx/cdxgen 12.3.3 → 12.4.1
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 +69 -25
- package/bin/audit.js +21 -7
- package/bin/cdxgen.js +270 -127
- package/bin/convert.js +34 -15
- package/bin/hbom.js +495 -0
- package/bin/repl.js +592 -37
- package/bin/validate.js +31 -4
- package/bin/verify.js +18 -5
- package/data/README.md +298 -25
- package/data/component-tags.json +6 -0
- package/data/crypto-oid.json +16 -0
- package/data/cyclonedx-2.0-bundled.schema.json +7182 -0
- package/data/predictive-audit-allowlist.json +11 -0
- package/data/queries-darwin.json +12 -1
- package/data/queries-win.json +7 -1
- package/data/queries.json +39 -2
- package/data/rules/ai-agent-governance.yaml +16 -0
- package/data/rules/asar-archives.yaml +150 -0
- package/data/rules/chrome-extensions.yaml +8 -0
- package/data/rules/ci-permissions.yaml +42 -18
- package/data/rules/container-risk.yaml +14 -7
- package/data/rules/dependency-sources.yaml +11 -0
- package/data/rules/hbom-compliance.yaml +325 -0
- package/data/rules/hbom-performance.yaml +307 -0
- package/data/rules/hbom-security.yaml +248 -0
- package/data/rules/host-topology.yaml +165 -0
- package/data/rules/mcp-servers.yaml +18 -3
- package/data/rules/obom-runtime.yaml +907 -22
- package/data/rules/package-integrity.yaml +14 -0
- package/data/rules/rootfs-hardening.yaml +179 -0
- package/data/rules/vscode-extensions.yaml +9 -0
- package/lib/audit/index.js +210 -8
- package/lib/audit/index.poku.js +332 -0
- package/lib/audit/reporters.js +222 -0
- package/lib/audit/targets.js +146 -1
- package/lib/audit/targets.poku.js +186 -0
- package/lib/cli/asar.poku.js +328 -0
- package/lib/cli/index.js +527 -99
- package/lib/cli/index.poku.js +1469 -212
- package/lib/evinser/evinser.js +14 -9
- package/lib/helpers/analyzer.js +1406 -29
- package/lib/helpers/analyzer.poku.js +342 -0
- package/lib/helpers/analyzerScope.js +712 -0
- package/lib/helpers/asarutils.js +1556 -0
- package/lib/helpers/asarutils.poku.js +443 -0
- package/lib/helpers/auditCategories.js +12 -0
- package/lib/helpers/auditCategories.poku.js +32 -0
- package/lib/helpers/bomUtils.js +155 -1
- package/lib/helpers/bomUtils.poku.js +79 -1
- package/lib/helpers/cbomutils.js +271 -1
- package/lib/helpers/cbomutils.poku.js +248 -5
- package/lib/helpers/display.js +291 -1
- package/lib/helpers/display.poku.js +149 -0
- package/lib/helpers/evidenceUtils.js +58 -0
- package/lib/helpers/evidenceUtils.poku.js +54 -0
- package/lib/helpers/exportUtils.js +9 -0
- package/lib/helpers/gtfobins.js +142 -8
- package/lib/helpers/gtfobins.poku.js +24 -1
- package/lib/helpers/hbom.js +710 -0
- package/lib/helpers/hbom.poku.js +496 -0
- package/lib/helpers/hbomAnalysis.js +268 -0
- package/lib/helpers/hbomAnalysis.poku.js +249 -0
- package/lib/helpers/hbomLoader.js +35 -0
- package/lib/helpers/hostTopology.js +803 -0
- package/lib/helpers/hostTopology.poku.js +363 -0
- package/lib/helpers/inventoryStats.js +69 -0
- package/lib/helpers/inventoryStats.poku.js +86 -0
- package/lib/helpers/lolbas.js +19 -1
- package/lib/helpers/lolbas.poku.js +23 -0
- package/lib/helpers/osqueryTransform.js +47 -0
- package/lib/helpers/osqueryTransform.poku.js +47 -0
- package/lib/helpers/plugins.js +350 -0
- package/lib/helpers/plugins.poku.js +57 -0
- package/lib/helpers/protobom.js +209 -45
- package/lib/helpers/protobom.poku.js +183 -5
- package/lib/helpers/protobomLoader.js +43 -0
- package/lib/helpers/protobomLoader.poku.js +31 -0
- package/lib/helpers/remote/dependency-track.js +36 -3
- package/lib/helpers/remote/dependency-track.poku.js +44 -0
- package/lib/helpers/source.js +24 -0
- package/lib/helpers/source.poku.js +32 -0
- package/lib/helpers/utils.js +1438 -93
- package/lib/helpers/utils.poku.js +846 -4
- package/lib/managers/binary.e2e.poku.js +367 -0
- package/lib/managers/binary.js +2293 -353
- package/lib/managers/binary.poku.js +1699 -1
- package/lib/managers/docker.js +201 -79
- package/lib/managers/docker.poku.js +337 -12
- package/lib/server/server.js +4 -28
- package/lib/stages/postgen/annotator.js +38 -0
- package/lib/stages/postgen/annotator.poku.js +107 -1
- package/lib/stages/postgen/auditBom.js +121 -18
- package/lib/stages/postgen/auditBom.poku.js +1366 -31
- package/lib/stages/postgen/hostTopologyAudit.poku.js +186 -0
- package/lib/stages/postgen/postgen.js +406 -8
- package/lib/stages/postgen/postgen.poku.js +484 -0
- package/lib/stages/postgen/ruleEngine.js +116 -0
- package/lib/stages/pregen/envAudit.js +14 -3
- package/lib/validator/bomValidator.js +90 -38
- package/lib/validator/bomValidator.poku.js +90 -0
- package/lib/validator/complianceRules.js +4 -2
- package/lib/validator/index.poku.js +14 -0
- package/package.json +23 -21
- package/types/bin/hbom.d.ts +3 -0
- package/types/bin/hbom.d.ts.map +1 -0
- package/types/bin/repl.d.ts +1 -1
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts +44 -0
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/audit/reporters.d.ts +16 -0
- package/types/lib/audit/reporters.d.ts.map +1 -1
- package/types/lib/audit/targets.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +16 -0
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +4 -0
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/analyzer.d.ts +33 -0
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/analyzerScope.d.ts +11 -0
- package/types/lib/helpers/analyzerScope.d.ts.map +1 -0
- package/types/lib/helpers/asarutils.d.ts +34 -0
- package/types/lib/helpers/asarutils.d.ts.map +1 -0
- package/types/lib/helpers/auditCategories.d.ts +5 -0
- package/types/lib/helpers/auditCategories.d.ts.map +1 -1
- package/types/lib/helpers/bomUtils.d.ts +10 -0
- package/types/lib/helpers/bomUtils.d.ts.map +1 -1
- package/types/lib/helpers/cbomutils.d.ts +3 -2
- package/types/lib/helpers/cbomutils.d.ts.map +1 -1
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/evidenceUtils.d.ts +8 -0
- package/types/lib/helpers/evidenceUtils.d.ts.map +1 -0
- package/types/lib/helpers/exportUtils.d.ts.map +1 -1
- package/types/lib/helpers/gtfobins.d.ts +8 -0
- package/types/lib/helpers/gtfobins.d.ts.map +1 -1
- package/types/lib/helpers/hbom.d.ts +49 -0
- package/types/lib/helpers/hbom.d.ts.map +1 -0
- package/types/lib/helpers/hbomAnalysis.d.ts +76 -0
- package/types/lib/helpers/hbomAnalysis.d.ts.map +1 -0
- package/types/lib/helpers/hbomLoader.d.ts +7 -0
- package/types/lib/helpers/hbomLoader.d.ts.map +1 -0
- package/types/lib/helpers/hostTopology.d.ts +12 -0
- package/types/lib/helpers/hostTopology.d.ts.map +1 -0
- package/types/lib/helpers/inventoryStats.d.ts +11 -0
- package/types/lib/helpers/inventoryStats.d.ts.map +1 -0
- package/types/lib/helpers/lolbas.d.ts.map +1 -1
- package/types/lib/helpers/osqueryTransform.d.ts +3 -0
- package/types/lib/helpers/osqueryTransform.d.ts.map +1 -1
- package/types/lib/helpers/plugins.d.ts +58 -0
- package/types/lib/helpers/plugins.d.ts.map +1 -0
- package/types/lib/helpers/protobom.d.ts +5 -4
- package/types/lib/helpers/protobom.d.ts.map +1 -1
- package/types/lib/helpers/protobomLoader.d.ts +17 -0
- package/types/lib/helpers/protobomLoader.d.ts.map +1 -0
- package/types/lib/helpers/remote/dependency-track.d.ts +10 -3
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
- package/types/lib/helpers/source.d.ts.map +1 -1
- package/types/lib/helpers/utils.d.ts +45 -8
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts +5 -0
- 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 +2 -1
- 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 +26 -1
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts +2 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
- package/types/lib/third-party/arborist/lib/node.d.ts +23 -0
- package/types/lib/third-party/arborist/lib/node.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/complianceRules.d.ts.map +1 -1
- package/data/spdx-model-v3.0.1.jsonld +0 -15999
|
@@ -3,27 +3,51 @@ import { join } from "node:path";
|
|
|
3
3
|
|
|
4
4
|
import { assert, it } from "poku";
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
assertProtoSupportedSpecVersion,
|
|
8
|
+
isProtoBomFile,
|
|
9
|
+
readBinary,
|
|
10
|
+
writeBinary,
|
|
11
|
+
} from "./protobom.js";
|
|
7
12
|
import { getTmpDir } from "./utils.js";
|
|
8
13
|
|
|
9
|
-
const tempDir = mkdtempSync(join(getTmpDir(), "bin-tests-"));
|
|
10
14
|
const testBom = JSON.parse(
|
|
11
15
|
readFileSync("./test/data/bom-java.json", { encoding: "utf-8" }),
|
|
12
16
|
);
|
|
17
|
+
const cbomFixture = JSON.parse(
|
|
18
|
+
readFileSync("./test/data/bom-cbom-js-fixture.json", { encoding: "utf-8" }),
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const createTempDir = () => mkdtempSync(join(getTmpDir(), "bin-tests-"));
|
|
22
|
+
|
|
23
|
+
const cleanupTempDir = (tempDir) => {
|
|
24
|
+
if (tempDir?.startsWith(getTmpDir()) && rmSync) {
|
|
25
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
26
|
+
}
|
|
27
|
+
};
|
|
13
28
|
|
|
14
29
|
it("proto binary tests", () => {
|
|
30
|
+
const tempDir = createTempDir();
|
|
15
31
|
const binFile = join(tempDir, "test.cdx.bin");
|
|
16
32
|
writeBinary({}, binFile);
|
|
17
33
|
assert.deepStrictEqual(existsSync(binFile), true);
|
|
18
34
|
writeBinary(testBom, binFile);
|
|
19
35
|
assert.deepStrictEqual(existsSync(binFile), true);
|
|
36
|
+
assert.equal(isProtoBomFile(binFile), true);
|
|
37
|
+
assert.equal(isProtoBomFile("test.proto"), true);
|
|
38
|
+
assert.equal(isProtoBomFile("bom.json"), false);
|
|
20
39
|
let bomObject = readBinary(binFile);
|
|
21
40
|
assert.ok(bomObject);
|
|
22
41
|
assert.deepStrictEqual(
|
|
23
42
|
bomObject.serialNumber,
|
|
24
43
|
"urn:uuid:cc8b5a04-2698-4375-b04c-cedfa4317fee",
|
|
25
44
|
);
|
|
45
|
+
assert.deepStrictEqual(bomObject.bomFormat, "CycloneDX");
|
|
26
46
|
assert.deepStrictEqual(bomObject.specVersion, "1.5");
|
|
47
|
+
assert.equal(
|
|
48
|
+
bomObject.metadata.component.type.startsWith("CLASSIFICATION_"),
|
|
49
|
+
false,
|
|
50
|
+
);
|
|
27
51
|
bomObject = readBinary(binFile, false, 1.5);
|
|
28
52
|
assert.ok(bomObject);
|
|
29
53
|
assert.deepStrictEqual(
|
|
@@ -31,7 +55,161 @@ it("proto binary tests", () => {
|
|
|
31
55
|
"urn:uuid:cc8b5a04-2698-4375-b04c-cedfa4317fee",
|
|
32
56
|
);
|
|
33
57
|
assert.deepStrictEqual(bomObject.specVersion, "1.5");
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
58
|
+
const modernBinFile = join(tempDir, "test-1.7.cdx");
|
|
59
|
+
writeBinary(
|
|
60
|
+
{
|
|
61
|
+
bomFormat: "CycloneDX",
|
|
62
|
+
metadata: {
|
|
63
|
+
component: {
|
|
64
|
+
name: "cdxgen",
|
|
65
|
+
type: "application",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
serialNumber: "urn:uuid:11111111-1111-1111-1111-111111111111",
|
|
69
|
+
specVersion: "1.7",
|
|
70
|
+
version: 1,
|
|
71
|
+
},
|
|
72
|
+
modernBinFile,
|
|
73
|
+
);
|
|
74
|
+
const modernBomObject = readBinary(modernBinFile);
|
|
75
|
+
assert.ok(modernBomObject);
|
|
76
|
+
assert.deepStrictEqual(modernBomObject.bomFormat, "CycloneDX");
|
|
77
|
+
assert.deepStrictEqual(modernBomObject.specVersion, "1.7");
|
|
78
|
+
assert.deepStrictEqual(
|
|
79
|
+
modernBomObject.metadata.component.type,
|
|
80
|
+
"application",
|
|
81
|
+
);
|
|
82
|
+
assert.deepStrictEqual(modernBomObject.metadata.component.name, "cdxgen");
|
|
83
|
+
cleanupTempDir(tempDir);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("keeps canonical definitions and declarations as objects during proto round-trip", () => {
|
|
87
|
+
const tempDir = createTempDir();
|
|
88
|
+
const binFile = join(tempDir, "standard-sections.cdx");
|
|
89
|
+
writeBinary(
|
|
90
|
+
{
|
|
91
|
+
bomFormat: "CycloneDX",
|
|
92
|
+
declarations: {
|
|
93
|
+
affirmation: {
|
|
94
|
+
statement: "verified",
|
|
95
|
+
},
|
|
96
|
+
claims: [
|
|
97
|
+
{
|
|
98
|
+
predicate: "meets-control",
|
|
99
|
+
target: "pkg:npm/demo-app@1.0.0",
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
definitions: {
|
|
104
|
+
standards: [
|
|
105
|
+
{
|
|
106
|
+
name: "ASVS",
|
|
107
|
+
requirements: [
|
|
108
|
+
{
|
|
109
|
+
identifier: "V1.1",
|
|
110
|
+
title: "Authenticate requests",
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
version: "5.0",
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
metadata: {
|
|
118
|
+
component: {
|
|
119
|
+
name: "demo-app",
|
|
120
|
+
type: "application",
|
|
121
|
+
version: "1.0.0",
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
serialNumber: "urn:uuid:22222222-2222-2222-2222-222222222222",
|
|
125
|
+
specVersion: "1.7",
|
|
126
|
+
version: 1,
|
|
127
|
+
},
|
|
128
|
+
binFile,
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const bomObject = readBinary(binFile);
|
|
132
|
+
assert.ok(bomObject);
|
|
133
|
+
assert.equal(Array.isArray(bomObject.definitions), false);
|
|
134
|
+
assert.equal(Array.isArray(bomObject.declarations), false);
|
|
135
|
+
assert.equal(bomObject.definitions.standards[0].name, "ASVS");
|
|
136
|
+
assert.equal(
|
|
137
|
+
bomObject.definitions.standards[0].requirements[0].identifier,
|
|
138
|
+
"V1.1",
|
|
139
|
+
);
|
|
140
|
+
assert.equal(bomObject.declarations.claims[0].predicate, "meets-control");
|
|
141
|
+
assert.equal(bomObject.declarations.affirmation.statement, "verified");
|
|
142
|
+
cleanupTempDir(tempDir);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("rejects unsupported CycloneDX 2.0 protobuf operations with a clear error", () => {
|
|
146
|
+
const tempDir = createTempDir();
|
|
147
|
+
const binFile = join(tempDir, "unsupported-2.0.cdx");
|
|
148
|
+
assert.throws(
|
|
149
|
+
() =>
|
|
150
|
+
writeBinary(
|
|
151
|
+
{
|
|
152
|
+
specFormat: "CycloneDX",
|
|
153
|
+
specVersion: "2.0",
|
|
154
|
+
version: 1,
|
|
155
|
+
},
|
|
156
|
+
binFile,
|
|
157
|
+
),
|
|
158
|
+
/CycloneDX 2\.0 is not currently supported for protobuf serialization/,
|
|
159
|
+
);
|
|
160
|
+
assert.throws(
|
|
161
|
+
() => assertProtoSupportedSpecVersion("2.0", "protobuf export"),
|
|
162
|
+
/@appthreat\/cdx-proto supports 1\.5, 1\.6, 1\.7 only/,
|
|
163
|
+
);
|
|
164
|
+
assert.throws(
|
|
165
|
+
() =>
|
|
166
|
+
writeBinary(
|
|
167
|
+
{
|
|
168
|
+
bomFormat: "CycloneDX",
|
|
169
|
+
specVersion: "2.0.1",
|
|
170
|
+
version: 1,
|
|
171
|
+
},
|
|
172
|
+
binFile,
|
|
173
|
+
),
|
|
174
|
+
/CycloneDX 2\.0\.1 is not currently supported for protobuf serialization/,
|
|
175
|
+
);
|
|
176
|
+
assert.throws(
|
|
177
|
+
() => assertProtoSupportedSpecVersion("2.0.1", "protobuf export"),
|
|
178
|
+
/CycloneDX 2\.0\.1 is not currently supported for protobuf export/,
|
|
179
|
+
);
|
|
180
|
+
cleanupTempDir(tempDir);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("round-trips real CBOM fixture data with cryptographic assets intact", () => {
|
|
184
|
+
const tempDir = createTempDir();
|
|
185
|
+
const binFile = join(tempDir, "cbom-fixture.cdx");
|
|
186
|
+
writeBinary(cbomFixture, binFile);
|
|
187
|
+
|
|
188
|
+
const bomObject = readBinary(binFile);
|
|
189
|
+
const cryptoComponents = (bomObject.components || []).filter(
|
|
190
|
+
(component) => component.type === "cryptographic-asset",
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
assert.ok(bomObject);
|
|
194
|
+
assert.equal(bomObject.specVersion, "1.7");
|
|
195
|
+
assert.ok(cryptoComponents.length >= 3);
|
|
196
|
+
assert.equal(
|
|
197
|
+
cryptoComponents.some(
|
|
198
|
+
(component) => component.cryptoProperties?.assetType === "algorithm",
|
|
199
|
+
),
|
|
200
|
+
true,
|
|
201
|
+
);
|
|
202
|
+
assert.equal(
|
|
203
|
+
cryptoComponents.some((component) => component.purl !== undefined),
|
|
204
|
+
false,
|
|
205
|
+
);
|
|
206
|
+
assert.equal(
|
|
207
|
+
cryptoComponents.some(
|
|
208
|
+
(component) =>
|
|
209
|
+
component.name === "sha-512" &&
|
|
210
|
+
component.cryptoProperties?.oid === "2.16.840.1.101.3.4.2.3",
|
|
211
|
+
),
|
|
212
|
+
true,
|
|
213
|
+
);
|
|
214
|
+
cleanupTempDir(tempDir);
|
|
37
215
|
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const PROTO_BOM_FILE_EXTENSIONS = [".cdx", ".cdx.bin", ".proto"];
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Determine whether a path looks like a CycloneDX protobuf BOM file.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} filePath File path
|
|
7
|
+
* @returns {boolean} true when the path uses a protobuf BOM extension
|
|
8
|
+
*/
|
|
9
|
+
export function isProtoBomPath(filePath) {
|
|
10
|
+
const normalizedPath = `${filePath || ""}`.toLowerCase();
|
|
11
|
+
return PROTO_BOM_FILE_EXTENSIONS.some((extension) =>
|
|
12
|
+
normalizedPath.endsWith(extension),
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Import protobuf BOM helpers and replace optional-dependency loader failures
|
|
18
|
+
* with actionable command-specific messages.
|
|
19
|
+
*
|
|
20
|
+
* @param {string} [commandName="cdxgen"] CLI command name
|
|
21
|
+
* @param {string} [featureDescription="protobuf support"] Feature being used
|
|
22
|
+
* @returns {Promise<object>} Loaded protobom module namespace
|
|
23
|
+
*/
|
|
24
|
+
export async function importProtobomModule(
|
|
25
|
+
commandName = "cdxgen",
|
|
26
|
+
featureDescription = "protobuf support",
|
|
27
|
+
) {
|
|
28
|
+
try {
|
|
29
|
+
return await import("./protobom.js");
|
|
30
|
+
} catch (error) {
|
|
31
|
+
const message = `${error?.message || ""}`;
|
|
32
|
+
if (
|
|
33
|
+
error?.code === "ERR_MODULE_NOT_FOUND" ||
|
|
34
|
+
message.includes("@appthreat/cdx-proto") ||
|
|
35
|
+
message.includes("@bufbuild/protobuf")
|
|
36
|
+
) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`${commandName} ${featureDescription} requires the optional '@appthreat/cdx-proto' and '@bufbuild/protobuf' dependencies. Install optional dependencies or use a binary that bundles protobuf support.`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { assert, describe, it } from "poku";
|
|
2
|
+
|
|
3
|
+
import { importProtobomModule, isProtoBomPath } from "./protobomLoader.js";
|
|
4
|
+
|
|
5
|
+
describe("protobomLoader", () => {
|
|
6
|
+
it("detects protobuf BOM file extensions", () => {
|
|
7
|
+
assert.strictEqual(isProtoBomPath("bom.cdx"), true);
|
|
8
|
+
assert.strictEqual(isProtoBomPath("bom.CDX.BIN"), true);
|
|
9
|
+
assert.strictEqual(isProtoBomPath("bom.proto"), true);
|
|
10
|
+
assert.strictEqual(isProtoBomPath("bom.json"), false);
|
|
11
|
+
assert.strictEqual(isProtoBomPath(""), false);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("imports the protobuf BOM helper when optional support is installed", async () => {
|
|
15
|
+
let protobomModule;
|
|
16
|
+
try {
|
|
17
|
+
protobomModule = await importProtobomModule(
|
|
18
|
+
"cdx-test",
|
|
19
|
+
"protobuf BOM input",
|
|
20
|
+
);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
assert.match(
|
|
23
|
+
error.message,
|
|
24
|
+
/requires the optional '@appthreat\/cdx-proto' and '@bufbuild\/protobuf' dependencies/u,
|
|
25
|
+
);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
assert.strictEqual(typeof protobomModule.readBinary, "function");
|
|
29
|
+
assert.strictEqual(typeof protobomModule.writeBinary, "function");
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -1,13 +1,46 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
2
|
|
|
3
|
+
import { hasDangerousUnicode } from "../utils.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Returns the Dependency-Track BOM API URL as a sanitized URL object.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} serverUrl Dependency-Track server URL
|
|
9
|
+
* @returns {URL | undefined} API URL to submit BOM payload
|
|
10
|
+
*/
|
|
11
|
+
export function getDependencyTrackBomApiUrl(serverUrl) {
|
|
12
|
+
const rawServerUrl = `${serverUrl || ""}`.trim();
|
|
13
|
+
if (!rawServerUrl || hasDangerousUnicode(rawServerUrl)) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
let parsedUrl;
|
|
17
|
+
try {
|
|
18
|
+
parsedUrl = new URL(rawServerUrl);
|
|
19
|
+
} catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
if (!["http:", "https:"].includes(parsedUrl.protocol)) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
if (!parsedUrl.hostname || hasDangerousUnicode(parsedUrl.hostname)) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
parsedUrl.username = "";
|
|
29
|
+
parsedUrl.password = "";
|
|
30
|
+
parsedUrl.search = "";
|
|
31
|
+
parsedUrl.hash = "";
|
|
32
|
+
parsedUrl.pathname = `${parsedUrl.pathname.replace(/\/+$/, "")}/api/v1/bom`;
|
|
33
|
+
return parsedUrl;
|
|
34
|
+
}
|
|
35
|
+
|
|
3
36
|
/**
|
|
4
|
-
* Returns the Dependency-Track BOM API URL.
|
|
37
|
+
* Returns the Dependency-Track BOM API URL string.
|
|
5
38
|
*
|
|
6
39
|
* @param {string} serverUrl Dependency-Track server URL
|
|
7
|
-
* @returns {string} API URL to submit BOM payload
|
|
40
|
+
* @returns {string | undefined} API URL to submit BOM payload
|
|
8
41
|
*/
|
|
9
42
|
export function getDependencyTrackBomUrl(serverUrl) {
|
|
10
|
-
return
|
|
43
|
+
return getDependencyTrackBomApiUrl(serverUrl)?.toString();
|
|
11
44
|
}
|
|
12
45
|
|
|
13
46
|
/**
|
|
@@ -2,6 +2,7 @@ import { assert, describe, it } from "poku";
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
buildDependencyTrackBomPayload,
|
|
5
|
+
getDependencyTrackBomApiUrl,
|
|
5
6
|
getDependencyTrackBomUrl,
|
|
6
7
|
} from "./dependency-track.js";
|
|
7
8
|
|
|
@@ -17,6 +18,49 @@ describe("Dependency-Track helper tests", () => {
|
|
|
17
18
|
);
|
|
18
19
|
});
|
|
19
20
|
|
|
21
|
+
it("removes credentials, query strings, and fragments from the submission URL", () => {
|
|
22
|
+
assert.strictEqual(
|
|
23
|
+
getDependencyTrackBomUrl(
|
|
24
|
+
"https://user:pass@dtrack.example.com/base/?token=secret#frag",
|
|
25
|
+
),
|
|
26
|
+
"https://dtrack.example.com/base/api/v1/bom",
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("returns a sanitized URL object for Dependency-Track requests", () => {
|
|
31
|
+
const apiUrl = getDependencyTrackBomApiUrl(
|
|
32
|
+
"https://user:pass@dtrack.example.com/base/?token=secret#frag",
|
|
33
|
+
);
|
|
34
|
+
assert.ok(apiUrl instanceof URL);
|
|
35
|
+
assert.strictEqual(apiUrl?.hostname, "dtrack.example.com");
|
|
36
|
+
assert.strictEqual(apiUrl?.pathname, "/base/api/v1/bom");
|
|
37
|
+
assert.strictEqual(apiUrl?.username, "");
|
|
38
|
+
assert.strictEqual(apiUrl?.password, "");
|
|
39
|
+
assert.strictEqual(apiUrl?.search, "");
|
|
40
|
+
assert.strictEqual(apiUrl?.hash, "");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("rejects malformed or unsupported submission URLs", () => {
|
|
44
|
+
assert.strictEqual(
|
|
45
|
+
getDependencyTrackBomUrl("file:///tmp/dtrack"),
|
|
46
|
+
undefined,
|
|
47
|
+
);
|
|
48
|
+
assert.strictEqual(
|
|
49
|
+
getDependencyTrackBomApiUrl("file:///tmp/dtrack"),
|
|
50
|
+
undefined,
|
|
51
|
+
);
|
|
52
|
+
assert.strictEqual(
|
|
53
|
+
getDependencyTrackBomUrl("javascript:alert(1)"),
|
|
54
|
+
undefined,
|
|
55
|
+
);
|
|
56
|
+
assert.strictEqual(
|
|
57
|
+
getDependencyTrackBomApiUrl("javascript:alert(1)"),
|
|
58
|
+
undefined,
|
|
59
|
+
);
|
|
60
|
+
assert.strictEqual(getDependencyTrackBomUrl("not a url"), undefined);
|
|
61
|
+
assert.strictEqual(getDependencyTrackBomApiUrl("not a url"), undefined);
|
|
62
|
+
});
|
|
63
|
+
|
|
20
64
|
it("builds payload with parentUUID and tags", () => {
|
|
21
65
|
const payload = buildDependencyTrackBomPayload(
|
|
22
66
|
{
|
package/lib/helpers/source.js
CHANGED
|
@@ -77,6 +77,21 @@ function isSafeGitRefName(refName) {
|
|
|
77
77
|
return /^[A-Za-z0-9._/@+-]+$/.test(refName);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
function inferGitOperation(args = []) {
|
|
81
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
82
|
+
const arg = args[index];
|
|
83
|
+
if (arg === "-c" || arg === "--config-env") {
|
|
84
|
+
index += 1;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (typeof arg === "string" && arg.startsWith("-")) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
return arg || "command";
|
|
91
|
+
}
|
|
92
|
+
return "command";
|
|
93
|
+
}
|
|
94
|
+
|
|
80
95
|
/**
|
|
81
96
|
* Execute git with hardened defaults.
|
|
82
97
|
*
|
|
@@ -86,6 +101,7 @@ function isSafeGitRefName(refName) {
|
|
|
86
101
|
* @returns {Object} spawn result
|
|
87
102
|
*/
|
|
88
103
|
export function hardenedGitCommand(args, options = {}) {
|
|
104
|
+
const gitOperation = inferGitOperation(args);
|
|
89
105
|
const gitAllowProtocol = getGitAllowProtocol();
|
|
90
106
|
const envConfigs = {
|
|
91
107
|
GIT_CONFIG_COUNT: "2",
|
|
@@ -109,6 +125,14 @@ export function hardenedGitCommand(args, options = {}) {
|
|
|
109
125
|
GIT_ALLOW_PROTOCOL: gitAllowProtocol,
|
|
110
126
|
};
|
|
111
127
|
return safeSpawnSync("git", args, {
|
|
128
|
+
cdxgenActivity: {
|
|
129
|
+
blockedReason: `Dry run mode blocks git ${gitOperation} operations.`,
|
|
130
|
+
gitOperation,
|
|
131
|
+
kind: `git-${gitOperation}`,
|
|
132
|
+
metadata: {
|
|
133
|
+
capability: "git-operation",
|
|
134
|
+
},
|
|
135
|
+
},
|
|
112
136
|
shell: false,
|
|
113
137
|
cwd: options.cwd,
|
|
114
138
|
env,
|
|
@@ -45,6 +45,38 @@ describe("source helper purl resolution", () => {
|
|
|
45
45
|
assert.strictEqual(recordActivity.firstCall.args[0].status, "blocked");
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
+
it("hardenedGitCommand() tags git operations with the specific subcommand", async () => {
|
|
49
|
+
const safeSpawnSync = sinon
|
|
50
|
+
.stub()
|
|
51
|
+
.returns({ status: 0, stdout: "", stderr: "" });
|
|
52
|
+
const { hardenedGitCommand } = await esmock("./source.js", {
|
|
53
|
+
"./utils.js": {
|
|
54
|
+
cdxgenAgent: { get: sinon.stub() },
|
|
55
|
+
DEBUG_MODE: false,
|
|
56
|
+
fetchPomXmlAsJson: sinon.stub(),
|
|
57
|
+
getTmpDir: sinon.stub().returns(os.tmpdir()),
|
|
58
|
+
hasDangerousUnicode: sinon.stub().returns(false),
|
|
59
|
+
isDryRun: false,
|
|
60
|
+
isSecureMode: false,
|
|
61
|
+
isValidDriveRoot: sinon.stub().returns(true),
|
|
62
|
+
isWin: false,
|
|
63
|
+
safeMkdtempSync: sinon.stub(),
|
|
64
|
+
safeRmSync: sinon.stub(),
|
|
65
|
+
safeSpawnSync,
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
hardenedGitCommand(["fetch", "--tags"], { cwd: "/tmp/repo" });
|
|
70
|
+
|
|
71
|
+
sinon.assert.calledWithMatch(safeSpawnSync, "git", ["fetch", "--tags"], {
|
|
72
|
+
cdxgenActivity: sinon.match({
|
|
73
|
+
gitOperation: "fetch",
|
|
74
|
+
kind: "git-fetch",
|
|
75
|
+
}),
|
|
76
|
+
cwd: "/tmp/repo",
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
48
80
|
it("resolves npm purl to repository URL", async () => {
|
|
49
81
|
const getStub = sinon.stub().resolves({
|
|
50
82
|
body: {
|