@cyclonedx/cdxgen 12.2.1 → 12.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +239 -90
- package/bin/audit.js +191 -0
- package/bin/cdxgen.js +513 -167
- package/bin/convert.js +99 -0
- package/bin/evinse.js +23 -0
- package/bin/repl.js +339 -8
- package/bin/sign.js +8 -0
- package/bin/validate.js +8 -0
- package/bin/verify.js +8 -0
- package/data/container-knowledge-index.json +125 -0
- package/data/gtfobins-index.json +6296 -0
- package/data/lolbas-index.json +150 -0
- package/data/queries-darwin.json +63 -3
- package/data/queries-win.json +45 -3
- package/data/queries.json +74 -2
- package/data/rules/chrome-extensions.yaml +240 -0
- package/data/rules/ci-permissions.yaml +478 -18
- package/data/rules/container-risk.yaml +270 -0
- package/data/rules/obom-runtime.yaml +891 -0
- package/data/rules/package-integrity.yaml +49 -0
- package/data/spdx-export.schema.json +6794 -0
- package/data/spdx-model-v3.0.1.jsonld +15999 -0
- package/lib/audit/index.js +1924 -0
- package/lib/audit/index.poku.js +1488 -0
- package/lib/audit/progress.js +137 -0
- package/lib/audit/progress.poku.js +188 -0
- package/lib/audit/reporters.js +618 -0
- package/lib/audit/scoring.js +310 -0
- package/lib/audit/scoring.poku.js +341 -0
- package/lib/audit/targets.js +260 -0
- package/lib/audit/targets.poku.js +331 -0
- package/lib/cli/index.js +154 -11
- package/lib/cli/index.poku.js +251 -0
- package/lib/helpers/analyzer.js +446 -2
- package/lib/helpers/analyzer.poku.js +72 -1
- package/lib/helpers/annotationFormatter.js +49 -0
- package/lib/helpers/annotationFormatter.poku.js +44 -0
- package/lib/helpers/bomUtils.js +36 -0
- package/lib/helpers/bomUtils.poku.js +51 -0
- package/lib/helpers/caxa.js +2 -2
- package/lib/helpers/chromextutils.js +1153 -0
- package/lib/helpers/chromextutils.poku.js +493 -0
- package/lib/helpers/ciParsers/githubActions.js +1632 -45
- package/lib/helpers/ciParsers/githubActions.poku.js +853 -1
- package/lib/helpers/containerRisk.js +186 -0
- package/lib/helpers/containerRisk.poku.js +52 -0
- package/lib/helpers/display.js +241 -59
- package/lib/helpers/display.poku.js +162 -2
- package/lib/helpers/exportUtils.js +123 -0
- package/lib/helpers/exportUtils.poku.js +60 -0
- package/lib/helpers/formulationParsers.js +69 -0
- package/lib/helpers/formulationParsers.poku.js +44 -0
- package/lib/helpers/gtfobins.js +189 -0
- package/lib/helpers/gtfobins.poku.js +49 -0
- package/lib/helpers/lolbas.js +267 -0
- package/lib/helpers/lolbas.poku.js +39 -0
- package/lib/helpers/osqueryTransform.js +84 -0
- package/lib/helpers/osqueryTransform.poku.js +49 -0
- package/lib/helpers/provenanceUtils.js +193 -0
- package/lib/helpers/provenanceUtils.poku.js +145 -0
- package/lib/helpers/pylockutils.js +281 -0
- package/lib/helpers/pylockutils.poku.js +48 -0
- package/lib/helpers/registryProvenance.js +793 -0
- package/lib/helpers/registryProvenance.poku.js +452 -0
- package/lib/helpers/source.js +1267 -0
- package/lib/helpers/source.poku.js +771 -0
- package/lib/helpers/spdxUtils.js +97 -0
- package/lib/helpers/spdxUtils.poku.js +70 -0
- package/lib/helpers/unicodeScan.js +147 -0
- package/lib/helpers/unicodeScan.poku.js +45 -0
- package/lib/helpers/utils.js +700 -128
- package/lib/helpers/utils.poku.js +877 -80
- package/lib/managers/binary.js +29 -5
- package/lib/managers/docker.js +179 -52
- package/lib/managers/docker.poku.js +327 -28
- package/lib/managers/oci.js +107 -23
- package/lib/managers/oci.poku.js +132 -0
- package/lib/server/openapi.yaml +17 -0
- package/lib/server/server.js +225 -336
- package/lib/server/server.poku.js +16 -10
- package/lib/stages/postgen/annotator.js +7 -0
- package/lib/stages/postgen/annotator.poku.js +40 -0
- package/lib/stages/postgen/auditBom.js +19 -3
- package/lib/stages/postgen/auditBom.poku.js +1729 -67
- package/lib/stages/postgen/postgen.js +40 -0
- package/lib/stages/postgen/postgen.poku.js +47 -0
- package/lib/stages/postgen/ruleEngine.js +80 -2
- package/lib/stages/postgen/spdxConverter.js +796 -0
- package/lib/stages/postgen/spdxConverter.poku.js +341 -0
- package/lib/validator/bomValidator.js +232 -0
- package/lib/validator/bomValidator.poku.js +70 -0
- package/lib/validator/complianceRules.js +70 -7
- package/lib/validator/complianceRules.poku.js +30 -0
- package/lib/validator/reporters/annotations.js +2 -2
- package/lib/validator/reporters/console.js +11 -0
- package/lib/validator/reporters.poku.js +13 -0
- package/package.json +10 -7
- package/types/bin/audit.d.ts +3 -0
- package/types/bin/audit.d.ts.map +1 -0
- package/types/bin/convert.d.ts +3 -0
- package/types/bin/convert.d.ts.map +1 -0
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts +115 -0
- package/types/lib/audit/index.d.ts.map +1 -0
- package/types/lib/audit/progress.d.ts +27 -0
- package/types/lib/audit/progress.d.ts.map +1 -0
- package/types/lib/audit/reporters.d.ts +35 -0
- package/types/lib/audit/reporters.d.ts.map +1 -0
- package/types/lib/audit/scoring.d.ts +35 -0
- package/types/lib/audit/scoring.d.ts.map +1 -0
- package/types/lib/audit/targets.d.ts +63 -0
- package/types/lib/audit/targets.d.ts.map +1 -0
- package/types/lib/cli/index.d.ts +8 -0
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/helpers/analyzer.d.ts +13 -0
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/annotationFormatter.d.ts +23 -0
- package/types/lib/helpers/annotationFormatter.d.ts.map +1 -0
- package/types/lib/helpers/bomUtils.d.ts +5 -0
- package/types/lib/helpers/bomUtils.d.ts.map +1 -0
- package/types/lib/helpers/chromextutils.d.ts +97 -0
- package/types/lib/helpers/chromextutils.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts +3 -8
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
- package/types/lib/helpers/containerRisk.d.ts +17 -0
- package/types/lib/helpers/containerRisk.d.ts.map +1 -0
- package/types/lib/helpers/display.d.ts +4 -1
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/exportUtils.d.ts +40 -0
- package/types/lib/helpers/exportUtils.d.ts.map +1 -0
- package/types/lib/helpers/formulationParsers.d.ts.map +1 -1
- package/types/lib/helpers/gtfobins.d.ts +17 -0
- package/types/lib/helpers/gtfobins.d.ts.map +1 -0
- package/types/lib/helpers/lolbas.d.ts +16 -0
- package/types/lib/helpers/lolbas.d.ts.map +1 -0
- package/types/lib/helpers/osqueryTransform.d.ts +7 -0
- package/types/lib/helpers/osqueryTransform.d.ts.map +1 -0
- package/types/lib/helpers/provenanceUtils.d.ts +90 -0
- package/types/lib/helpers/provenanceUtils.d.ts.map +1 -0
- package/types/lib/helpers/pylockutils.d.ts +51 -0
- package/types/lib/helpers/pylockutils.d.ts.map +1 -0
- package/types/lib/helpers/registryProvenance.d.ts +17 -0
- package/types/lib/helpers/registryProvenance.d.ts.map +1 -0
- package/types/lib/helpers/source.d.ts +141 -0
- package/types/lib/helpers/source.d.ts.map +1 -0
- package/types/lib/helpers/spdxUtils.d.ts +2 -0
- package/types/lib/helpers/spdxUtils.d.ts.map +1 -0
- package/types/lib/helpers/unicodeScan.d.ts +46 -0
- package/types/lib/helpers/unicodeScan.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts +29 -11
- 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.map +1 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +0 -36
- 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
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/postgen/spdxConverter.d.ts +11 -0
- package/types/lib/stages/postgen/spdxConverter.d.ts.map +1 -0
- package/types/lib/validator/bomValidator.d.ts +1 -0
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/complianceRules.d.ts.map +1 -1
- package/types/lib/validator/reporters/console.d.ts.map +1 -1
- package/types/bin/dependencies.d.ts +0 -3
- package/types/bin/dependencies.d.ts.map +0 -1
- package/types/bin/licenses.d.ts +0 -3
- package/types/bin/licenses.d.ts.map +0 -1
package/bin/convert.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import { dirname } from "node:path";
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
|
|
7
|
+
import yargs from "yargs";
|
|
8
|
+
import { hideBin } from "yargs/helpers";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
getNonCycloneDxErrorMessage,
|
|
12
|
+
isCycloneDxBom,
|
|
13
|
+
} from "../lib/helpers/bomUtils.js";
|
|
14
|
+
import { deriveSpdxOutputPath } from "../lib/helpers/exportUtils.js";
|
|
15
|
+
import {
|
|
16
|
+
retrieveCdxgenVersion,
|
|
17
|
+
safeExistsSync,
|
|
18
|
+
safeMkdirSync,
|
|
19
|
+
} from "../lib/helpers/utils.js";
|
|
20
|
+
import { convertCycloneDxToSpdx } from "../lib/stages/postgen/spdxConverter.js";
|
|
21
|
+
import { validateSpdx } from "../lib/validator/bomValidator.js";
|
|
22
|
+
|
|
23
|
+
const _yargs = yargs(hideBin(process.argv));
|
|
24
|
+
|
|
25
|
+
const args = _yargs
|
|
26
|
+
.option("input", {
|
|
27
|
+
alias: "i",
|
|
28
|
+
default: "bom.json",
|
|
29
|
+
description: "Input CycloneDX BOM JSON file.",
|
|
30
|
+
})
|
|
31
|
+
.option("output", {
|
|
32
|
+
alias: "o",
|
|
33
|
+
description: "Output SPDX JSON file. Defaults to <input>.spdx.json.",
|
|
34
|
+
})
|
|
35
|
+
.option("validate", {
|
|
36
|
+
type: "boolean",
|
|
37
|
+
default: true,
|
|
38
|
+
description:
|
|
39
|
+
"Validate the generated SPDX export. Pass --no-validate to skip.",
|
|
40
|
+
})
|
|
41
|
+
.option("json-pretty", {
|
|
42
|
+
type: "boolean",
|
|
43
|
+
default: false,
|
|
44
|
+
description: "Pretty-print generated JSON output.",
|
|
45
|
+
})
|
|
46
|
+
.completion("completion", "Generate bash/zsh completion")
|
|
47
|
+
.epilogue("for documentation, visit https://cdxgen.github.io/cdxgen")
|
|
48
|
+
.scriptName("cdx-convert")
|
|
49
|
+
.version(retrieveCdxgenVersion())
|
|
50
|
+
.help()
|
|
51
|
+
.wrap(Math.min(120, yargs().terminalWidth())).argv;
|
|
52
|
+
|
|
53
|
+
if (!safeExistsSync(args.input)) {
|
|
54
|
+
console.error(`Input file '${args.input}' not found.`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let bomJson;
|
|
59
|
+
try {
|
|
60
|
+
bomJson = JSON.parse(fs.readFileSync(args.input, "utf8"));
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error(`Failed to parse '${args.input}' as JSON: ${error.message}`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!isCycloneDxBom(bomJson)) {
|
|
67
|
+
console.error(getNonCycloneDxErrorMessage(bomJson, "cdx-convert"));
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
const cdxSpecVersion = Number.parseFloat(`${bomJson?.specVersion || ""}`);
|
|
71
|
+
if (![1.6, 1.7].includes(cdxSpecVersion)) {
|
|
72
|
+
console.error(
|
|
73
|
+
`Unsupported CycloneDX specVersion '${bomJson?.specVersion}'. cdx-convert currently supports CycloneDX 1.6 or 1.7 input and exports SPDX 3.0.1.`,
|
|
74
|
+
);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const spdxJson = convertCycloneDxToSpdx(bomJson, args);
|
|
79
|
+
if (!spdxJson) {
|
|
80
|
+
console.error("Conversion failed: unable to generate SPDX output.");
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (args.validate && !validateSpdx(spdxJson)) {
|
|
85
|
+
console.error("SPDX validation failed for the converted output.");
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const outputPath = args.output || deriveSpdxOutputPath(args.input);
|
|
90
|
+
const outputParent = dirname(outputPath);
|
|
91
|
+
if (outputParent && outputParent !== "." && !safeExistsSync(outputParent)) {
|
|
92
|
+
safeMkdirSync(outputParent, { recursive: true });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
fs.writeFileSync(
|
|
96
|
+
outputPath,
|
|
97
|
+
JSON.stringify(spdxJson, null, args.jsonPretty ? 2 : null),
|
|
98
|
+
);
|
|
99
|
+
console.log(`Successfully converted '${args.input}' to '${outputPath}'.`);
|
package/bin/evinse.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Evinse (Evinse Verification Is Nearly SBOM Evidence)
|
|
3
3
|
|
|
4
|
+
import fs from "node:fs";
|
|
4
5
|
import process from "node:process";
|
|
5
6
|
|
|
6
7
|
import yargs from "yargs";
|
|
@@ -11,12 +12,17 @@ import {
|
|
|
11
12
|
createEvinseFile,
|
|
12
13
|
prepareDB,
|
|
13
14
|
} from "../lib/evinser/evinser.js";
|
|
15
|
+
import {
|
|
16
|
+
getNonCycloneDxErrorMessage,
|
|
17
|
+
isCycloneDxBom,
|
|
18
|
+
} from "../lib/helpers/bomUtils.js";
|
|
14
19
|
import {
|
|
15
20
|
printCallStack,
|
|
16
21
|
printOccurrences,
|
|
17
22
|
printReachables,
|
|
18
23
|
printServices,
|
|
19
24
|
} from "../lib/helpers/display.js";
|
|
25
|
+
import { safeExistsSync } from "../lib/helpers/utils.js";
|
|
20
26
|
import { validateBom } from "../lib/validator/bomValidator.js";
|
|
21
27
|
|
|
22
28
|
const args = yargs(hideBin(process.argv))
|
|
@@ -149,6 +155,23 @@ if (process.env?.CDXGEN_NODE_OPTIONS) {
|
|
|
149
155
|
}
|
|
150
156
|
|
|
151
157
|
console.log(evinseArt);
|
|
158
|
+
function ensureCycloneDxInput(inputFile) {
|
|
159
|
+
if (!safeExistsSync(inputFile)) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
let bomJson;
|
|
163
|
+
try {
|
|
164
|
+
bomJson = JSON.parse(fs.readFileSync(inputFile, "utf8"));
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.error(`Unable to parse '${inputFile}' as JSON: ${error.message}`);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
if (!isCycloneDxBom(bomJson)) {
|
|
170
|
+
console.error(getNonCycloneDxErrorMessage(bomJson, "evinse"));
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
ensureCycloneDxInput(args.input);
|
|
152
175
|
(async () => {
|
|
153
176
|
// First, prepare the database by cataloging jars and other libraries
|
|
154
177
|
const dbObjMap = await prepareDB(args);
|
package/bin/repl.js
CHANGED
|
@@ -9,6 +9,7 @@ import repl from "node:repl";
|
|
|
9
9
|
import jsonata from "jsonata";
|
|
10
10
|
|
|
11
11
|
import { createBom } from "../lib/cli/index.js";
|
|
12
|
+
import { isSpdxJsonLd } from "../lib/helpers/bomUtils.js";
|
|
12
13
|
import {
|
|
13
14
|
printCallStack,
|
|
14
15
|
printDependencyTree,
|
|
@@ -21,6 +22,12 @@ import {
|
|
|
21
22
|
printVulnerabilities,
|
|
22
23
|
} from "../lib/helpers/display.js";
|
|
23
24
|
import { readBinary } from "../lib/helpers/protobom.js";
|
|
25
|
+
import {
|
|
26
|
+
getProvenanceComponents,
|
|
27
|
+
getTrustedComponents,
|
|
28
|
+
} from "../lib/helpers/provenanceUtils.js";
|
|
29
|
+
import { toCycloneDxLikeBom } from "../lib/helpers/spdxUtils.js";
|
|
30
|
+
import { table } from "../lib/helpers/table.js";
|
|
24
31
|
import { getTmpDir } from "../lib/helpers/utils.js";
|
|
25
32
|
import { getBomWithOras } from "../lib/managers/oci.js";
|
|
26
33
|
import { validateBom } from "../lib/validator/bomValidator.js";
|
|
@@ -54,6 +61,95 @@ if (process.env?.CDXGEN_NODE_OPTIONS) {
|
|
|
54
61
|
|
|
55
62
|
// The current sbom is stored here
|
|
56
63
|
let sbom;
|
|
64
|
+
const getInteractiveBom = () => toCycloneDxLikeBom(sbom);
|
|
65
|
+
|
|
66
|
+
function unescapeAnnotationText(value) {
|
|
67
|
+
return String(value || "")
|
|
68
|
+
.replace(/<br>/g, "\n")
|
|
69
|
+
.replace(/</g, "<")
|
|
70
|
+
.replace(/>/g, ">")
|
|
71
|
+
.replace(/&/g, "&")
|
|
72
|
+
.replace(/\\([\\`*_{}\[\]()#+!|])/g, "$1")
|
|
73
|
+
.trim();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function parseAnnotationProperties(text) {
|
|
77
|
+
const properties = {};
|
|
78
|
+
const lines = String(text || "").split(/\r?\n/);
|
|
79
|
+
let foundHeader = false;
|
|
80
|
+
for (const line of lines) {
|
|
81
|
+
if (!line.startsWith("|")) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const cells = line
|
|
85
|
+
.split("|")
|
|
86
|
+
.slice(1, -1)
|
|
87
|
+
.map((cell) => cell.trim());
|
|
88
|
+
if (cells.length < 2) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (cells[0] === "Property" && cells[1] === "Value") {
|
|
92
|
+
foundHeader = true;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (
|
|
96
|
+
foundHeader &&
|
|
97
|
+
/^-+$/.test(cells[0].replace(/\s/g, "")) &&
|
|
98
|
+
/^-+$/.test(cells[1].replace(/\s/g, ""))
|
|
99
|
+
) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (!foundHeader) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
properties[unescapeAnnotationText(cells[0])] = unescapeAnnotationText(
|
|
106
|
+
cells[1],
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
return properties;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function getAuditAnnotations() {
|
|
113
|
+
return (sbom?.annotations || [])
|
|
114
|
+
.map((annotation) => {
|
|
115
|
+
const properties = parseAnnotationProperties(annotation?.text);
|
|
116
|
+
return {
|
|
117
|
+
firstLine: unescapeAnnotationText(
|
|
118
|
+
String(annotation?.text || "").split(/\r?\n/, 1)[0],
|
|
119
|
+
),
|
|
120
|
+
properties,
|
|
121
|
+
raw: annotation,
|
|
122
|
+
};
|
|
123
|
+
})
|
|
124
|
+
.filter(
|
|
125
|
+
(annotation) =>
|
|
126
|
+
Object.keys(annotation.properties).some((key) =>
|
|
127
|
+
key.startsWith("cdx:audit:"),
|
|
128
|
+
) || annotation.firstLine.includes("cdx:audit:"),
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function printAuditTable(title, rows) {
|
|
133
|
+
if (rows.length <= 1) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
console.log(
|
|
137
|
+
table(rows, {
|
|
138
|
+
header: {
|
|
139
|
+
alignment: "center",
|
|
140
|
+
content: title,
|
|
141
|
+
},
|
|
142
|
+
}),
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function isLikelyObom(bom) {
|
|
147
|
+
return Boolean(
|
|
148
|
+
bom?.components?.some((comp) =>
|
|
149
|
+
comp?.properties?.some((prop) => prop?.name === "cdx:osquery:category"),
|
|
150
|
+
),
|
|
151
|
+
);
|
|
152
|
+
}
|
|
57
153
|
|
|
58
154
|
let historyFile;
|
|
59
155
|
const historyConfigDir = join(homedir(), ".config", ".cdxgen");
|
|
@@ -73,11 +169,24 @@ export const importSbom = (sbomOrPath) => {
|
|
|
73
169
|
try {
|
|
74
170
|
sbom = JSON.parse(fs.readFileSync(sbomOrPath, "utf-8"));
|
|
75
171
|
let bomType = "SBOM";
|
|
172
|
+
if (isSpdxJsonLd(sbom)) {
|
|
173
|
+
bomType = "SPDX";
|
|
174
|
+
}
|
|
76
175
|
if (sbom?.vulnerabilities && Array.isArray(sbom.vulnerabilities)) {
|
|
77
176
|
bomType = "VDR";
|
|
78
177
|
}
|
|
79
178
|
console.log(`✅ ${bomType} imported successfully from ${sbomOrPath}`);
|
|
80
179
|
printSummary(sbom);
|
|
180
|
+
if (isLikelyObom(sbom)) {
|
|
181
|
+
console.log(
|
|
182
|
+
"💭 OBOM detected. Try .osinfocategories, .obomtips, .processes, or .services_snapshot",
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
if (getAuditAnnotations().length) {
|
|
186
|
+
console.log(
|
|
187
|
+
"💭 Audit annotations detected. Try .auditfindings, .auditactions, or .dispatchedges.",
|
|
188
|
+
);
|
|
189
|
+
}
|
|
81
190
|
} catch (e) {
|
|
82
191
|
console.log(`⚠ Unable to import the BOM from ${sbomOrPath} due to ${e}`);
|
|
83
192
|
}
|
|
@@ -111,12 +220,24 @@ export const importSbom = (sbomOrPath) => {
|
|
|
111
220
|
if (process.argv.length > 2) {
|
|
112
221
|
importSbom(process.argv[process.argv.length - 1]);
|
|
113
222
|
console.log("💭 Type .print to view the BOM as a table");
|
|
223
|
+
console.log("💭 Type .trusted to list components with trusted publishing.");
|
|
224
|
+
console.log(
|
|
225
|
+
"💭 Type .provenance to list components with registry provenance evidence.",
|
|
226
|
+
);
|
|
227
|
+
if (getAuditAnnotations().length) {
|
|
228
|
+
console.log(
|
|
229
|
+
"💭 Type .auditfindings to review cdx-audit and bom-audit annotations.",
|
|
230
|
+
);
|
|
231
|
+
}
|
|
114
232
|
} else if (fs.existsSync("bom.json")) {
|
|
115
233
|
// If the current directory has a bom.json load it
|
|
116
234
|
importSbom("bom.json");
|
|
117
235
|
} else {
|
|
118
236
|
console.log("💭 Use .create <path> to create an SBOM for the given path.");
|
|
119
237
|
console.log("💭 Use .import <json> to import an existing BOM.");
|
|
238
|
+
console.log(
|
|
239
|
+
"💭 For OBOM investigations, try .obomtips after importing an OBOM.",
|
|
240
|
+
);
|
|
120
241
|
console.log("💭 Type .exit or press ctrl+d to close.");
|
|
121
242
|
}
|
|
122
243
|
|
|
@@ -148,6 +269,17 @@ cdxgenRepl.defineCommand("create", {
|
|
|
148
269
|
sbom = bomNSData.bomJson;
|
|
149
270
|
console.log("✅ BOM imported successfully.");
|
|
150
271
|
console.log("💭 Type .print to view the BOM as a table");
|
|
272
|
+
console.log(
|
|
273
|
+
"💭 Type .trusted to list components with trusted publishing.",
|
|
274
|
+
);
|
|
275
|
+
console.log(
|
|
276
|
+
"💭 Type .provenance to list components with registry provenance evidence.",
|
|
277
|
+
);
|
|
278
|
+
if (getAuditAnnotations().length) {
|
|
279
|
+
console.log(
|
|
280
|
+
"💭 Type .auditfindings to review cdx-audit and bom-audit annotations.",
|
|
281
|
+
);
|
|
282
|
+
}
|
|
151
283
|
} else {
|
|
152
284
|
console.log("BOM was not generated successfully");
|
|
153
285
|
}
|
|
@@ -207,9 +339,12 @@ cdxgenRepl.defineCommand("search", {
|
|
|
207
339
|
fixedSearchStr = `components[group ~> /${fixedSearchStr}/i or name ~> /${fixedSearchStr}/i or description ~> /${fixedSearchStr}/i or publisher ~> /${fixedSearchStr}/i or purl ~> /${fixedSearchStr}/i or tags ~> /${fixedSearchStr}/i]`;
|
|
208
340
|
}
|
|
209
341
|
const expression = jsonata(fixedSearchStr);
|
|
210
|
-
|
|
342
|
+
const bomForSearch = searchStr.includes("~>")
|
|
343
|
+
? sbom
|
|
344
|
+
: getInteractiveBom();
|
|
345
|
+
let components = await expression.evaluate(bomForSearch);
|
|
211
346
|
const dexpression = jsonata(dependenciesSearchStr);
|
|
212
|
-
let dependencies = await dexpression.evaluate(
|
|
347
|
+
let dependencies = await dexpression.evaluate(bomForSearch);
|
|
213
348
|
if (components && !Array.isArray(components)) {
|
|
214
349
|
components = [components];
|
|
215
350
|
}
|
|
@@ -303,8 +438,63 @@ cdxgenRepl.defineCommand("query", {
|
|
|
303
438
|
cdxgenRepl.defineCommand("print", {
|
|
304
439
|
help: "print the current bom as a table",
|
|
305
440
|
action() {
|
|
306
|
-
|
|
307
|
-
|
|
441
|
+
const interactiveBom = getInteractiveBom();
|
|
442
|
+
if (interactiveBom) {
|
|
443
|
+
printTable(interactiveBom);
|
|
444
|
+
} else {
|
|
445
|
+
console.log(
|
|
446
|
+
"⚠ No BOM is loaded. Use .import command to import an existing BOM",
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
this.displayPrompt();
|
|
450
|
+
},
|
|
451
|
+
});
|
|
452
|
+
cdxgenRepl.defineCommand("trusted", {
|
|
453
|
+
help: "print components with trusted publishing",
|
|
454
|
+
action() {
|
|
455
|
+
const interactiveBom = getInteractiveBom();
|
|
456
|
+
if (interactiveBom?.components) {
|
|
457
|
+
const trustedComponents = getTrustedComponents(interactiveBom.components);
|
|
458
|
+
if (!trustedComponents.length) {
|
|
459
|
+
console.log(
|
|
460
|
+
"No trusted-publishing components found. Look for components enriched with cdx:npm:trustedPublishing or cdx:pypi:trustedPublishing.",
|
|
461
|
+
);
|
|
462
|
+
} else {
|
|
463
|
+
printTable(
|
|
464
|
+
{ components: trustedComponents, dependencies: [] },
|
|
465
|
+
undefined,
|
|
466
|
+
undefined,
|
|
467
|
+
`Found ${trustedComponents.length} trusted component(s) backed by trusted publishing metadata.`,
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
} else {
|
|
471
|
+
console.log(
|
|
472
|
+
"⚠ No BOM is loaded. Use .import command to import an existing BOM",
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
this.displayPrompt();
|
|
476
|
+
},
|
|
477
|
+
});
|
|
478
|
+
cdxgenRepl.defineCommand("provenance", {
|
|
479
|
+
help: "print components with direct registry provenance evidence",
|
|
480
|
+
action() {
|
|
481
|
+
const interactiveBom = getInteractiveBom();
|
|
482
|
+
if (interactiveBom?.components) {
|
|
483
|
+
const provenanceComponents = getProvenanceComponents(
|
|
484
|
+
interactiveBom.components,
|
|
485
|
+
);
|
|
486
|
+
if (!provenanceComponents.length) {
|
|
487
|
+
console.log(
|
|
488
|
+
"No provenance-backed components found. Look for registry URLs, digests, signatures, or key IDs captured as component properties.",
|
|
489
|
+
);
|
|
490
|
+
} else {
|
|
491
|
+
printTable(
|
|
492
|
+
{ components: provenanceComponents, dependencies: [] },
|
|
493
|
+
undefined,
|
|
494
|
+
undefined,
|
|
495
|
+
`Found ${provenanceComponents.length} component(s) with direct registry provenance evidence.`,
|
|
496
|
+
);
|
|
497
|
+
}
|
|
308
498
|
} else {
|
|
309
499
|
console.log(
|
|
310
500
|
"⚠ No BOM is loaded. Use .import command to import an existing BOM",
|
|
@@ -342,8 +532,9 @@ cdxgenRepl.defineCommand("frameworks", {
|
|
|
342
532
|
cdxgenRepl.defineCommand("tree", {
|
|
343
533
|
help: "display the dependency tree",
|
|
344
534
|
action() {
|
|
345
|
-
|
|
346
|
-
|
|
535
|
+
const interactiveBom = getInteractiveBom();
|
|
536
|
+
if (interactiveBom) {
|
|
537
|
+
printDependencyTree(interactiveBom);
|
|
347
538
|
} else {
|
|
348
539
|
console.log(
|
|
349
540
|
"⚠ No BOM is loaded. Use .import command to import an existing BOM",
|
|
@@ -355,8 +546,9 @@ cdxgenRepl.defineCommand("tree", {
|
|
|
355
546
|
cdxgenRepl.defineCommand("provides", {
|
|
356
547
|
help: "display the provides tree",
|
|
357
548
|
action() {
|
|
358
|
-
|
|
359
|
-
|
|
549
|
+
const interactiveBom = getInteractiveBom();
|
|
550
|
+
if (interactiveBom) {
|
|
551
|
+
printDependencyTree(interactiveBom, "provides");
|
|
360
552
|
} else {
|
|
361
553
|
console.log(
|
|
362
554
|
"⚠ No BOM is loaded. Use .import command to import an existing BOM",
|
|
@@ -566,6 +758,113 @@ cdxgenRepl.defineCommand("formulation", {
|
|
|
566
758
|
this.displayPrompt();
|
|
567
759
|
},
|
|
568
760
|
});
|
|
761
|
+
cdxgenRepl.defineCommand("auditfindings", {
|
|
762
|
+
help: "summarize cdx-audit and bom-audit annotations from the loaded BOM",
|
|
763
|
+
action() {
|
|
764
|
+
if (!sbom) {
|
|
765
|
+
console.log("⚠ No BOM is loaded. Use .import command to import an SBOM");
|
|
766
|
+
this.displayPrompt();
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
const auditAnnotations = getAuditAnnotations();
|
|
770
|
+
if (!auditAnnotations.length) {
|
|
771
|
+
console.log(
|
|
772
|
+
"No audit annotations found. Generate an SBOM with --bom-audit or import a BOM enriched by cdx-audit.",
|
|
773
|
+
);
|
|
774
|
+
this.displayPrompt();
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
const rows = [
|
|
778
|
+
["Engine", "Severity", "Rule", "Target / Edge", "Next action"],
|
|
779
|
+
];
|
|
780
|
+
auditAnnotations.forEach((annotation) => {
|
|
781
|
+
const props = annotation.properties;
|
|
782
|
+
rows.push([
|
|
783
|
+
props["cdx:audit:engine"] || "bom-audit",
|
|
784
|
+
props["cdx:audit:severity"] || "unknown",
|
|
785
|
+
props["cdx:audit:topFinding:ruleId"] ||
|
|
786
|
+
props["cdx:audit:ruleId"] ||
|
|
787
|
+
"-",
|
|
788
|
+
props["cdx:audit:dispatch:edge"] ||
|
|
789
|
+
props["cdx:audit:target:purl"] ||
|
|
790
|
+
props["cdx:audit:location:file"] ||
|
|
791
|
+
annotation.firstLine,
|
|
792
|
+
props["cdx:audit:nextAction"] ||
|
|
793
|
+
props["cdx:audit:upstreamGuidance"] ||
|
|
794
|
+
props["cdx:audit:mitigation"] ||
|
|
795
|
+
"-",
|
|
796
|
+
]);
|
|
797
|
+
});
|
|
798
|
+
printAuditTable("Audit findings", rows);
|
|
799
|
+
this.displayPrompt();
|
|
800
|
+
},
|
|
801
|
+
});
|
|
802
|
+
cdxgenRepl.defineCommand("auditactions", {
|
|
803
|
+
help: "list next actions from predictive audit annotations",
|
|
804
|
+
action() {
|
|
805
|
+
if (!sbom) {
|
|
806
|
+
console.log("⚠ No BOM is loaded. Use .import command to import an SBOM");
|
|
807
|
+
this.displayPrompt();
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
const auditAnnotations = getAuditAnnotations().filter(
|
|
811
|
+
(annotation) => annotation.properties["cdx:audit:nextAction"],
|
|
812
|
+
);
|
|
813
|
+
if (!auditAnnotations.length) {
|
|
814
|
+
console.log(
|
|
815
|
+
"No predictive next actions found. Import a BOM annotated by cdx-audit to review remediation guidance.",
|
|
816
|
+
);
|
|
817
|
+
this.displayPrompt();
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
const rows = [["Severity", "Target", "Next action", "Upstream guidance"]];
|
|
821
|
+
auditAnnotations.forEach((annotation) => {
|
|
822
|
+
const props = annotation.properties;
|
|
823
|
+
rows.push([
|
|
824
|
+
props["cdx:audit:severity"] || "unknown",
|
|
825
|
+
props["cdx:audit:dispatch:edge"] ||
|
|
826
|
+
props["cdx:audit:target:purl"] ||
|
|
827
|
+
annotation.firstLine,
|
|
828
|
+
props["cdx:audit:nextAction"] || "-",
|
|
829
|
+
props["cdx:audit:upstreamGuidance"] || "-",
|
|
830
|
+
]);
|
|
831
|
+
});
|
|
832
|
+
printAuditTable("Predictive audit actions", rows);
|
|
833
|
+
this.displayPrompt();
|
|
834
|
+
},
|
|
835
|
+
});
|
|
836
|
+
cdxgenRepl.defineCommand("dispatchedges", {
|
|
837
|
+
help: "show local sender to receiver workflow edges captured by predictive audit",
|
|
838
|
+
action() {
|
|
839
|
+
if (!sbom) {
|
|
840
|
+
console.log("⚠ No BOM is loaded. Use .import command to import an SBOM");
|
|
841
|
+
this.displayPrompt();
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
const auditAnnotations = getAuditAnnotations().filter(
|
|
845
|
+
(annotation) => annotation.properties["cdx:audit:dispatch:edge"],
|
|
846
|
+
);
|
|
847
|
+
if (!auditAnnotations.length) {
|
|
848
|
+
console.log(
|
|
849
|
+
"No local dispatch edges found. Import a BOM annotated by cdx-audit with correlated workflow dispatch findings.",
|
|
850
|
+
);
|
|
851
|
+
this.displayPrompt();
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
const rows = [["Severity", "Rule", "Sender -> Receiver", "Receiver files"]];
|
|
855
|
+
auditAnnotations.forEach((annotation) => {
|
|
856
|
+
const props = annotation.properties;
|
|
857
|
+
rows.push([
|
|
858
|
+
props["cdx:audit:severity"] || "unknown",
|
|
859
|
+
props["cdx:audit:topFinding:ruleId"] || "-",
|
|
860
|
+
props["cdx:audit:dispatch:edge"] || annotation.firstLine,
|
|
861
|
+
props["cdx:audit:dispatch:receiverFiles"] || "-",
|
|
862
|
+
]);
|
|
863
|
+
});
|
|
864
|
+
printAuditTable("Predictive workflow dispatch edges", rows);
|
|
865
|
+
this.displayPrompt();
|
|
866
|
+
},
|
|
867
|
+
});
|
|
569
868
|
cdxgenRepl.defineCommand("osinfocategories", {
|
|
570
869
|
help: "view the category names for the OS info from the obom",
|
|
571
870
|
async action() {
|
|
@@ -591,6 +890,27 @@ cdxgenRepl.defineCommand("osinfocategories", {
|
|
|
591
890
|
this.displayPrompt();
|
|
592
891
|
},
|
|
593
892
|
});
|
|
893
|
+
// OBOM-specific analyst helper tips for SOC/IR and compliance workflows.
|
|
894
|
+
cdxgenRepl.defineCommand("obomtips", {
|
|
895
|
+
help: "show analyst tips and useful commands for OBOM investigations",
|
|
896
|
+
action() {
|
|
897
|
+
console.log("OBOM analyst quick guide:");
|
|
898
|
+
console.log("1. .osinfocategories");
|
|
899
|
+
console.log(
|
|
900
|
+
"2. Run an OS-query category command from .help (examples below)",
|
|
901
|
+
);
|
|
902
|
+
console.log(" .processes");
|
|
903
|
+
console.log(" .services_snapshot");
|
|
904
|
+
console.log(" .scheduled_tasks");
|
|
905
|
+
console.log(" .startup_items");
|
|
906
|
+
console.log("3. .inspect <name>");
|
|
907
|
+
console.log("4. .services / .print / .summary for quick pivots");
|
|
908
|
+
console.log(
|
|
909
|
+
"Tip: Generate with --bom-audit --bom-audit-categories obom-runtime for prioritized findings.",
|
|
910
|
+
);
|
|
911
|
+
this.displayPrompt();
|
|
912
|
+
},
|
|
913
|
+
});
|
|
594
914
|
cdxgenRepl.defineCommand("licenses", {
|
|
595
915
|
help: "visualize license distribution",
|
|
596
916
|
async action() {
|
|
@@ -631,6 +951,13 @@ cdxgenRepl.defineCommand("licenses", {
|
|
|
631
951
|
cdxgenRepl.defineCommand("inspect", {
|
|
632
952
|
help: "view full JSON details of a component: .inspect <name_search_string>",
|
|
633
953
|
async action(nameStr) {
|
|
954
|
+
if (!nameStr) {
|
|
955
|
+
console.log(
|
|
956
|
+
"⚠ Specify a component name or purl fragment. Eg: .inspect lodash",
|
|
957
|
+
);
|
|
958
|
+
this.displayPrompt();
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
634
961
|
if (sbom?.components) {
|
|
635
962
|
const found = sbom.components.find(
|
|
636
963
|
(c) =>
|
|
@@ -792,6 +1119,7 @@ cdxgenRepl.defineCommand("tagcloud", {
|
|
|
792
1119
|
"kernel_integrity",
|
|
793
1120
|
"kernel_modules",
|
|
794
1121
|
"ld_preload",
|
|
1122
|
+
"elevated_processes",
|
|
795
1123
|
"listening_ports",
|
|
796
1124
|
"os_version",
|
|
797
1125
|
"pipes",
|
|
@@ -799,11 +1127,14 @@ cdxgenRepl.defineCommand("tagcloud", {
|
|
|
799
1127
|
"portage_packages",
|
|
800
1128
|
"process_events",
|
|
801
1129
|
"processes",
|
|
1130
|
+
"privilege_transitions",
|
|
1131
|
+
"privileged_listening_ports",
|
|
802
1132
|
"python_packages",
|
|
803
1133
|
"rpm_packages",
|
|
804
1134
|
"scheduled_tasks",
|
|
805
1135
|
"services_snapshot",
|
|
806
1136
|
"startup_items",
|
|
1137
|
+
"sudo_executions",
|
|
807
1138
|
"system_info_snapshot",
|
|
808
1139
|
"windows_drivers",
|
|
809
1140
|
"windows_patches",
|
package/bin/sign.js
CHANGED
|
@@ -7,6 +7,10 @@ import yargs from "yargs";
|
|
|
7
7
|
import { hideBin } from "yargs/helpers";
|
|
8
8
|
|
|
9
9
|
import { signBom } from "../lib/helpers/bomSigner.js";
|
|
10
|
+
import {
|
|
11
|
+
getNonCycloneDxErrorMessage,
|
|
12
|
+
isCycloneDxBom,
|
|
13
|
+
} from "../lib/helpers/bomUtils.js";
|
|
10
14
|
import { retrieveCdxgenVersion, safeExistsSync } from "../lib/helpers/utils.js";
|
|
11
15
|
|
|
12
16
|
const _yargs = yargs(hideBin(process.argv));
|
|
@@ -78,6 +82,10 @@ if (!safeExistsSync(args.privateKey)) {
|
|
|
78
82
|
try {
|
|
79
83
|
const bomJson = JSON.parse(fs.readFileSync(args.input, "utf8"));
|
|
80
84
|
const privateKey = fs.readFileSync(args.privateKey, "utf8");
|
|
85
|
+
if (!isCycloneDxBom(bomJson)) {
|
|
86
|
+
console.error(getNonCycloneDxErrorMessage(bomJson, "cdx-sign"));
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
81
89
|
|
|
82
90
|
const signedBom = signBom(bomJson, {
|
|
83
91
|
privateKey,
|
package/bin/validate.js
CHANGED
|
@@ -19,6 +19,10 @@ import process from "node:process";
|
|
|
19
19
|
import yargs from "yargs";
|
|
20
20
|
import { hideBin } from "yargs/helpers";
|
|
21
21
|
|
|
22
|
+
import {
|
|
23
|
+
getNonCycloneDxErrorMessage,
|
|
24
|
+
isCycloneDxBom,
|
|
25
|
+
} from "../lib/helpers/bomUtils.js";
|
|
22
26
|
import {
|
|
23
27
|
dirNameStr,
|
|
24
28
|
retrieveCdxgenVersion,
|
|
@@ -177,6 +181,10 @@ function writeOrPrint(content, outputPath) {
|
|
|
177
181
|
|
|
178
182
|
const bomJson = loadBom(args.input, args.platform);
|
|
179
183
|
const publicKeyStr = loadPublicKey(args.publicKey);
|
|
184
|
+
if (!isCycloneDxBom(bomJson)) {
|
|
185
|
+
console.error(getNonCycloneDxErrorMessage(bomJson, "cdx-validate"));
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
180
188
|
|
|
181
189
|
const report = validateBomAdvanced(bomJson, {
|
|
182
190
|
schema: args.schema,
|
package/bin/verify.js
CHANGED
|
@@ -8,6 +8,10 @@ import yargs from "yargs";
|
|
|
8
8
|
import { hideBin } from "yargs/helpers";
|
|
9
9
|
|
|
10
10
|
import { verifyNode } from "../lib/helpers/bomSigner.js";
|
|
11
|
+
import {
|
|
12
|
+
getNonCycloneDxErrorMessage,
|
|
13
|
+
isCycloneDxBom,
|
|
14
|
+
} from "../lib/helpers/bomUtils.js";
|
|
11
15
|
import {
|
|
12
16
|
dirNameStr,
|
|
13
17
|
retrieveCdxgenVersion,
|
|
@@ -91,6 +95,10 @@ if (!bomJson) {
|
|
|
91
95
|
console.log(`${args.input} is invalid!`);
|
|
92
96
|
process.exit(1);
|
|
93
97
|
}
|
|
98
|
+
if (!isCycloneDxBom(bomJson)) {
|
|
99
|
+
console.log(getNonCycloneDxErrorMessage(bomJson, "cdx-verify"));
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
94
102
|
|
|
95
103
|
if (bomJson && !safeExistsSync(args.publicKey)) {
|
|
96
104
|
console.log("Public key for signature verification is missing!");
|