@cdxgen/cdxgen 13.0.1 → 13.1.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 +26 -13
- package/bin/audit.js +6 -0
- package/bin/cdxgen.js +125 -4
- package/bin/evinse.js +1 -1
- package/data/component-tags.json +8 -1
- package/data/frameworks-list.json +86 -1
- package/data/remediations.json +1144 -0
- package/data/rules/asar-archives.yaml +1 -1
- package/data/rules/build-fidelity.yaml +782 -0
- package/data/rules/package-integrity.yaml +13 -13
- package/lib/audit/index.js +48 -4
- package/lib/audit/scoring.js +2 -8
- package/lib/cli/bomAssembly.js +20 -1
- package/lib/cli/cliOptions.js +94 -7
- package/lib/cli/index.js +76 -48
- package/lib/cli/jsBom.js +32 -3
- package/lib/cli/jvmBom.js +1494 -1315
- package/lib/cli/managedBom.js +97 -28
- package/lib/cli/nativeBom.js +189 -10
- package/lib/core/buildLedger.js +799 -0
- package/lib/core/deferredExit.js +141 -0
- package/lib/core/env.js +69 -1
- package/lib/core/fs.js +60 -0
- package/lib/core/logger.js +12 -99
- package/lib/core/severity.js +16 -0
- package/lib/core/syncFileWriter.js +106 -0
- package/lib/ecosystems/ecosystems.js +9 -0
- package/lib/ecosystems/gradleutils.js +355 -15
- package/lib/ecosystems/parsers-js.js +26 -0
- package/lib/ecosystems/parsers-jvm.js +9 -0
- package/lib/ecosystems/parsers-misc.js +16 -2
- package/lib/ecosystems/parsers-python.js +171 -35
- package/lib/ecosystems/parsers-rust.js +12 -2
- package/lib/ecosystems/pypiClassifiers.js +326 -0
- package/lib/ecosystems/pythonutils.js +49 -1
- package/lib/ecosystems/rubyutils.js +13 -0
- package/lib/evinser/evinser.js +30 -12
- package/lib/helpers/remote/dependency-track.js +81 -14
- package/lib/inventory/auditCategories.js +9 -0
- package/lib/inventory/depsUtils.js +72 -0
- package/lib/inventory/dosai.js +431 -22
- package/lib/inventory/envcontext.js +206 -18
- package/lib/inventory/fetchBatch.js +57 -0
- package/lib/inventory/jvmToolEnv.js +593 -0
- package/lib/inventory/osinfo.js +1 -0
- package/lib/inventory/toolRequirements.js +903 -0
- package/lib/managers/binary.js +24 -0
- package/lib/managers/docker.js +26 -5
- package/lib/server/server.js +1 -1
- package/lib/stages/postgen/annotationBuilder.js +97 -0
- package/lib/stages/postgen/auditBom.js +76 -79
- package/lib/stages/postgen/introspection/annotate.js +320 -0
- package/lib/stages/postgen/introspection/commandFacts.js +293 -0
- package/lib/stages/postgen/introspection/emit.js +192 -0
- package/lib/stages/postgen/introspection/formulationEvidence.js +427 -0
- package/lib/stages/postgen/introspection/markers.js +227 -0
- package/lib/stages/postgen/introspection/reflect.js +1214 -0
- package/lib/stages/postgen/introspection/report.js +1208 -0
- package/lib/stages/postgen/introspection/score.js +1067 -0
- package/lib/stages/postgen/introspection/shapeCommand.js +304 -0
- package/lib/stages/postgen/parentEdge.js +156 -0
- package/lib/stages/postgen/postgen.js +115 -2
- package/lib/stages/postgen/ruleEngine.js +17 -0
- package/lib/stages/pregen/envAudit.js +42 -2
- package/lib/stages/pregen/pregen.js +1171 -6
- package/lib/validator/bomValidator.js +11 -2
- package/package.json +14 -14
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/audit/scoring.d.ts +2 -8
- package/types/lib/audit/scoring.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +2 -1
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +8 -0
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/buildLedger.d.ts +272 -0
- package/types/lib/core/buildLedger.d.ts.map +1 -0
- package/types/lib/core/deferredExit.d.ts +84 -0
- package/types/lib/core/deferredExit.d.ts.map +1 -0
- package/types/lib/core/env.d.ts +22 -1
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +21 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/logger.d.ts +2 -11
- package/types/lib/core/logger.d.ts.map +1 -1
- package/types/lib/core/severity.d.ts +11 -0
- package/types/lib/core/severity.d.ts.map +1 -0
- package/types/lib/core/syncFileWriter.d.ts +33 -0
- package/types/lib/core/syncFileWriter.d.ts.map +1 -0
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +33 -0
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
- package/types/lib/ecosystems/pypiClassifiers.d.ts +80 -0
- package/types/lib/ecosystems/pypiClassifiers.d.ts.map +1 -0
- package/types/lib/ecosystems/pythonutils.d.ts +20 -0
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +5 -1
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/remote/dependency-track.d.ts +45 -4
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
- package/types/lib/inventory/auditCategories.d.ts +8 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -1
- package/types/lib/inventory/depsUtils.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +20 -0
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts +9 -0
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/jvmToolEnv.d.ts +189 -0
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -0
- package/types/lib/inventory/osinfo.d.ts.map +1 -1
- package/types/lib/inventory/toolRequirements.d.ts +216 -0
- package/types/lib/inventory/toolRequirements.d.ts.map +1 -0
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotationBuilder.d.ts +53 -0
- package/types/lib/stages/postgen/annotationBuilder.d.ts.map +1 -0
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/introspection/annotate.d.ts +81 -0
- package/types/lib/stages/postgen/introspection/annotate.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/commandFacts.d.ts +32 -0
- package/types/lib/stages/postgen/introspection/commandFacts.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/emit.d.ts +71 -0
- package/types/lib/stages/postgen/introspection/emit.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts +154 -0
- package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts +57 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/reflect.d.ts +77 -0
- package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/report.d.ts +85 -0
- package/types/lib/stages/postgen/introspection/report.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/score.d.ts +149 -0
- package/types/lib/stages/postgen/introspection/score.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/shapeCommand.d.ts +79 -0
- package/types/lib/stages/postgen/introspection/shapeCommand.d.ts.map +1 -0
- package/types/lib/stages/postgen/parentEdge.d.ts +35 -0
- package/types/lib/stages/postgen/parentEdge.d.ts.map +1 -0
- package/types/lib/stages/postgen/postgen.d.ts +2 -2
- 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/stages/pregen/pregen.d.ts +131 -0
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
|
@@ -250,7 +250,7 @@
|
|
|
250
250
|
and $prop($, 'cdx:cargo:hasNativeBuild') = 'true'
|
|
251
251
|
and $count($$.components[
|
|
252
252
|
$prop($, 'cdx:github:action:ecosystem') = 'cargo'
|
|
253
|
-
and $contains($
|
|
253
|
+
and $contains($nullSafeProp($, 'cdx:github:action:role'), 'toolchain')
|
|
254
254
|
and $prop($, 'cdx:github:action:versionPinningType') != 'sha'
|
|
255
255
|
]) > 0
|
|
256
256
|
]
|
|
@@ -267,7 +267,7 @@
|
|
|
267
267
|
"buildScriptCapabilities": $prop($, 'cdx:cargo:buildScriptCapabilities'),
|
|
268
268
|
"toolchainActions": $$.components[
|
|
269
269
|
$prop($, 'cdx:github:action:ecosystem') = 'cargo'
|
|
270
|
-
and $contains($
|
|
270
|
+
and $contains($nullSafeProp($, 'cdx:github:action:role'), 'toolchain')
|
|
271
271
|
and $prop($, 'cdx:github:action:versionPinningType') != 'sha'
|
|
272
272
|
].$prop($, 'cdx:github:action:uses')
|
|
273
273
|
}
|
|
@@ -283,17 +283,17 @@
|
|
|
283
283
|
$prop($, 'cdx:rust:buildTool') = 'cargo'
|
|
284
284
|
and $prop($, 'cdx:cargo:hasNativeBuild') = 'true'
|
|
285
285
|
and (
|
|
286
|
-
$contains($
|
|
287
|
-
or $contains($
|
|
288
|
-
or $contains($
|
|
286
|
+
$contains($nullSafeProp($, 'cdx:cargo:buildScriptCapabilities'), 'process-execution')
|
|
287
|
+
or $contains($nullSafeProp($, 'cdx:cargo:buildScriptCapabilities'), 'network-access')
|
|
288
|
+
or $contains($nullSafeProp($, 'cdx:cargo:nativeBuildIndicators'), '-sys')
|
|
289
289
|
)
|
|
290
290
|
and $count($$.components[
|
|
291
291
|
$prop($, 'cdx:github:step:usesCargo') = 'true'
|
|
292
292
|
and (
|
|
293
|
-
$contains($
|
|
294
|
-
or $contains($
|
|
295
|
-
or $contains($
|
|
296
|
-
or $contains($
|
|
293
|
+
$contains($nullSafeProp($, 'cdx:github:step:cargoSubcommands'), 'build')
|
|
294
|
+
or $contains($nullSafeProp($, 'cdx:github:step:cargoSubcommands'), 'test')
|
|
295
|
+
or $contains($nullSafeProp($, 'cdx:github:step:cargoSubcommands'), 'package')
|
|
296
|
+
or $contains($nullSafeProp($, 'cdx:github:step:cargoSubcommands'), 'publish')
|
|
297
297
|
)
|
|
298
298
|
]) > 0
|
|
299
299
|
]
|
|
@@ -311,10 +311,10 @@
|
|
|
311
311
|
"cargoCommands": $$.components[
|
|
312
312
|
$prop($, 'cdx:github:step:usesCargo') = 'true'
|
|
313
313
|
and (
|
|
314
|
-
$contains($
|
|
315
|
-
or $contains($
|
|
316
|
-
or $contains($
|
|
317
|
-
or $contains($
|
|
314
|
+
$contains($nullSafeProp($, 'cdx:github:step:cargoSubcommands'), 'build')
|
|
315
|
+
or $contains($nullSafeProp($, 'cdx:github:step:cargoSubcommands'), 'test')
|
|
316
|
+
or $contains($nullSafeProp($, 'cdx:github:step:cargoSubcommands'), 'package')
|
|
317
|
+
or $contains($nullSafeProp($, 'cdx:github:step:cargoSubcommands'), 'publish')
|
|
318
318
|
)
|
|
319
319
|
].$prop($, 'cdx:github:step:command')
|
|
320
320
|
}
|
package/lib/audit/index.js
CHANGED
|
@@ -6,9 +6,11 @@ import process from "node:process";
|
|
|
6
6
|
import { createBom } from "../cli/index.js";
|
|
7
7
|
import {
|
|
8
8
|
isDryRun,
|
|
9
|
+
isSecureMode,
|
|
9
10
|
readEnvironmentVariable,
|
|
10
11
|
recordActivity,
|
|
11
12
|
} from "../core/activity.js";
|
|
13
|
+
import { getRemediationCatalog } from "../core/buildLedger.js";
|
|
12
14
|
import {
|
|
13
15
|
getTmpDir,
|
|
14
16
|
safeExistsSync,
|
|
@@ -53,6 +55,12 @@ import {
|
|
|
53
55
|
isHbomLikeBom,
|
|
54
56
|
isObomLikeBom,
|
|
55
57
|
} from "../stages/postgen/auditBom.js";
|
|
58
|
+
import {
|
|
59
|
+
isIntrospectionEnabled,
|
|
60
|
+
reflectOnRun,
|
|
61
|
+
} from "../stages/postgen/introspection/reflect.js";
|
|
62
|
+
import { buildIntrospectionJson } from "../stages/postgen/introspection/report.js";
|
|
63
|
+
import { scoreReflection } from "../stages/postgen/introspection/score.js";
|
|
56
64
|
import { postProcess } from "../stages/postgen/postgen.js";
|
|
57
65
|
import { formatTargetLabel } from "./progress.js";
|
|
58
66
|
import { renderAuditReport } from "./reporters.js";
|
|
@@ -259,7 +267,7 @@ export async function runDirectBomAuditFromBoms(inputBoms, options = {}) {
|
|
|
259
267
|
...directAuditOptions,
|
|
260
268
|
getBomWithOras,
|
|
261
269
|
});
|
|
262
|
-
|
|
270
|
+
const result = {
|
|
263
271
|
auditOptions: directAuditOptions,
|
|
264
272
|
bomFormat: getCycloneDxFormat(inputBom.bomJson),
|
|
265
273
|
findings,
|
|
@@ -268,7 +276,43 @@ export async function runDirectBomAuditFromBoms(inputBoms, options = {}) {
|
|
|
268
276
|
specVersion: inputBom.bomJson?.specVersion,
|
|
269
277
|
status: "audited",
|
|
270
278
|
summary: summarizeDirectAuditFindings(findings),
|
|
271
|
-
}
|
|
279
|
+
};
|
|
280
|
+
if (isIntrospectionEnabled(options)) {
|
|
281
|
+
// The audited BOM was not generated by this run, so there is no ledger
|
|
282
|
+
// of its own; the verdict rests on BOM structure alone and says so.
|
|
283
|
+
try {
|
|
284
|
+
const reflection = await reflectOnRun(inputBom.bomJson, options, {
|
|
285
|
+
ledgerEvents: [],
|
|
286
|
+
ledgerSource: "none",
|
|
287
|
+
projectPath: inputBom.source || "",
|
|
288
|
+
});
|
|
289
|
+
reflection.scoring = scoreReflection(
|
|
290
|
+
reflection,
|
|
291
|
+
getRemediationCatalog(),
|
|
292
|
+
{
|
|
293
|
+
secureMode: isSecureMode,
|
|
294
|
+
inContainer:
|
|
295
|
+
readEnvironmentVariable("CDXGEN_IN_CONTAINER") === "true",
|
|
296
|
+
},
|
|
297
|
+
);
|
|
298
|
+
reflection.bom = {
|
|
299
|
+
serialNumber: inputBom.bomJson?.serialNumber,
|
|
300
|
+
componentCount: Array.isArray(inputBom.bomJson?.components)
|
|
301
|
+
? inputBom.bomJson.components.length
|
|
302
|
+
: 0,
|
|
303
|
+
};
|
|
304
|
+
result.introspection = buildIntrospectionJson(
|
|
305
|
+
reflection,
|
|
306
|
+
reflection.scoring,
|
|
307
|
+
options,
|
|
308
|
+
);
|
|
309
|
+
} catch (err) {
|
|
310
|
+
console.warn(
|
|
311
|
+
`Build introspection failed to complete and was skipped: ${err?.message}`,
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
results.push(result);
|
|
272
316
|
}
|
|
273
317
|
const summary = {
|
|
274
318
|
findingsBySeverity: {
|
|
@@ -1744,7 +1788,7 @@ async function recheckFindingsAgainstDefaultBranch(
|
|
|
1744
1788
|
if (!bomNSData?.bomJson) {
|
|
1745
1789
|
return undefined;
|
|
1746
1790
|
}
|
|
1747
|
-
const processedBomNSData = postProcess(
|
|
1791
|
+
const processedBomNSData = await postProcess(
|
|
1748
1792
|
bomNSData,
|
|
1749
1793
|
childOptions,
|
|
1750
1794
|
defaultSourceSelection.scanDir,
|
|
@@ -1948,7 +1992,7 @@ export async function auditTarget(target, options) {
|
|
|
1948
1992
|
options,
|
|
1949
1993
|
);
|
|
1950
1994
|
}
|
|
1951
|
-
const processedBomNSData = postProcess(
|
|
1995
|
+
const processedBomNSData = await postProcess(
|
|
1952
1996
|
bomNSData,
|
|
1953
1997
|
childOptions,
|
|
1954
1998
|
sourceSelection.scanDir,
|
package/lib/audit/scoring.js
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
import { thoughtLog } from "../core/logger.js";
|
|
2
|
+
import { SEVERITY_ORDER } from "../core/severity.js";
|
|
2
3
|
import {
|
|
3
4
|
hasRegistryProvenanceEvidenceProperties,
|
|
4
5
|
hasTrustedPublishingProperties,
|
|
5
6
|
} from "../inventory/provenanceUtils.js";
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
export const SEVERITY_ORDER = {
|
|
9
|
-
none: -1,
|
|
10
|
-
low: 0,
|
|
11
|
-
medium: 1,
|
|
12
|
-
high: 2,
|
|
13
|
-
critical: 3,
|
|
14
|
-
};
|
|
8
|
+
export { SEVERITY_ORDER };
|
|
15
9
|
|
|
16
10
|
const BASE_FINDING_WEIGHT = {
|
|
17
11
|
low: 4,
|
package/lib/cli/bomAssembly.js
CHANGED
|
@@ -200,6 +200,8 @@ export const determineParentComponent = (options) => {
|
|
|
200
200
|
* @param {object|undefined} detectedParent Parent the generator derived
|
|
201
201
|
* @param {object[]} components The component list, appended to in place
|
|
202
202
|
* @param {object[]} dependencies Edges the generator produced
|
|
203
|
+
* @param {object} options CLI options
|
|
204
|
+
* @param {string} ptype Package type
|
|
203
205
|
* @returns {object[]} Edges to emit, re-anchored when an override applies
|
|
204
206
|
*/
|
|
205
207
|
function anchorDetectedParent(
|
|
@@ -207,6 +209,8 @@ function anchorDetectedParent(
|
|
|
207
209
|
detectedParent,
|
|
208
210
|
components,
|
|
209
211
|
dependencies,
|
|
212
|
+
options,
|
|
213
|
+
ptype,
|
|
210
214
|
) {
|
|
211
215
|
const detectedRef = detectedParent?.["bom-ref"];
|
|
212
216
|
const overrideRef = overrideParent?.["bom-ref"];
|
|
@@ -225,7 +229,20 @@ function anchorDetectedParent(
|
|
|
225
229
|
// `metadata.component` is cleaned of. As a regular component it must go
|
|
226
230
|
// through the same conversion or schema validation rejects the document
|
|
227
231
|
// (issue #4320). The copy keeps the caller's context object untouched.
|
|
228
|
-
|
|
232
|
+
//
|
|
233
|
+
// Its workspace members go through `listComponents`, the same conversion
|
|
234
|
+
// `addMetadata` gives them when the detected parent is the one reaching
|
|
235
|
+
// `metadata.component`. Without it they keep the raw parser shape
|
|
236
|
+
// (`homepage: { url }`) that the nested Component schema rejects
|
|
237
|
+
// (issue #4326).
|
|
238
|
+
components.push(
|
|
239
|
+
cleanParentComponent({
|
|
240
|
+
...detectedParent,
|
|
241
|
+
components: detectedParent.components?.length
|
|
242
|
+
? listComponents(options, {}, detectedParent.components, ptype)
|
|
243
|
+
: undefined,
|
|
244
|
+
}),
|
|
245
|
+
);
|
|
229
246
|
}
|
|
230
247
|
// The caller owns the array it passed, and postProcess merges these edges
|
|
231
248
|
// across generators, so the re-anchoring is returned rather than spliced in.
|
|
@@ -1446,6 +1463,8 @@ export const buildBomNSData = (options, pkgInfo, ptype, context) => {
|
|
|
1446
1463
|
context.parentComponent,
|
|
1447
1464
|
components,
|
|
1448
1465
|
context.dependencies || [],
|
|
1466
|
+
options,
|
|
1467
|
+
ptype,
|
|
1449
1468
|
);
|
|
1450
1469
|
if (
|
|
1451
1470
|
components &&
|
package/lib/cli/cliOptions.js
CHANGED
|
@@ -88,6 +88,21 @@ const POST_BUILD_SUPPORTED_PROJECT_TYPES = new Set([
|
|
|
88
88
|
// Helpers
|
|
89
89
|
// ---------------------------------------------------------------------------
|
|
90
90
|
|
|
91
|
+
/**
|
|
92
|
+
* The formulation privacy notice, shared by every path that turns formulation
|
|
93
|
+
* collection on automatically: the warning about emails and secrets is
|
|
94
|
+
* identical whichever caller adds the section.
|
|
95
|
+
*
|
|
96
|
+
* @returns {OptionsWarning} The notice.
|
|
97
|
+
*/
|
|
98
|
+
function formulationPrivacyNotice() {
|
|
99
|
+
return {
|
|
100
|
+
level: "info",
|
|
101
|
+
message:
|
|
102
|
+
"NOTE: Automatically collecting formulation information. The section may include sensitive data such as emails and secrets.\nPlease review the generated SBOM before distribution or LLM training.",
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
91
106
|
/**
|
|
92
107
|
* Check whether a value was explicitly provided by the user (i.e., is not
|
|
93
108
|
* the yargs default). Yargs always populates default values, so we cannot
|
|
@@ -175,7 +190,39 @@ function resolveOutputTarget(args, { filePath, isRemoteOrPurl }) {
|
|
|
175
190
|
: resolve(join(filePath, args.output));
|
|
176
191
|
}
|
|
177
192
|
|
|
178
|
-
|
|
193
|
+
/**
|
|
194
|
+
* Resolve the introspection opt-in from the flag and the environment.
|
|
195
|
+
*
|
|
196
|
+
* The parser maps `CDXGEN_INTROSPECT` onto the same boolean the flag sets, so
|
|
197
|
+
* a value outside the documented opt-in vocabulary — an exported but empty
|
|
198
|
+
* variable most of all — arrives indistinguishable from `--no-introspect`.
|
|
199
|
+
* The command line is therefore the authority on refusal, and the environment
|
|
200
|
+
* speaks only through `true` and `1`. An unmentioned flag stays undefined so a
|
|
201
|
+
* profile can still enable it.
|
|
202
|
+
*
|
|
203
|
+
* @param {Object} args Parsed CLI arguments.
|
|
204
|
+
* @param {string[]} argv Raw command-line arguments the parser was given.
|
|
205
|
+
* @returns {boolean|undefined} The requested state, or undefined when nothing asked.
|
|
206
|
+
*/
|
|
207
|
+
function resolveIntrospectOption(args, argv) {
|
|
208
|
+
const refused =
|
|
209
|
+
args.introspect === false &&
|
|
210
|
+
argv.some((argument) =>
|
|
211
|
+
["--no-introspect", "--introspect=false"].includes(argument),
|
|
212
|
+
);
|
|
213
|
+
if (refused) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
return args.introspect === true ||
|
|
217
|
+
["true", "1"].includes(readEnvironmentVariable("CDXGEN_INTROSPECT"))
|
|
218
|
+
? true
|
|
219
|
+
: undefined;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function buildInitialOptions(
|
|
223
|
+
args,
|
|
224
|
+
{ filePath, isRemoteOrPurl, argv = process.argv },
|
|
225
|
+
) {
|
|
179
226
|
const res = Object.assign({}, args, {
|
|
180
227
|
projectType: args.type,
|
|
181
228
|
multiProject: args.recurse,
|
|
@@ -188,6 +235,10 @@ export function buildInitialOptions(args, { filePath, isRemoteOrPurl }) {
|
|
|
188
235
|
include: args.include || args.includeRegex,
|
|
189
236
|
noIgnore: args.noIgnore,
|
|
190
237
|
gitAiNotesRef: args.gitAiNotesRef || "refs/notes/ai",
|
|
238
|
+
// Introspection: the flag or the documented environment opt-in. An
|
|
239
|
+
// explicit --no-introspect stays `false` and beats both, while an
|
|
240
|
+
// unmentioned flag stays undefined so a profile can still enable it.
|
|
241
|
+
introspect: resolveIntrospectOption(args, argv),
|
|
191
242
|
// License normalization and enrichment are on by default; the only escape
|
|
192
243
|
// hatch is the corresponding environment variable.
|
|
193
244
|
licenseEnhance:
|
|
@@ -318,6 +369,20 @@ export function applyPostConstructionOverrides(options, context) {
|
|
|
318
369
|
options.installDeps = false;
|
|
319
370
|
}
|
|
320
371
|
|
|
372
|
+
// --- Introspection CI gate threshold -----------------------------------
|
|
373
|
+
if (options.introspectFailBelow !== undefined) {
|
|
374
|
+
const threshold = Number(options.introspectFailBelow);
|
|
375
|
+
if (!Number.isFinite(threshold) || threshold < 0 || threshold > 100) {
|
|
376
|
+
warnings.push({
|
|
377
|
+
level: "error",
|
|
378
|
+
message:
|
|
379
|
+
"--introspect-fail-below requires a number between 0 and 100, where absent means the gate never fails.",
|
|
380
|
+
});
|
|
381
|
+
} else {
|
|
382
|
+
options.introspectFailBelow = threshold;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
321
386
|
// When a standard is requested, the minimum spec version is 1.6
|
|
322
387
|
if (options.standard) {
|
|
323
388
|
if (!userSetSpecVersion) {
|
|
@@ -381,6 +446,8 @@ export function applyAdvancedOptions(options, context = {}) {
|
|
|
381
446
|
const secureMode = context.isSecureMode ?? isSecureMode;
|
|
382
447
|
const warnings = [];
|
|
383
448
|
const isHbomOnly = isHbomOnlyProjectTypes(options.projectType);
|
|
449
|
+
const includeFormulationBeforeProfiles = options.includeFormulation === true;
|
|
450
|
+
const introspectExplicitlyDisabled = options.introspect === false;
|
|
384
451
|
|
|
385
452
|
// --- Profile expansion -------------------------------------------------
|
|
386
453
|
switch (options.profile) {
|
|
@@ -412,6 +479,14 @@ export function applyAdvancedOptions(options, context = {}) {
|
|
|
412
479
|
case "license-compliance":
|
|
413
480
|
process.env.FETCH_LICENSE = "true";
|
|
414
481
|
break;
|
|
482
|
+
case "introspect":
|
|
483
|
+
options.introspect = true;
|
|
484
|
+
options.introspectAnnotate = options.introspectAnnotate !== false;
|
|
485
|
+
// The ledger's own record of what ran; formulation is where a consumer
|
|
486
|
+
// looks for the build environment the SBOM was produced in.
|
|
487
|
+
options.includeFormulation = !isHbomOnly;
|
|
488
|
+
options.evidence = true;
|
|
489
|
+
break;
|
|
415
490
|
case "ml-tiny":
|
|
416
491
|
process.env.FETCH_LICENSE = "true";
|
|
417
492
|
options.deep = false;
|
|
@@ -471,17 +546,29 @@ export function applyAdvancedOptions(options, context = {}) {
|
|
|
471
546
|
}
|
|
472
547
|
}
|
|
473
548
|
|
|
549
|
+
// An explicit --no-introspect negates the introspect profile's headline
|
|
550
|
+
// capability while keeping the bundle's other effects: the user asked for
|
|
551
|
+
// the profile minus introspection.
|
|
552
|
+
if (options.profile === "introspect" && introspectExplicitlyDisabled) {
|
|
553
|
+
options.introspect = false;
|
|
554
|
+
warnings.push({
|
|
555
|
+
level: "info",
|
|
556
|
+
message:
|
|
557
|
+
"NOTE: Introspection stays off because --no-introspect was passed explicitly; the other introspect profile settings still apply.",
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
|
|
474
561
|
// --- BOM audit → auto-formulation --------------------------------------
|
|
475
|
-
|
|
562
|
+
const introspectProfileAddsFormulation =
|
|
563
|
+
options.profile === "introspect" && !includeFormulationBeforeProfiles;
|
|
564
|
+
if (options.bomAudit || introspectProfileAddsFormulation) {
|
|
476
565
|
if (isHbomOnly) {
|
|
477
566
|
// HBOM-only: skip formulation
|
|
478
567
|
} else if (!options.includeFormulation) {
|
|
479
|
-
warnings.push(
|
|
480
|
-
level: "info",
|
|
481
|
-
message:
|
|
482
|
-
"NOTE: Automatically collecting formulation information. The section may include sensitive data such as emails and secrets.\nPlease review the generated SBOM before distribution or LLM training.",
|
|
483
|
-
});
|
|
568
|
+
warnings.push(formulationPrivacyNotice());
|
|
484
569
|
options.includeFormulation = true;
|
|
570
|
+
} else if (introspectProfileAddsFormulation) {
|
|
571
|
+
warnings.push(formulationPrivacyNotice());
|
|
485
572
|
}
|
|
486
573
|
}
|
|
487
574
|
|
package/lib/cli/index.js
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
recordActivity,
|
|
19
19
|
setActivityContext,
|
|
20
20
|
} from "../core/activity.js";
|
|
21
|
+
import { recordDegradation } from "../core/buildLedger.js";
|
|
21
22
|
import { hasAnyProjectType, PROJECT_TYPE_ALIASES } from "../core/env.js";
|
|
22
23
|
import {
|
|
23
24
|
clearFileDiscoveryCache,
|
|
@@ -40,7 +41,7 @@ import {
|
|
|
40
41
|
} from "../ecosystems/parsers-misc.js";
|
|
41
42
|
import { isPyLockFile } from "../ecosystems/pylockutils.js";
|
|
42
43
|
import {
|
|
43
|
-
|
|
44
|
+
buildDependencyTrackBomRequest,
|
|
44
45
|
getDependencyTrackBomApiUrl,
|
|
45
46
|
} from "../helpers/remote/dependency-track.js";
|
|
46
47
|
import { optionIncludesAiInventoryProjectType } from "../inventory/aiInventory.js";
|
|
@@ -173,6 +174,11 @@ export {
|
|
|
173
174
|
createZigBom,
|
|
174
175
|
} from "./nativeBom.js";
|
|
175
176
|
|
|
177
|
+
import {
|
|
178
|
+
deferFailOnError,
|
|
179
|
+
isDeferredFailOnError,
|
|
180
|
+
} from "../core/deferredExit.js";
|
|
181
|
+
|
|
176
182
|
const dirName = dirNameStr;
|
|
177
183
|
|
|
178
184
|
const isWin = _platform() === "win32";
|
|
@@ -816,6 +822,23 @@ export async function createMultiXBom(pathList, options) {
|
|
|
816
822
|
// from here on, so repeated walks of them can share one view of the tree.
|
|
817
823
|
setDirWalkCacheRoot(options.allLayersExplodedDir);
|
|
818
824
|
const scanOnePath = async (path) => {
|
|
825
|
+
try {
|
|
826
|
+
return await scanOnePathInner(path);
|
|
827
|
+
} catch (err) {
|
|
828
|
+
if (!isDeferredFailOnError(err)) {
|
|
829
|
+
throw err;
|
|
830
|
+
}
|
|
831
|
+
// The failing path contributes nothing, and the remaining paths still
|
|
832
|
+
// do: fail-on-error scopes the loss to the leg that failed.
|
|
833
|
+
return {
|
|
834
|
+
components: [],
|
|
835
|
+
dependencies: [],
|
|
836
|
+
options: { ...sharedOptions },
|
|
837
|
+
parentSubComponents: [],
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
};
|
|
841
|
+
const scanOnePathInner = async (path) => {
|
|
819
842
|
// Each path accumulates into its own lists and its own view of options, so
|
|
820
843
|
// that legs which record a parent component or a tool cannot overwrite
|
|
821
844
|
// another path's. The lists are merged below in pathList order.
|
|
@@ -1433,6 +1456,14 @@ export async function createMultiXBom(pathList, options) {
|
|
|
1433
1456
|
);
|
|
1434
1457
|
}
|
|
1435
1458
|
setProjectTypeActivityContext("jar", path);
|
|
1459
|
+
recordDegradation("jvm.jar.heuristic", {
|
|
1460
|
+
ecosystem: "java",
|
|
1461
|
+
tool: "jar",
|
|
1462
|
+
impact: "versions",
|
|
1463
|
+
path,
|
|
1464
|
+
detail:
|
|
1465
|
+
"Components are being inferred by scanning jar/war/ear files from disk.",
|
|
1466
|
+
});
|
|
1436
1467
|
bomData = await createJarBom(path, options);
|
|
1437
1468
|
if (bomData?.bomJson?.components?.length) {
|
|
1438
1469
|
thoughtLog(
|
|
@@ -2292,7 +2323,28 @@ export async function createHBom(_path, options) {
|
|
|
2292
2323
|
* @param {Object} options Parse options from the cli
|
|
2293
2324
|
* @returns {Promise<Object>} Promise resolving to BOM object
|
|
2294
2325
|
*/
|
|
2326
|
+
/**
|
|
2327
|
+
* Generate a BOM for one path, letting a deferred fail-on-error abort return
|
|
2328
|
+
* the empty BOM the flag promises rather than unwinding past the reports.
|
|
2329
|
+
*
|
|
2330
|
+
* @param {string} path Source path.
|
|
2331
|
+
* @param {Object} options CLI options.
|
|
2332
|
+
* @returns {Promise<Object>} BOM NS data.
|
|
2333
|
+
*/
|
|
2295
2334
|
export async function createBom(path, options) {
|
|
2335
|
+
try {
|
|
2336
|
+
return await createBomForPath(path, options);
|
|
2337
|
+
} catch (err) {
|
|
2338
|
+
if (!isDeferredFailOnError(err)) {
|
|
2339
|
+
throw err;
|
|
2340
|
+
}
|
|
2341
|
+
// fail-on-error forbids an incomplete result, so the extractor kept
|
|
2342
|
+
// nothing; the CLI still writes the reports and exits non-zero.
|
|
2343
|
+
return buildBomNSData(options, [], "", { src: path, filename: "" });
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
async function createBomForPath(path, options) {
|
|
2296
2348
|
clearFileDiscoveryCache();
|
|
2297
2349
|
let { projectType } = options;
|
|
2298
2350
|
if (!projectType) {
|
|
@@ -2327,7 +2379,11 @@ export async function createBom(path, options) {
|
|
|
2327
2379
|
console.log(
|
|
2328
2380
|
`OS BOM generation has failed due to problems with exporting the image ${path}`,
|
|
2329
2381
|
);
|
|
2330
|
-
options
|
|
2382
|
+
deferFailOnError(options, {
|
|
2383
|
+
ecosystem: "container",
|
|
2384
|
+
tool: "docker",
|
|
2385
|
+
detail: "exporting the image for the OS BOM failed",
|
|
2386
|
+
});
|
|
2331
2387
|
return {};
|
|
2332
2388
|
}
|
|
2333
2389
|
isContainerMode = true;
|
|
@@ -2367,7 +2423,11 @@ export async function createBom(path, options) {
|
|
|
2367
2423
|
console.log(
|
|
2368
2424
|
`OCI BOM generation has failed due to problems with exporting the image ${path}.`,
|
|
2369
2425
|
);
|
|
2370
|
-
options
|
|
2426
|
+
deferFailOnError(options, {
|
|
2427
|
+
ecosystem: "container",
|
|
2428
|
+
tool: "docker",
|
|
2429
|
+
detail: "exporting the image for the OCI BOM failed",
|
|
2430
|
+
});
|
|
2371
2431
|
return {};
|
|
2372
2432
|
}
|
|
2373
2433
|
if (DEBUG_MODE) {
|
|
@@ -2660,8 +2720,8 @@ export async function submitBom(args, bomContents) {
|
|
|
2660
2720
|
});
|
|
2661
2721
|
return undefined;
|
|
2662
2722
|
}
|
|
2663
|
-
const
|
|
2664
|
-
if (!
|
|
2723
|
+
const bomRequest = buildDependencyTrackBomRequest(args, bomContents);
|
|
2724
|
+
if (!bomRequest) {
|
|
2665
2725
|
console.log(
|
|
2666
2726
|
"Invalid Dependency-Track submission arguments. Provide projectId or projectName (projectVersion defaults to main) and specify parent project either by UUID or by parent project name + version.",
|
|
2667
2727
|
);
|
|
@@ -2674,21 +2734,21 @@ export async function submitBom(args, bomContents) {
|
|
|
2674
2734
|
serverUrl,
|
|
2675
2735
|
"params",
|
|
2676
2736
|
args.projectName,
|
|
2677
|
-
|
|
2737
|
+
args.projectVersion || "main",
|
|
2678
2738
|
);
|
|
2679
2739
|
}
|
|
2680
2740
|
try {
|
|
2681
2741
|
const requestOptions = {
|
|
2682
|
-
method: "
|
|
2742
|
+
method: "POST",
|
|
2683
2743
|
followRedirect: false,
|
|
2684
2744
|
https: {
|
|
2685
2745
|
rejectUnauthorized: !args.skipDtTlsCheck,
|
|
2686
2746
|
},
|
|
2687
2747
|
headers: {
|
|
2688
2748
|
"X-Api-Key": (args.apiKey || "").replace(/[\r\n]/g, ""),
|
|
2689
|
-
"Content-Type":
|
|
2749
|
+
"Content-Type": bomRequest.contentType,
|
|
2690
2750
|
},
|
|
2691
|
-
|
|
2751
|
+
body: bomRequest.body,
|
|
2692
2752
|
responseType: "json",
|
|
2693
2753
|
context: {
|
|
2694
2754
|
activityIntent: "bom-submit",
|
|
@@ -2711,47 +2771,15 @@ export async function submitBom(args, bomContents) {
|
|
|
2711
2771
|
);
|
|
2712
2772
|
} else if (error.response && error.response.statusCode === 405) {
|
|
2713
2773
|
console.log(
|
|
2714
|
-
|
|
2774
|
+
`The server at ${serverUrl} does not accept a multipart/form-data POST on /api/v1/bom. Check that the URL points to a Dependency-Track 4.x or later API server.`,
|
|
2775
|
+
);
|
|
2776
|
+
} else if (DEBUG_MODE) {
|
|
2777
|
+
console.log(
|
|
2778
|
+
"Unable to submit the SBOM to the Dependency-Track server",
|
|
2779
|
+
error,
|
|
2715
2780
|
);
|
|
2716
|
-
// Method not allowed errors
|
|
2717
|
-
try {
|
|
2718
|
-
return await cdxgenAgent(dependencyTrackApiUrl, {
|
|
2719
|
-
method: "POST",
|
|
2720
|
-
followRedirect: false,
|
|
2721
|
-
https: {
|
|
2722
|
-
rejectUnauthorized: !args.skipDtTlsCheck,
|
|
2723
|
-
},
|
|
2724
|
-
headers: {
|
|
2725
|
-
"X-Api-Key": (args.apiKey || "").replace(/[\r\n]/g, ""),
|
|
2726
|
-
"Content-Type": "application/json",
|
|
2727
|
-
},
|
|
2728
|
-
json: bomPayload,
|
|
2729
|
-
responseType: "json",
|
|
2730
|
-
context: {
|
|
2731
|
-
activityIntent: "bom-submit",
|
|
2732
|
-
},
|
|
2733
|
-
}).json();
|
|
2734
|
-
} catch (error) {
|
|
2735
|
-
if (DEBUG_MODE) {
|
|
2736
|
-
console.log(
|
|
2737
|
-
"Unable to submit the SBOM to the Dependency-Track server using POST method",
|
|
2738
|
-
error,
|
|
2739
|
-
);
|
|
2740
|
-
} else {
|
|
2741
|
-
console.log(
|
|
2742
|
-
"Unable to submit the SBOM to the Dependency-Track server using POST method",
|
|
2743
|
-
);
|
|
2744
|
-
}
|
|
2745
|
-
}
|
|
2746
2781
|
} else {
|
|
2747
|
-
|
|
2748
|
-
console.log(
|
|
2749
|
-
"Unable to submit the SBOM to the Dependency-Track server using POST method",
|
|
2750
|
-
error,
|
|
2751
|
-
);
|
|
2752
|
-
} else {
|
|
2753
|
-
console.log("Unable to submit the SBOM to the Dependency-Track server");
|
|
2754
|
-
}
|
|
2782
|
+
console.log("Unable to submit the SBOM to the Dependency-Track server");
|
|
2755
2783
|
}
|
|
2756
2784
|
// rethrow error as function is async and we should try to catch it in the caller
|
|
2757
2785
|
throw error;
|
package/lib/cli/jsBom.js
CHANGED
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
readEnvironmentVariable,
|
|
12
12
|
recordSensitiveFileRead,
|
|
13
13
|
} from "../core/activity.js";
|
|
14
|
+
import { recordDegradation } from "../core/buildLedger.js";
|
|
15
|
+
import { deferFailOnError } from "../core/deferredExit.js";
|
|
14
16
|
import { hasAnyProjectType, isPackageManagerAllowed } from "../core/env.js";
|
|
15
17
|
import { getAllFiles, safeExistsSync, safeSpawnSync } from "../core/fs.js";
|
|
16
18
|
import { thoughtLog } from "../core/logger.js";
|
|
@@ -529,6 +531,14 @@ export async function createNodejsBom(path, options) {
|
|
|
529
531
|
isSecureMode &&
|
|
530
532
|
!installArgs.join(" ").includes("--allow-git")
|
|
531
533
|
) {
|
|
534
|
+
recordDegradation("js.git-dependency", {
|
|
535
|
+
ecosystem: "npm",
|
|
536
|
+
tool: pkgMgr,
|
|
537
|
+
impact: "integrity",
|
|
538
|
+
command: `${pkgMgr} ${installArgs.join(" ")}`,
|
|
539
|
+
detail:
|
|
540
|
+
"Secure mode npm install may still fetch and install git dependencies.",
|
|
541
|
+
});
|
|
532
542
|
console.log(
|
|
533
543
|
"Consider passing '--allow-git=none' via the environment variable NPM_INSTALL_ARGS to prevent any git dependencies from being fetched and installed via npm.",
|
|
534
544
|
);
|
|
@@ -580,6 +590,13 @@ export async function createNodejsBom(path, options) {
|
|
|
580
590
|
`**PACKAGE MANAGER**: Let's run the '${pkgMgr}' command with the arguments '${installArgs.join(" ")}' to generate the needed lock files.`,
|
|
581
591
|
);
|
|
582
592
|
}
|
|
593
|
+
recordDegradation("js.no-lockfile", {
|
|
594
|
+
ecosystem: "npm",
|
|
595
|
+
tool: pkgMgr,
|
|
596
|
+
impact: "versions",
|
|
597
|
+
detail:
|
|
598
|
+
"No lockfile was found for this project, so the SBOM is generated without pinned dependency versions.",
|
|
599
|
+
});
|
|
583
600
|
console.warn(
|
|
584
601
|
"\x1b[1;35mNotice: Generating an SBOM without a lockfile is risky and non-deterministic. Consider generating and committing the lockfile to your repository to ensure reproducible builds and SBOMs.\x1b[0m",
|
|
585
602
|
);
|
|
@@ -640,7 +657,11 @@ export async function createNodejsBom(path, options) {
|
|
|
640
657
|
console.log("---------------");
|
|
641
658
|
console.log(result.stderr);
|
|
642
659
|
}
|
|
643
|
-
options
|
|
660
|
+
deferFailOnError(options, {
|
|
661
|
+
ecosystem: "npm",
|
|
662
|
+
tool: "npm",
|
|
663
|
+
detail: "the npm command failed",
|
|
664
|
+
});
|
|
644
665
|
} else {
|
|
645
666
|
anyInstallSuccess = true;
|
|
646
667
|
}
|
|
@@ -936,7 +957,11 @@ export async function createNodejsBom(path, options) {
|
|
|
936
957
|
);
|
|
937
958
|
if (result.status === 1 || result.error) {
|
|
938
959
|
console.error(result.stdout, result.stderr);
|
|
939
|
-
options
|
|
960
|
+
deferFailOnError(options, {
|
|
961
|
+
ecosystem: "npm",
|
|
962
|
+
tool: "npm",
|
|
963
|
+
detail: "the yarn or pnpm command failed",
|
|
964
|
+
});
|
|
940
965
|
}
|
|
941
966
|
}
|
|
942
967
|
// Look for shrinkwrap file
|
|
@@ -1013,7 +1038,11 @@ export async function createNodejsBom(path, options) {
|
|
|
1013
1038
|
pnpmLock,
|
|
1014
1039
|
"was found!",
|
|
1015
1040
|
);
|
|
1016
|
-
options
|
|
1041
|
+
deferFailOnError(options, {
|
|
1042
|
+
ecosystem: "npm",
|
|
1043
|
+
tool: "npm",
|
|
1044
|
+
detail: "no JavaScript dependency evidence could be collected",
|
|
1045
|
+
});
|
|
1017
1046
|
}
|
|
1018
1047
|
if (
|
|
1019
1048
|
yarnLockFiles?.length &&
|