@cyclonedx/cdxgen 12.1.5 → 12.2.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 +51 -40
- package/bin/cdxgen.js +194 -97
- package/bin/evinse.js +4 -4
- package/bin/repl.js +1 -1
- package/bin/sign.js +102 -0
- package/bin/validate.js +233 -0
- package/bin/verify.js +69 -28
- package/data/queries.json +1 -1
- package/data/rules/ci-permissions.yaml +186 -0
- package/data/rules/dependency-sources.yaml +123 -0
- package/data/rules/package-integrity.yaml +135 -0
- package/data/rules/vscode-extensions.yaml +228 -0
- package/lib/cli/index.js +449 -429
- package/lib/cli/index.poku.js +117 -0
- package/lib/evinser/db.js +137 -0
- package/lib/{helpers → evinser}/db.poku.js +2 -6
- package/lib/evinser/evinser.js +2 -14
- package/lib/helpers/analyzer.js +606 -3
- package/lib/helpers/analyzer.poku.js +230 -0
- package/lib/helpers/bomSigner.js +312 -0
- package/lib/helpers/bomSigner.poku.js +156 -0
- package/lib/helpers/ciParsers/azurePipelines.js +295 -0
- package/lib/helpers/ciParsers/azurePipelines.poku.js +253 -0
- package/lib/helpers/ciParsers/circleCi.js +286 -0
- package/lib/helpers/ciParsers/circleCi.poku.js +230 -0
- package/lib/helpers/ciParsers/common.js +24 -0
- package/lib/helpers/ciParsers/githubActions.js +636 -0
- package/lib/helpers/ciParsers/githubActions.poku.js +802 -0
- package/lib/helpers/ciParsers/gitlabCi.js +213 -0
- package/lib/helpers/ciParsers/gitlabCi.poku.js +247 -0
- package/lib/helpers/ciParsers/jenkins.js +181 -0
- package/lib/helpers/ciParsers/jenkins.poku.js +197 -0
- package/lib/helpers/depsUtils.js +219 -0
- package/lib/helpers/depsUtils.poku.js +207 -0
- package/lib/helpers/display.js +426 -5
- package/lib/helpers/envcontext.js +18 -3
- package/lib/helpers/formulationParsers.js +351 -0
- package/lib/helpers/logger.js +14 -0
- package/lib/helpers/protobom.js +9 -9
- package/lib/helpers/pythonutils.js +9 -0
- package/lib/helpers/remote/dependency-track.js +84 -0
- package/lib/helpers/remote/dependency-track.poku.js +119 -0
- package/lib/helpers/table.js +384 -0
- package/lib/helpers/table.poku.js +186 -0
- package/lib/helpers/utils.js +865 -416
- package/lib/helpers/utils.poku.js +172 -265
- package/lib/helpers/versutils.js +202 -0
- package/lib/helpers/versutils.poku.js +315 -0
- package/lib/helpers/vsixutils.js +1061 -0
- package/lib/helpers/vsixutils.poku.js +2247 -0
- package/lib/managers/binary.js +19 -19
- package/lib/managers/docker.js +108 -1
- package/lib/managers/oci.js +10 -0
- package/lib/managers/piptree.js +3 -9
- package/lib/parsers/npmrc.js +17 -13
- package/lib/parsers/npmrc.poku.js +41 -5
- package/lib/server/openapi.yaml +34 -1
- package/lib/server/server.js +50 -13
- package/lib/server/server.poku.js +332 -144
- package/lib/stages/postgen/annotator.js +1 -1
- package/lib/stages/postgen/auditBom.js +196 -0
- package/lib/stages/postgen/auditBom.poku.js +378 -0
- package/lib/stages/postgen/postgen.js +54 -1
- package/lib/stages/postgen/postgen.poku.js +90 -1
- package/lib/stages/postgen/ruleEngine.js +369 -0
- package/lib/stages/pregen/envAudit.js +299 -0
- package/lib/stages/pregen/envAudit.poku.js +572 -0
- package/lib/stages/pregen/pregen.js +12 -8
- package/lib/{helpers/validator.js → validator/bomValidator.js} +107 -47
- package/lib/validator/complianceEngine.js +241 -0
- package/lib/validator/complianceEngine.poku.js +168 -0
- package/lib/validator/complianceRules.js +1610 -0
- package/lib/validator/complianceRules.poku.js +328 -0
- package/lib/validator/index.js +222 -0
- package/lib/validator/index.poku.js +144 -0
- package/lib/validator/reporters/annotations.js +121 -0
- package/lib/validator/reporters/console.js +149 -0
- package/lib/validator/reporters/index.js +41 -0
- package/lib/validator/reporters/json.js +37 -0
- package/lib/validator/reporters/sarif.js +184 -0
- package/lib/validator/reporters.poku.js +150 -0
- package/package.json +8 -9
- package/types/bin/sign.d.ts +3 -0
- package/types/bin/sign.d.ts.map +1 -0
- package/types/bin/validate.d.ts +3 -0
- package/types/bin/validate.d.ts.map +1 -0
- package/types/helpers/utils.d.ts +0 -1
- package/types/lib/cli/index.d.ts +49 -52
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/evinser/db.d.ts +34 -0
- package/types/lib/evinser/db.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +63 -16
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +27 -0
- package/types/lib/helpers/bomSigner.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/azurePipelines.d.ts +17 -0
- package/types/lib/helpers/ciParsers/azurePipelines.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/circleCi.d.ts +17 -0
- package/types/lib/helpers/ciParsers/circleCi.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/common.d.ts +11 -0
- package/types/lib/helpers/ciParsers/common.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts +34 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/gitlabCi.d.ts +17 -0
- package/types/lib/helpers/ciParsers/gitlabCi.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/jenkins.d.ts +17 -0
- package/types/lib/helpers/ciParsers/jenkins.d.ts.map +1 -0
- package/types/lib/helpers/depsUtils.d.ts +21 -0
- package/types/lib/helpers/depsUtils.d.ts.map +1 -0
- package/types/lib/helpers/display.d.ts +111 -11
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/envcontext.d.ts +19 -7
- package/types/lib/helpers/envcontext.d.ts.map +1 -1
- package/types/lib/helpers/formulationParsers.d.ts +50 -0
- package/types/lib/helpers/formulationParsers.d.ts.map +1 -0
- package/types/lib/helpers/logger.d.ts +15 -1
- package/types/lib/helpers/logger.d.ts.map +1 -1
- package/types/lib/helpers/protobom.d.ts +2 -2
- package/types/lib/helpers/pythonutils.d.ts +10 -1
- package/types/lib/helpers/pythonutils.d.ts.map +1 -1
- package/types/lib/helpers/remote/dependency-track.d.ts +16 -0
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -0
- package/types/lib/helpers/table.d.ts +6 -0
- package/types/lib/helpers/table.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts +533 -128
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/helpers/versutils.d.ts +8 -0
- package/types/lib/helpers/versutils.d.ts.map +1 -0
- package/types/lib/helpers/vsixutils.d.ts +130 -0
- package/types/lib/helpers/vsixutils.d.ts.map +1 -0
- package/types/lib/managers/docker.d.ts +12 -31
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +11 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/managers/piptree.d.ts.map +1 -1
- package/types/lib/parsers/npmrc.d.ts +4 -1
- package/types/lib/parsers/npmrc.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +22 -2
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts +20 -0
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -0
- package/types/lib/stages/postgen/postgen.d.ts +8 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts +18 -0
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -0
- package/types/lib/stages/pregen/envAudit.d.ts +8 -0
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -0
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/{helpers/validator.d.ts → validator/bomValidator.d.ts} +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -0
- package/types/lib/validator/complianceEngine.d.ts +66 -0
- package/types/lib/validator/complianceEngine.d.ts.map +1 -0
- package/types/lib/validator/complianceRules.d.ts +70 -0
- package/types/lib/validator/complianceRules.d.ts.map +1 -0
- package/types/lib/validator/index.d.ts +70 -0
- package/types/lib/validator/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/annotations.d.ts +31 -0
- package/types/lib/validator/reporters/annotations.d.ts.map +1 -0
- package/types/lib/validator/reporters/console.d.ts +30 -0
- package/types/lib/validator/reporters/console.d.ts.map +1 -0
- package/types/lib/validator/reporters/index.d.ts +21 -0
- package/types/lib/validator/reporters/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/json.d.ts +11 -0
- package/types/lib/validator/reporters/json.d.ts.map +1 -0
- package/types/lib/validator/reporters/sarif.d.ts +16 -0
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -0
- package/lib/helpers/db.js +0 -162
- package/lib/stages/pregen/env-audit.js +0 -34
- package/lib/stages/pregen/env-audit.poku.js +0 -290
- package/types/helpers/db.d.ts +0 -35
- package/types/helpers/db.d.ts.map +0 -1
- package/types/lib/helpers/db.d.ts +0 -35
- package/types/lib/helpers/db.d.ts.map +0 -1
- package/types/lib/helpers/validator.d.ts.map +0 -1
- package/types/lib/stages/pregen/env-audit.d.ts +0 -2
- package/types/lib/stages/pregen/env-audit.d.ts.map +0 -1
- package/types/managers/binary.d.ts +0 -37
- package/types/managers/binary.d.ts.map +0 -1
- package/types/managers/docker.d.ts +0 -56
- package/types/managers/docker.d.ts.map +0 -1
- package/types/managers/oci.d.ts +0 -2
- package/types/managers/oci.d.ts.map +0 -1
- package/types/managers/piptree.d.ts +0 -2
- package/types/managers/piptree.d.ts.map +0 -1
- package/types/server/server.d.ts +0 -34
- package/types/server/server.d.ts.map +0 -1
- package/types/stages/postgen/annotator.d.ts +0 -27
- package/types/stages/postgen/annotator.d.ts.map +0 -1
- package/types/stages/postgen/postgen.d.ts +0 -51
- package/types/stages/postgen/postgen.d.ts.map +0 -1
- package/types/stages/pregen/pregen.d.ts +0 -59
- package/types/stages/pregen/pregen.d.ts.map +0 -1
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
|
|
3
|
+
import { v4 as uuidv4 } from "uuid";
|
|
4
|
+
|
|
5
|
+
import { collectOSCryptoLibs } from "./cbomutils.js";
|
|
6
|
+
import { azurePipelinesParser } from "./ciParsers/azurePipelines.js";
|
|
7
|
+
import { circleCiParser } from "./ciParsers/circleCi.js";
|
|
8
|
+
import { githubActionsParser } from "./ciParsers/githubActions.js";
|
|
9
|
+
import { gitlabCiParser } from "./ciParsers/gitlabCi.js";
|
|
10
|
+
import { jenkinsParser } from "./ciParsers/jenkins.js";
|
|
11
|
+
import { trimComponents } from "./depsUtils.js";
|
|
12
|
+
import {
|
|
13
|
+
collectEnvInfo,
|
|
14
|
+
getBranch,
|
|
15
|
+
getOriginUrl,
|
|
16
|
+
gitTreeHashes,
|
|
17
|
+
listFiles,
|
|
18
|
+
} from "./envcontext.js";
|
|
19
|
+
import { getAllFiles } from "./utils.js";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The parser registry. Pre-populated with the five built-in CI system parsers.
|
|
23
|
+
*
|
|
24
|
+
* External parsers added via {@link registerParser} are appended here.
|
|
25
|
+
*
|
|
26
|
+
* Each entry must satisfy the FormulationParser contract:
|
|
27
|
+
* ```
|
|
28
|
+
* {
|
|
29
|
+
* id: string, // unique stable identifier
|
|
30
|
+
* patterns: string[], // non-empty array of glob patterns for file discovery
|
|
31
|
+
* parse(files: string[], options: Object): // synchronous function
|
|
32
|
+
* { workflows?, components?, services?, properties?, dependencies? }
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
const _parsers = [
|
|
37
|
+
githubActionsParser,
|
|
38
|
+
gitlabCiParser,
|
|
39
|
+
jenkinsParser,
|
|
40
|
+
circleCiParser,
|
|
41
|
+
azurePipelinesParser,
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Register an external formulation parser.
|
|
46
|
+
*
|
|
47
|
+
* The parser is appended to the registry and will be invoked by
|
|
48
|
+
* {@link addFormulationSection} on the next call.
|
|
49
|
+
*
|
|
50
|
+
* @param {{ id: string, patterns: string[], parse: Function }} parser
|
|
51
|
+
*/
|
|
52
|
+
export function registerParser(parser) {
|
|
53
|
+
const hasValidPatterns =
|
|
54
|
+
Array.isArray(parser?.patterns) &&
|
|
55
|
+
parser.patterns.length > 0 &&
|
|
56
|
+
parser.patterns.every(
|
|
57
|
+
(pattern) => typeof pattern === "string" && pattern.trim().length > 0,
|
|
58
|
+
);
|
|
59
|
+
if (
|
|
60
|
+
typeof parser?.id !== "string" ||
|
|
61
|
+
parser.id.trim().length === 0 ||
|
|
62
|
+
!hasValidPatterns ||
|
|
63
|
+
typeof parser?.parse !== "function"
|
|
64
|
+
) {
|
|
65
|
+
throw new TypeError(
|
|
66
|
+
"registerParser: parser must have id (string), patterns (non-empty string[]), and parse (function)",
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
_parsers.push(parser);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Return a shallow copy of the currently registered parsers.
|
|
74
|
+
*
|
|
75
|
+
* @returns {Array<{ id: string, patterns: string[], parse: Function }>}
|
|
76
|
+
*/
|
|
77
|
+
export function getParsers() {
|
|
78
|
+
return [..._parsers];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Environment-variable prefixes whose values are safe to include in the
|
|
83
|
+
* formulation section. All other variables are ignored.
|
|
84
|
+
*/
|
|
85
|
+
const ENV_PREFIXES = [
|
|
86
|
+
"GIT_",
|
|
87
|
+
"ANDROID_",
|
|
88
|
+
"DENO_",
|
|
89
|
+
"DOTNET_",
|
|
90
|
+
"JAVA_",
|
|
91
|
+
"SDKMAN_",
|
|
92
|
+
"CARGO_",
|
|
93
|
+
"CONDA_",
|
|
94
|
+
"RUST",
|
|
95
|
+
"GEM_",
|
|
96
|
+
"SCALA_",
|
|
97
|
+
"MAVEN_",
|
|
98
|
+
"GRADLE_",
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Sub-strings that, when found (case-insensitively) in the variable *name*
|
|
103
|
+
* or *value*, cause the variable to be excluded from the formulation section.
|
|
104
|
+
*
|
|
105
|
+
* This blocklist is intentionally conservative to avoid leaking secrets.
|
|
106
|
+
* Common CI tokens and credentials patterns are enumerated explicitly.
|
|
107
|
+
*/
|
|
108
|
+
const ENV_BLOCKLIST = [
|
|
109
|
+
"key",
|
|
110
|
+
"token",
|
|
111
|
+
"pass",
|
|
112
|
+
"secret",
|
|
113
|
+
"user",
|
|
114
|
+
"email",
|
|
115
|
+
"auth",
|
|
116
|
+
"session",
|
|
117
|
+
"proxy",
|
|
118
|
+
"cred",
|
|
119
|
+
"askpass",
|
|
120
|
+
"api_key",
|
|
121
|
+
"apikey",
|
|
122
|
+
"private",
|
|
123
|
+
"signature",
|
|
124
|
+
"webhook",
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Build the formulation section for a CycloneDX BOM.
|
|
129
|
+
*
|
|
130
|
+
* This function is the top-level aggregator: it collects git metadata,
|
|
131
|
+
* invokes every registered CI parser, and merges the results into a single
|
|
132
|
+
* CycloneDX formulation entry.
|
|
133
|
+
*
|
|
134
|
+
* The function falls back to a minimal stub workflow when no CI config files
|
|
135
|
+
* are detected at the given path.
|
|
136
|
+
*
|
|
137
|
+
* @param {string} filePath File path
|
|
138
|
+
* @param {Object} options CLI options; `options.path` is used as the
|
|
139
|
+
* project root for file discovery.
|
|
140
|
+
* @param {Object} [context={}] Optional context object. If it contains a
|
|
141
|
+
* non-empty `formulationList` array those
|
|
142
|
+
* components are merged into the result.
|
|
143
|
+
*
|
|
144
|
+
* @returns {{ formulation: Object[], dependencies: Object[] }}
|
|
145
|
+
* `formulation` – array to be placed at `bomJson.formulation`
|
|
146
|
+
* `dependencies` – dependency objects to be merged into
|
|
147
|
+
* `bomJson.dependencies` via `mergeDependencies`
|
|
148
|
+
*/
|
|
149
|
+
export function addFormulationSection(filePath, options, context = {}) {
|
|
150
|
+
const projectPath = filePath;
|
|
151
|
+
const formulation = [];
|
|
152
|
+
const dependencies = [];
|
|
153
|
+
|
|
154
|
+
// ── Git metadata ─────────────────────────────────────────────────────────
|
|
155
|
+
const gitBranch = getBranch(undefined, projectPath);
|
|
156
|
+
const originUrl = getOriginUrl(projectPath);
|
|
157
|
+
const gitFiles = listFiles(projectPath);
|
|
158
|
+
const treeHashes = gitTreeHashes(projectPath);
|
|
159
|
+
|
|
160
|
+
let components = [];
|
|
161
|
+
|
|
162
|
+
// Reuse any existing formulation components (e.g. from Pixi lock data)
|
|
163
|
+
// See: PR #1172
|
|
164
|
+
if (context?.formulationList?.length) {
|
|
165
|
+
components = components.concat(trimComponents(context.formulationList));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// OmniBOR / Artifact Dependency Graph components (spec 1.6+)
|
|
169
|
+
let parentOmniborId;
|
|
170
|
+
let treeOmniborId;
|
|
171
|
+
if (options.specVersion >= 1.6 && Object.keys(treeHashes).length === 2) {
|
|
172
|
+
// treeHashes.parent is the parent commit SHA → gitoid:commit:sha1:
|
|
173
|
+
// treeHashes.tree is the git tree object SHA → gitoid:tree:sha1:
|
|
174
|
+
parentOmniborId = `gitoid:commit:sha1:${treeHashes.parent}`;
|
|
175
|
+
treeOmniborId = `gitoid:tree:sha1:${treeHashes.tree}`;
|
|
176
|
+
components.push({
|
|
177
|
+
type: "file",
|
|
178
|
+
name: "git-parent",
|
|
179
|
+
description: "Git Parent Node.",
|
|
180
|
+
"bom-ref": parentOmniborId,
|
|
181
|
+
omniborId: [parentOmniborId],
|
|
182
|
+
swhid: [`swh:1:rev:${treeHashes.parent}`],
|
|
183
|
+
});
|
|
184
|
+
components.push({
|
|
185
|
+
type: "file",
|
|
186
|
+
name: "git-tree",
|
|
187
|
+
description: "Git Tree Node.",
|
|
188
|
+
"bom-ref": treeOmniborId,
|
|
189
|
+
omniborId: [treeOmniborId],
|
|
190
|
+
swhid: [`swh:1:dir:${treeHashes.tree}`],
|
|
191
|
+
});
|
|
192
|
+
// OmniBOR linkage goes into the top-level dependencies array
|
|
193
|
+
dependencies.push({ ref: parentOmniborId, provides: [treeOmniborId] });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Git file list
|
|
197
|
+
if (gitBranch && gitFiles?.length) {
|
|
198
|
+
const gitFileComponents = gitFiles.map((f) =>
|
|
199
|
+
options.specVersion >= 1.6
|
|
200
|
+
? {
|
|
201
|
+
type: "file",
|
|
202
|
+
name: f.name,
|
|
203
|
+
version: f.hash,
|
|
204
|
+
"bom-ref": f.omniborId,
|
|
205
|
+
omniborId: [f.omniborId],
|
|
206
|
+
swhid: [f.swhid],
|
|
207
|
+
}
|
|
208
|
+
: {
|
|
209
|
+
type: "file",
|
|
210
|
+
name: f.name,
|
|
211
|
+
version: f.hash,
|
|
212
|
+
},
|
|
213
|
+
);
|
|
214
|
+
components = components.concat(gitFileComponents);
|
|
215
|
+
|
|
216
|
+
// Complete the Artifact Dependency Graph: tree → blob links
|
|
217
|
+
if (options.specVersion >= 1.6 && treeOmniborId) {
|
|
218
|
+
dependencies.push({
|
|
219
|
+
ref: treeOmniborId,
|
|
220
|
+
provides: gitFiles.map((f) => f.omniborId).filter(Boolean),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Build environment details (Java, .NET, Python, Node, GCC, Rust, Go, Ruby)
|
|
226
|
+
const infoComponents = collectEnvInfo(projectPath);
|
|
227
|
+
if (infoComponents?.length) {
|
|
228
|
+
components = components.concat(infoComponents);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// OS crypto libraries (cbom mode)
|
|
232
|
+
if (options.includeCrypto) {
|
|
233
|
+
const cryptoLibs = collectOSCryptoLibs(options);
|
|
234
|
+
if (cryptoLibs?.length) {
|
|
235
|
+
components = components.concat(cryptoLibs);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ── CI parser dispatch ────────────────────────────────────────────────────
|
|
240
|
+
const ciWorkflows = [];
|
|
241
|
+
const ciComponents = [];
|
|
242
|
+
const ciServices = [];
|
|
243
|
+
const ciProperties = [];
|
|
244
|
+
|
|
245
|
+
const discoveryPath = projectPath || ".";
|
|
246
|
+
|
|
247
|
+
for (const parser of _parsers) {
|
|
248
|
+
const matchedFiles = [];
|
|
249
|
+
for (const pattern of parser.patterns) {
|
|
250
|
+
const found = getAllFiles(discoveryPath, pattern, options);
|
|
251
|
+
if (found?.length) {
|
|
252
|
+
matchedFiles.push(...found);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const uniqueMatchedFiles = [...new Set(matchedFiles)];
|
|
256
|
+
if (!uniqueMatchedFiles.length) {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
let result;
|
|
261
|
+
try {
|
|
262
|
+
result = parser.parse(uniqueMatchedFiles, options);
|
|
263
|
+
} catch (err) {
|
|
264
|
+
// A broken parser must not kill SBOM generation
|
|
265
|
+
console.warn(
|
|
266
|
+
`[formulationParsers] Parser "${parser.id}" threw an error:`,
|
|
267
|
+
err.message,
|
|
268
|
+
);
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (result?.workflows?.length) {
|
|
273
|
+
ciWorkflows.push(...result.workflows);
|
|
274
|
+
}
|
|
275
|
+
if (result?.components?.length) {
|
|
276
|
+
ciComponents.push(...result.components);
|
|
277
|
+
}
|
|
278
|
+
if (result?.services?.length) {
|
|
279
|
+
ciServices.push(...result.services);
|
|
280
|
+
}
|
|
281
|
+
if (result?.properties?.length) {
|
|
282
|
+
ciProperties.push(...result.properties);
|
|
283
|
+
}
|
|
284
|
+
if (result?.dependencies?.length) {
|
|
285
|
+
dependencies.push(...result.dependencies);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Merge CI components into the formulation component list
|
|
290
|
+
if (ciComponents.length) {
|
|
291
|
+
components = components.concat(ciComponents);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ── Environment variables ─────────────────────────────────────────────────
|
|
295
|
+
let environmentVars = gitBranch?.length
|
|
296
|
+
? [{ name: "GIT_BRANCH", value: gitBranch }]
|
|
297
|
+
: [];
|
|
298
|
+
|
|
299
|
+
for (const aevar of Object.keys(process.env)) {
|
|
300
|
+
const lower = aevar.toLowerCase();
|
|
301
|
+
const value = process.env[aevar] ?? "";
|
|
302
|
+
if (
|
|
303
|
+
ENV_PREFIXES.some((p) => aevar.startsWith(p)) &&
|
|
304
|
+
!ENV_BLOCKLIST.some((b) => lower.includes(b)) &&
|
|
305
|
+
!ENV_BLOCKLIST.some((b) => value.toLowerCase().includes(b)) &&
|
|
306
|
+
value.length
|
|
307
|
+
) {
|
|
308
|
+
environmentVars.push({ name: aevar, value });
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (!environmentVars.length) {
|
|
313
|
+
environmentVars = undefined;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// ── Assemble formulation object ───────────────────────────────────────────
|
|
317
|
+
const aformulation = {
|
|
318
|
+
"bom-ref": uuidv4(),
|
|
319
|
+
components: trimComponents(components),
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
if (ciServices.length) {
|
|
323
|
+
aformulation.services = ciServices;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (ciProperties.length) {
|
|
327
|
+
aformulation.properties = ciProperties;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Use CI-detected workflows; fall back to a minimal stub when none found
|
|
331
|
+
if (ciWorkflows.length) {
|
|
332
|
+
aformulation.workflows = ciWorkflows;
|
|
333
|
+
} else {
|
|
334
|
+
let sourceInput;
|
|
335
|
+
if (environmentVars) {
|
|
336
|
+
sourceInput = { environmentVars };
|
|
337
|
+
}
|
|
338
|
+
const sourceWorkflow = {
|
|
339
|
+
"bom-ref": uuidv4(),
|
|
340
|
+
uid: uuidv4(),
|
|
341
|
+
taskTypes: originUrl ? ["build", "clone"] : ["build"],
|
|
342
|
+
};
|
|
343
|
+
if (sourceInput) {
|
|
344
|
+
sourceWorkflow.inputs = [sourceInput];
|
|
345
|
+
}
|
|
346
|
+
aformulation.workflows = [sourceWorkflow];
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
formulation.push(aformulation);
|
|
350
|
+
return { formulation, dependencies };
|
|
351
|
+
}
|
package/lib/helpers/logger.js
CHANGED
|
@@ -44,6 +44,14 @@ const traceLogger = new Console({
|
|
|
44
44
|
if (THINK_MODE) {
|
|
45
45
|
thinkLogger.group(colorizeText("<think>"));
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Logs a thought message to the think logger if THINK_MODE is enabled.
|
|
49
|
+
* Automatically appends a period to the message if it lacks terminal punctuation.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} s The thought message to log
|
|
52
|
+
* @param {Object} [args] Optional additional arguments to log alongside the message
|
|
53
|
+
* @returns {void}
|
|
54
|
+
*/
|
|
47
55
|
export function thoughtLog(s, args) {
|
|
48
56
|
if (!THINK_MODE) {
|
|
49
57
|
return;
|
|
@@ -58,6 +66,12 @@ export function thoughtLog(s, args) {
|
|
|
58
66
|
thinkLogger.log(colorizeText(`${s}`));
|
|
59
67
|
}
|
|
60
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Closes the think log group by emitting the closing `</think>` marker.
|
|
71
|
+
* Has no effect if THINK_MODE is not enabled.
|
|
72
|
+
*
|
|
73
|
+
* @returns {void}
|
|
74
|
+
*/
|
|
61
75
|
export function thoughtEnd() {
|
|
62
76
|
if (THINK_MODE) {
|
|
63
77
|
thinkLogger.groupEnd();
|
package/lib/helpers/protobom.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { cdx_16, cdx_17 } from "@appthreat/cdx-proto";
|
|
4
4
|
import {
|
|
5
5
|
fromBinary,
|
|
6
6
|
fromJsonString,
|
|
@@ -32,10 +32,10 @@ const stringifyIfNeeded = (bomJson) => {
|
|
|
32
32
|
export const writeBinary = (bomJson, binFile) => {
|
|
33
33
|
if (bomJson && binFile) {
|
|
34
34
|
let bomSchema;
|
|
35
|
-
if (+bomJson.specVersion === 1.
|
|
36
|
-
bomSchema =
|
|
35
|
+
if (+bomJson.specVersion === 1.7) {
|
|
36
|
+
bomSchema = cdx_17.BomSchema;
|
|
37
37
|
} else {
|
|
38
|
-
bomSchema =
|
|
38
|
+
bomSchema = cdx_16.BomSchema;
|
|
39
39
|
}
|
|
40
40
|
writeFileSync(
|
|
41
41
|
binFile,
|
|
@@ -57,17 +57,17 @@ export const writeBinary = (bomJson, binFile) => {
|
|
|
57
57
|
*
|
|
58
58
|
* @param {string} binFile Binary file name
|
|
59
59
|
* @param {boolean} asJson Convert to JSON
|
|
60
|
-
* @param {number} specVersion Specification version. Defaults to 1.
|
|
60
|
+
* @param {number} specVersion Specification version. Defaults to 1.7
|
|
61
61
|
*/
|
|
62
|
-
export const readBinary = (binFile, asJson = true, specVersion = 1.
|
|
62
|
+
export const readBinary = (binFile, asJson = true, specVersion = 1.7) => {
|
|
63
63
|
if (!safeExistsSync(binFile)) {
|
|
64
64
|
return undefined;
|
|
65
65
|
}
|
|
66
66
|
let bomSchema;
|
|
67
|
-
if (specVersion === 1.
|
|
68
|
-
bomSchema =
|
|
67
|
+
if (specVersion === 1.7) {
|
|
68
|
+
bomSchema = cdx_17.BomSchema;
|
|
69
69
|
} else {
|
|
70
|
-
bomSchema =
|
|
70
|
+
bomSchema = cdx_16.BomSchema;
|
|
71
71
|
}
|
|
72
72
|
const bomObject = fromBinary(bomSchema, readFileSync(binFile), {
|
|
73
73
|
readUnknownFields: true,
|
|
@@ -266,6 +266,15 @@ function _findCondaPythonPackage(condaMetaDir) {
|
|
|
266
266
|
}
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
+
/**
|
|
270
|
+
* Determines the appropriate Python executable path from a virtual environment.
|
|
271
|
+
* Inspects the virtual environment metadata to detect the Python type (system,
|
|
272
|
+
* conda, pyenv, etc.) and returns the most specific executable found, falling
|
|
273
|
+
* back to the global `PYTHON_CMD` constant when no executable is detected.
|
|
274
|
+
*
|
|
275
|
+
* @param {string} env Path to the Python virtual environment directory
|
|
276
|
+
* @returns {string} Path to the Python executable or the fallback command name
|
|
277
|
+
*/
|
|
269
278
|
export function get_python_command_from_env(env) {
|
|
270
279
|
const fallbackCmd = PYTHON_CMD;
|
|
271
280
|
const meta = getVenvMetadata(env);
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns the Dependency-Track BOM API URL.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} serverUrl Dependency-Track server URL
|
|
7
|
+
* @returns {string} API URL to submit BOM payload
|
|
8
|
+
*/
|
|
9
|
+
export function getDependencyTrackBomUrl(serverUrl) {
|
|
10
|
+
return `${serverUrl.replace(/\/$/, "")}/api/v1/bom`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Build the payload for Dependency-Track BOM submission.
|
|
15
|
+
*
|
|
16
|
+
* @param {Object} args CLI/server arguments
|
|
17
|
+
* @param {Object} bomContents BOM Json
|
|
18
|
+
* @returns {Object | undefined} payload object if project coordinates are valid
|
|
19
|
+
*/
|
|
20
|
+
export function buildDependencyTrackBomPayload(args, bomContents) {
|
|
21
|
+
let encodedBomContents = Buffer.from(JSON.stringify(bomContents)).toString(
|
|
22
|
+
"base64",
|
|
23
|
+
);
|
|
24
|
+
if (encodedBomContents.startsWith("77u/")) {
|
|
25
|
+
encodedBomContents = encodedBomContents.substring(4);
|
|
26
|
+
}
|
|
27
|
+
const autoCreate =
|
|
28
|
+
typeof args.autoCreate === "boolean"
|
|
29
|
+
? args.autoCreate
|
|
30
|
+
: args.autoCreate !== "false";
|
|
31
|
+
const bomPayload = {
|
|
32
|
+
autoCreate: String(autoCreate),
|
|
33
|
+
bom: encodedBomContents,
|
|
34
|
+
};
|
|
35
|
+
if (
|
|
36
|
+
typeof args.projectId !== "undefined" ||
|
|
37
|
+
typeof args.projectName !== "undefined"
|
|
38
|
+
) {
|
|
39
|
+
if (typeof args.projectId !== "undefined") {
|
|
40
|
+
bomPayload.project = args.projectId;
|
|
41
|
+
}
|
|
42
|
+
if (typeof args.projectName !== "undefined") {
|
|
43
|
+
bomPayload.projectName = args.projectName;
|
|
44
|
+
}
|
|
45
|
+
// Dependency-Track submissions use "main" as fallback when no version is provided.
|
|
46
|
+
bomPayload.projectVersion = args.projectVersion || "main";
|
|
47
|
+
} else {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
const parentProjectId = args.parentProjectId || args.parentUUID;
|
|
51
|
+
const hasParentUuidMode = typeof parentProjectId !== "undefined";
|
|
52
|
+
const hasParentName = typeof args.parentProjectName !== "undefined";
|
|
53
|
+
const hasParentVersion = typeof args.parentProjectVersion !== "undefined";
|
|
54
|
+
const hasParentCoordsMode = hasParentName || hasParentVersion;
|
|
55
|
+
if (hasParentUuidMode && hasParentCoordsMode) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
if (!hasParentUuidMode && hasParentName !== hasParentVersion) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
if (hasParentUuidMode) {
|
|
62
|
+
bomPayload.parentUUID = parentProjectId;
|
|
63
|
+
}
|
|
64
|
+
if (hasParentName && hasParentVersion) {
|
|
65
|
+
bomPayload.parentName = args.parentProjectName;
|
|
66
|
+
bomPayload.parentVersion = args.parentProjectVersion;
|
|
67
|
+
}
|
|
68
|
+
if (
|
|
69
|
+
typeof args.isLatest === "boolean" ||
|
|
70
|
+
args.isLatest === "true" ||
|
|
71
|
+
args.isLatest === "false"
|
|
72
|
+
) {
|
|
73
|
+
bomPayload.isLatest =
|
|
74
|
+
typeof args.isLatest === "boolean"
|
|
75
|
+
? args.isLatest
|
|
76
|
+
: args.isLatest === "true";
|
|
77
|
+
}
|
|
78
|
+
if (typeof args.projectTag !== "undefined") {
|
|
79
|
+
bomPayload.projectTags = (
|
|
80
|
+
Array.isArray(args.projectTag) ? args.projectTag : [args.projectTag]
|
|
81
|
+
).map((tag) => ({ name: tag }));
|
|
82
|
+
}
|
|
83
|
+
return bomPayload;
|
|
84
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { assert, describe, it } from "poku";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
buildDependencyTrackBomPayload,
|
|
5
|
+
getDependencyTrackBomUrl,
|
|
6
|
+
} from "./dependency-track.js";
|
|
7
|
+
|
|
8
|
+
describe("Dependency-Track helper tests", () => {
|
|
9
|
+
it("returns submission URL without trailing slash duplication", () => {
|
|
10
|
+
assert.strictEqual(
|
|
11
|
+
getDependencyTrackBomUrl("https://dtrack.example.com/"),
|
|
12
|
+
"https://dtrack.example.com/api/v1/bom",
|
|
13
|
+
);
|
|
14
|
+
assert.strictEqual(
|
|
15
|
+
getDependencyTrackBomUrl("https://dtrack.example.com"),
|
|
16
|
+
"https://dtrack.example.com/api/v1/bom",
|
|
17
|
+
);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("builds payload with parentUUID and tags", () => {
|
|
21
|
+
const payload = buildDependencyTrackBomPayload(
|
|
22
|
+
{
|
|
23
|
+
projectName: "child",
|
|
24
|
+
projectVersion: "1.0.0",
|
|
25
|
+
parentProjectId: "d9628844-5f04-4ca7-88a2-64eb6bc64db0",
|
|
26
|
+
projectTag: ["tag1", "tag2"],
|
|
27
|
+
},
|
|
28
|
+
{ bom: "test" },
|
|
29
|
+
);
|
|
30
|
+
assert.deepStrictEqual(payload, {
|
|
31
|
+
autoCreate: "true",
|
|
32
|
+
bom: "eyJib20iOiJ0ZXN0In0=",
|
|
33
|
+
parentUUID: "d9628844-5f04-4ca7-88a2-64eb6bc64db0",
|
|
34
|
+
projectName: "child",
|
|
35
|
+
projectTags: [{ name: "tag1" }, { name: "tag2" }],
|
|
36
|
+
projectVersion: "1.0.0",
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("builds payload with parentName and parentVersion", () => {
|
|
41
|
+
const payload = buildDependencyTrackBomPayload(
|
|
42
|
+
{
|
|
43
|
+
projectName: "child",
|
|
44
|
+
projectVersion: "1.0.0",
|
|
45
|
+
parentProjectName: "parent",
|
|
46
|
+
parentProjectVersion: "2.0.0",
|
|
47
|
+
},
|
|
48
|
+
{ bom: "test2" },
|
|
49
|
+
);
|
|
50
|
+
assert.deepStrictEqual(payload, {
|
|
51
|
+
autoCreate: "true",
|
|
52
|
+
bom: "eyJib20iOiJ0ZXN0MiJ9",
|
|
53
|
+
parentName: "parent",
|
|
54
|
+
parentVersion: "2.0.0",
|
|
55
|
+
projectName: "child",
|
|
56
|
+
projectVersion: "1.0.0",
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("returns undefined when project identity is missing", () => {
|
|
61
|
+
const payload = buildDependencyTrackBomPayload({}, { bom: "test3" });
|
|
62
|
+
assert.strictEqual(payload, undefined);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("supports configurable autoCreate and isLatest", () => {
|
|
66
|
+
const payload = buildDependencyTrackBomPayload(
|
|
67
|
+
{
|
|
68
|
+
autoCreate: false,
|
|
69
|
+
isLatest: true,
|
|
70
|
+
projectName: "child",
|
|
71
|
+
},
|
|
72
|
+
{ bom: "test4" },
|
|
73
|
+
);
|
|
74
|
+
assert.deepStrictEqual(payload, {
|
|
75
|
+
autoCreate: "false",
|
|
76
|
+
bom: "eyJib20iOiJ0ZXN0NCJ9",
|
|
77
|
+
isLatest: true,
|
|
78
|
+
projectName: "child",
|
|
79
|
+
projectVersion: "main",
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("defaults projectVersion to main when only projectName is provided", () => {
|
|
84
|
+
const payload = buildDependencyTrackBomPayload(
|
|
85
|
+
{ projectName: "child" },
|
|
86
|
+
{ bom: "test5" },
|
|
87
|
+
);
|
|
88
|
+
assert.deepStrictEqual(payload, {
|
|
89
|
+
autoCreate: "true",
|
|
90
|
+
bom: "eyJib20iOiJ0ZXN0NSJ9",
|
|
91
|
+
projectName: "child",
|
|
92
|
+
projectVersion: "main",
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("returns undefined when parent UUID and parent name/version are both provided", () => {
|
|
97
|
+
const payload = buildDependencyTrackBomPayload(
|
|
98
|
+
{
|
|
99
|
+
parentProjectId: "d9628844-5f04-4ca7-88a2-64eb6bc64db0",
|
|
100
|
+
parentProjectName: "parent",
|
|
101
|
+
parentProjectVersion: "1.0.0",
|
|
102
|
+
projectName: "child",
|
|
103
|
+
},
|
|
104
|
+
{ bom: "test6" },
|
|
105
|
+
);
|
|
106
|
+
assert.strictEqual(payload, undefined);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("returns undefined when parent name/version mode is incomplete", () => {
|
|
110
|
+
const payload = buildDependencyTrackBomPayload(
|
|
111
|
+
{
|
|
112
|
+
parentProjectName: "parent",
|
|
113
|
+
projectName: "child",
|
|
114
|
+
},
|
|
115
|
+
{ bom: "test7" },
|
|
116
|
+
);
|
|
117
|
+
assert.strictEqual(payload, undefined);
|
|
118
|
+
});
|
|
119
|
+
});
|