@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.
Files changed (155) hide show
  1. package/README.md +26 -13
  2. package/bin/audit.js +6 -0
  3. package/bin/cdxgen.js +125 -4
  4. package/bin/evinse.js +1 -1
  5. package/data/component-tags.json +8 -1
  6. package/data/frameworks-list.json +86 -1
  7. package/data/remediations.json +1144 -0
  8. package/data/rules/asar-archives.yaml +1 -1
  9. package/data/rules/build-fidelity.yaml +782 -0
  10. package/data/rules/package-integrity.yaml +13 -13
  11. package/lib/audit/index.js +48 -4
  12. package/lib/audit/scoring.js +2 -8
  13. package/lib/cli/bomAssembly.js +20 -1
  14. package/lib/cli/cliOptions.js +94 -7
  15. package/lib/cli/index.js +76 -48
  16. package/lib/cli/jsBom.js +32 -3
  17. package/lib/cli/jvmBom.js +1494 -1315
  18. package/lib/cli/managedBom.js +97 -28
  19. package/lib/cli/nativeBom.js +189 -10
  20. package/lib/core/buildLedger.js +799 -0
  21. package/lib/core/deferredExit.js +141 -0
  22. package/lib/core/env.js +69 -1
  23. package/lib/core/fs.js +60 -0
  24. package/lib/core/logger.js +12 -99
  25. package/lib/core/severity.js +16 -0
  26. package/lib/core/syncFileWriter.js +106 -0
  27. package/lib/ecosystems/ecosystems.js +9 -0
  28. package/lib/ecosystems/gradleutils.js +355 -15
  29. package/lib/ecosystems/parsers-js.js +26 -0
  30. package/lib/ecosystems/parsers-jvm.js +9 -0
  31. package/lib/ecosystems/parsers-misc.js +16 -2
  32. package/lib/ecosystems/parsers-python.js +171 -35
  33. package/lib/ecosystems/parsers-rust.js +12 -2
  34. package/lib/ecosystems/pypiClassifiers.js +326 -0
  35. package/lib/ecosystems/pythonutils.js +49 -1
  36. package/lib/ecosystems/rubyutils.js +13 -0
  37. package/lib/evinser/evinser.js +30 -12
  38. package/lib/helpers/remote/dependency-track.js +81 -14
  39. package/lib/inventory/auditCategories.js +9 -0
  40. package/lib/inventory/depsUtils.js +72 -0
  41. package/lib/inventory/dosai.js +431 -22
  42. package/lib/inventory/envcontext.js +206 -18
  43. package/lib/inventory/fetchBatch.js +57 -0
  44. package/lib/inventory/jvmToolEnv.js +593 -0
  45. package/lib/inventory/osinfo.js +1 -0
  46. package/lib/inventory/toolRequirements.js +903 -0
  47. package/lib/managers/binary.js +24 -0
  48. package/lib/managers/docker.js +26 -5
  49. package/lib/server/server.js +1 -1
  50. package/lib/stages/postgen/annotationBuilder.js +97 -0
  51. package/lib/stages/postgen/auditBom.js +76 -79
  52. package/lib/stages/postgen/introspection/annotate.js +320 -0
  53. package/lib/stages/postgen/introspection/commandFacts.js +293 -0
  54. package/lib/stages/postgen/introspection/emit.js +192 -0
  55. package/lib/stages/postgen/introspection/formulationEvidence.js +427 -0
  56. package/lib/stages/postgen/introspection/markers.js +227 -0
  57. package/lib/stages/postgen/introspection/reflect.js +1214 -0
  58. package/lib/stages/postgen/introspection/report.js +1208 -0
  59. package/lib/stages/postgen/introspection/score.js +1067 -0
  60. package/lib/stages/postgen/introspection/shapeCommand.js +304 -0
  61. package/lib/stages/postgen/parentEdge.js +156 -0
  62. package/lib/stages/postgen/postgen.js +115 -2
  63. package/lib/stages/postgen/ruleEngine.js +17 -0
  64. package/lib/stages/pregen/envAudit.js +42 -2
  65. package/lib/stages/pregen/pregen.js +1171 -6
  66. package/lib/validator/bomValidator.js +11 -2
  67. package/package.json +14 -14
  68. package/types/lib/audit/index.d.ts.map +1 -1
  69. package/types/lib/audit/scoring.d.ts +2 -8
  70. package/types/lib/audit/scoring.d.ts.map +1 -1
  71. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  72. package/types/lib/cli/cliOptions.d.ts +2 -1
  73. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  74. package/types/lib/cli/index.d.ts +8 -0
  75. package/types/lib/cli/index.d.ts.map +1 -1
  76. package/types/lib/cli/jsBom.d.ts.map +1 -1
  77. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  78. package/types/lib/cli/managedBom.d.ts.map +1 -1
  79. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  80. package/types/lib/core/buildLedger.d.ts +272 -0
  81. package/types/lib/core/buildLedger.d.ts.map +1 -0
  82. package/types/lib/core/deferredExit.d.ts +84 -0
  83. package/types/lib/core/deferredExit.d.ts.map +1 -0
  84. package/types/lib/core/env.d.ts +22 -1
  85. package/types/lib/core/env.d.ts.map +1 -1
  86. package/types/lib/core/fs.d.ts +21 -0
  87. package/types/lib/core/fs.d.ts.map +1 -1
  88. package/types/lib/core/logger.d.ts +2 -11
  89. package/types/lib/core/logger.d.ts.map +1 -1
  90. package/types/lib/core/severity.d.ts +11 -0
  91. package/types/lib/core/severity.d.ts.map +1 -0
  92. package/types/lib/core/syncFileWriter.d.ts +33 -0
  93. package/types/lib/core/syncFileWriter.d.ts.map +1 -0
  94. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  95. package/types/lib/ecosystems/gradleutils.d.ts +33 -0
  96. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  97. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  98. package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
  99. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  100. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  101. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
  102. package/types/lib/ecosystems/pypiClassifiers.d.ts +80 -0
  103. package/types/lib/ecosystems/pypiClassifiers.d.ts.map +1 -0
  104. package/types/lib/ecosystems/pythonutils.d.ts +20 -0
  105. package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
  106. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  107. package/types/lib/evinser/evinser.d.ts +5 -1
  108. package/types/lib/evinser/evinser.d.ts.map +1 -1
  109. package/types/lib/helpers/remote/dependency-track.d.ts +45 -4
  110. package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
  111. package/types/lib/inventory/auditCategories.d.ts +8 -0
  112. package/types/lib/inventory/auditCategories.d.ts.map +1 -1
  113. package/types/lib/inventory/depsUtils.d.ts.map +1 -1
  114. package/types/lib/inventory/dosai.d.ts +20 -0
  115. package/types/lib/inventory/dosai.d.ts.map +1 -1
  116. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  117. package/types/lib/inventory/fetchBatch.d.ts +9 -0
  118. package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
  119. package/types/lib/inventory/jvmToolEnv.d.ts +189 -0
  120. package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -0
  121. package/types/lib/inventory/osinfo.d.ts.map +1 -1
  122. package/types/lib/inventory/toolRequirements.d.ts +216 -0
  123. package/types/lib/inventory/toolRequirements.d.ts.map +1 -0
  124. package/types/lib/managers/binary.d.ts.map +1 -1
  125. package/types/lib/managers/docker.d.ts.map +1 -1
  126. package/types/lib/stages/postgen/annotationBuilder.d.ts +53 -0
  127. package/types/lib/stages/postgen/annotationBuilder.d.ts.map +1 -0
  128. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  129. package/types/lib/stages/postgen/introspection/annotate.d.ts +81 -0
  130. package/types/lib/stages/postgen/introspection/annotate.d.ts.map +1 -0
  131. package/types/lib/stages/postgen/introspection/commandFacts.d.ts +32 -0
  132. package/types/lib/stages/postgen/introspection/commandFacts.d.ts.map +1 -0
  133. package/types/lib/stages/postgen/introspection/emit.d.ts +71 -0
  134. package/types/lib/stages/postgen/introspection/emit.d.ts.map +1 -0
  135. package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts +154 -0
  136. package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts.map +1 -0
  137. package/types/lib/stages/postgen/introspection/markers.d.ts +57 -0
  138. package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -0
  139. package/types/lib/stages/postgen/introspection/reflect.d.ts +77 -0
  140. package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -0
  141. package/types/lib/stages/postgen/introspection/report.d.ts +85 -0
  142. package/types/lib/stages/postgen/introspection/report.d.ts.map +1 -0
  143. package/types/lib/stages/postgen/introspection/score.d.ts +149 -0
  144. package/types/lib/stages/postgen/introspection/score.d.ts.map +1 -0
  145. package/types/lib/stages/postgen/introspection/shapeCommand.d.ts +79 -0
  146. package/types/lib/stages/postgen/introspection/shapeCommand.d.ts.map +1 -0
  147. package/types/lib/stages/postgen/parentEdge.d.ts +35 -0
  148. package/types/lib/stages/postgen/parentEdge.d.ts.map +1 -0
  149. package/types/lib/stages/postgen/postgen.d.ts +2 -2
  150. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  151. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  152. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
  153. package/types/lib/stages/pregen/pregen.d.ts +131 -0
  154. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
  155. package/types/lib/validator/bomValidator.d.ts.map +1 -1
@@ -18,6 +18,7 @@ import {
18
18
  recordActivity,
19
19
  recordSymlinkResolution,
20
20
  } from "../core/activity.js";
21
+ import { recordDegradation } from "../core/buildLedger.js";
21
22
  import { extractPathEnv } from "../core/env.js";
22
23
  import {
23
24
  getTmpDir,
@@ -779,6 +780,16 @@ export async function getOSPackages(src, imageConfig, options = {}) {
779
780
  .filter(Boolean),
780
781
  ),
781
782
  ).sort();
783
+ if (binPaths.length) {
784
+ recordDegradation("binary.paths.inferred", {
785
+ ecosystem: "generic",
786
+ tool: "trivy",
787
+ impact: "none",
788
+ path: src,
789
+ detail:
790
+ "The image config carried no PATH, so executable directories were inferred by scanning the filesystem.",
791
+ });
792
+ }
782
793
  if (DEBUG_MODE && binPaths.length) {
783
794
  console.log(
784
795
  `Falling back to inferred binary paths for ${src}: ${binPaths.join(", ")}`,
@@ -887,9 +898,14 @@ export async function getOSPackages(src, imageConfig, options = {}) {
887
898
  }
888
899
  }
889
900
  break;
901
+ case "alpaquita":
902
+ purl_type = "apk";
903
+ break;
890
904
  default:
891
905
  if (distro_id_like.includes("debian")) {
892
906
  purl_type = "deb";
907
+ } else if (distro_id_like.includes("alpine")) {
908
+ purl_type = "apk";
893
909
  } else if (
894
910
  distro_id_like.includes("rhel") ||
895
911
  distro_id_like.includes("centos") ||
@@ -3296,6 +3312,14 @@ export function getDotnetSlices(src, slicesFile) {
3296
3312
  "You must install or update .NET to run this application",
3297
3313
  )
3298
3314
  ) {
3315
+ recordDegradation("dotnet.sdk.missing", {
3316
+ ecosystem: "csharp",
3317
+ tool: "dotnet",
3318
+ impact: "components",
3319
+ command: `dosai ${args.join(" ")}`,
3320
+ detail:
3321
+ "dosai reported that no .NET SDK is installed, so dotnet-related evidence for this image is incomplete.",
3322
+ });
3299
3323
  console.log(
3300
3324
  "Dotnet SDK is not installed. Please use the cdxgen dotnet container images to generate slices for this project.",
3301
3325
  );
@@ -21,6 +21,7 @@ import {
21
21
  recordDecisionActivity,
22
22
  recordSensitiveFileRead,
23
23
  } from "../core/activity.js";
24
+ import { deferFailOnError } from "../core/deferredExit.js";
24
25
  import { extractPathEnv } from "../core/env.js";
25
26
  import {
26
27
  getAllFiles,
@@ -1407,7 +1408,11 @@ export const extractTar = async (fullImageName, dir, options) => {
1407
1408
  } else if (DEBUG_MODE) {
1408
1409
  console.log(err.code, "is not handled yet in extractTar method.");
1409
1410
  }
1410
- options.failOnError && process.exit(1);
1411
+ deferFailOnError(options, {
1412
+ ecosystem: "container",
1413
+ tool: "docker",
1414
+ detail: "extracting the image archive failed",
1415
+ });
1411
1416
  return false;
1412
1417
  }
1413
1418
  };
@@ -1598,10 +1603,18 @@ export const exportArchive = async (fullImageName, options = {}) => {
1598
1603
  return exportData;
1599
1604
  }
1600
1605
  console.log(`Unable to extract image archive to ${tempDir}`);
1601
- options.failOnError && process.exit(1);
1606
+ deferFailOnError(options, {
1607
+ ecosystem: "container",
1608
+ tool: "docker",
1609
+ detail: "the image archive could not be extracted",
1610
+ });
1602
1611
  } catch (_err) {
1603
1612
  // ignore
1604
- options.failOnError && process.exit(1);
1613
+ deferFailOnError(options, {
1614
+ ecosystem: "container",
1615
+ tool: "docker",
1616
+ detail: "the image archive could not be extracted",
1617
+ });
1605
1618
  }
1606
1619
  return undefined;
1607
1620
  };
@@ -1686,7 +1699,11 @@ export const extractFromManifest = async (
1686
1699
  }
1687
1700
  } else {
1688
1701
  console.log(err);
1689
- options.failOnError && process.exit(1);
1702
+ deferFailOnError(options, {
1703
+ ecosystem: "container",
1704
+ tool: "docker",
1705
+ detail: "reading the image manifest failed",
1706
+ });
1690
1707
  }
1691
1708
  }
1692
1709
  }
@@ -1713,7 +1730,11 @@ export const extractFromManifest = async (
1713
1730
  ? join(allLayersExplodedDir, lastLayerConfig.config.WorkingDir)
1714
1731
  : "";
1715
1732
  } catch (_err) {
1716
- options.failOnError && process.exit(1);
1733
+ deferFailOnError(options, {
1734
+ ecosystem: "container",
1735
+ tool: "docker",
1736
+ detail: "reading the image manifest failed",
1737
+ });
1717
1738
  }
1718
1739
  }
1719
1740
  }
@@ -520,7 +520,7 @@ const start = (options) => {
520
520
  ensureAiProvenanceProperties(bomNSData.bomJson, reqOptions);
521
521
  await ensureAiOversightProperties(bomNSData.bomJson, reqOptions);
522
522
  }
523
- bomNSData = postProcess(
523
+ bomNSData = await postProcess(
524
524
  bomNSData,
525
525
  { ...reqOptions, executeOsQuery },
526
526
  srcDir,
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Shared assembly of CycloneDX document-level annotations.
3
+ *
4
+ * Two postgen features attach findings to the BOM document as annotations
5
+ * whose `subjects` is the BOM serial number: the BOM audit's rule findings and
6
+ * the build introspection's remediations. Both describe the document rather
7
+ * than a component, both are annotated by the cdxgen tool component from
8
+ * metadata, and both carry the document's own timestamp because an annotation
9
+ * describes the document it is attached to. The structured facts travel as
10
+ * `name`/`value` pairs rendered into the annotation text, one `:evidence:<key>`
11
+ * entry per evidence item.
12
+ */
13
+
14
+ import { DEBUG_MODE } from "../../core/activity.js";
15
+ import { getTimestamp } from "../../core/fs.js";
16
+ import { buildAnnotationText } from "../../helpers/annotationFormatter.js";
17
+
18
+ /**
19
+ * Find the cdxgen tool component that annotates the document.
20
+ *
21
+ * @param {Object} bomJson CycloneDX BOM
22
+ * @returns {Object[]} The cdxgen tool components, empty when metadata carries none.
23
+ */
24
+ function findCdxgenAnnotator(bomJson) {
25
+ return (
26
+ bomJson?.metadata?.tools?.components?.filter((c) => c.name === "cdxgen") ||
27
+ []
28
+ );
29
+ }
30
+
31
+ /**
32
+ * Flatten an evidence object into `:evidence:<key>` property entries. Nested
33
+ * values are serialized as JSON so every property value stays a string.
34
+ *
35
+ * @param {string} propertyNamePrefix Property namespace, e.g. "cdx:audit".
36
+ * @param {Object} evidence Arbitrary key/value evidence facts.
37
+ * @returns {{name: string, value: string}[]} Property entries.
38
+ */
39
+ export function evidenceProperties(propertyNamePrefix, evidence) {
40
+ const properties = [];
41
+ if (evidence && typeof evidence === "object") {
42
+ for (const [key, value] of Object.entries(evidence)) {
43
+ const propValue =
44
+ typeof value === "object" ? JSON.stringify(value) : String(value);
45
+ properties.push({
46
+ name: `${propertyNamePrefix}:evidence:${key}`,
47
+ value: propValue,
48
+ });
49
+ }
50
+ }
51
+ return properties;
52
+ }
53
+
54
+ /**
55
+ * Build one document-level annotation. Returns undefined when the BOM carries
56
+ * no cdxgen tool component to annotate with; the caller decides whether that
57
+ * is worth a warning.
58
+ *
59
+ * @param {Object} params Annotation inputs.
60
+ * @param {Object} params.bomJson CycloneDX BOM the annotation describes.
61
+ * @param {string} params.message Leading annotation sentence.
62
+ * @param {{name: string, value: string}[]} params.properties Structured facts rendered into the annotation text.
63
+ * @returns {Object|undefined} CycloneDX annotation, or undefined without an annotator.
64
+ */
65
+ export function buildDocumentAnnotation({ bomJson, message, properties }) {
66
+ const cdxgenAnnotator = findCdxgenAnnotator(bomJson);
67
+ if (!cdxgenAnnotator.length) {
68
+ return undefined;
69
+ }
70
+ return {
71
+ subjects: [bomJson.serialNumber],
72
+ annotator: {
73
+ component: cdxgenAnnotator[0],
74
+ },
75
+ // An annotation describes the document it is attached to, so it carries
76
+ // that document's timestamp rather than the moment the annotation was
77
+ // built.
78
+ timestamp: bomJson.metadata?.timestamp || getTimestamp(),
79
+ text: buildAnnotationText(message, properties),
80
+ };
81
+ }
82
+
83
+ /**
84
+ * Warn that a document could not be annotated because it carries no cdxgen
85
+ * tool component. Suppressed outside debug mode, where the missing annotator
86
+ * is an expected shape for foreign BOMs.
87
+ *
88
+ * @param {string} feature Feature that wanted to annotate.
89
+ * @returns {void}
90
+ */
91
+ export function warnMissingAnnotator(feature) {
92
+ if (DEBUG_MODE) {
93
+ console.warn(
94
+ `Cannot create ${feature} annotations: cdxgen tool component not found in metadata`,
95
+ );
96
+ }
97
+ }
@@ -6,16 +6,21 @@ import { join, resolve } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
 
8
8
  import { DEBUG_MODE } from "../../core/activity.js";
9
- import { getTimestamp, safeExistsSync } from "../../core/fs.js";
10
- import { buildAnnotationText } from "../../helpers/annotationFormatter.js";
9
+ import { safeExistsSync } from "../../core/fs.js";
11
10
  import { collectAiOversight } from "../../inventory/aiOversightCollector.js";
12
11
  import { collectAiProvenance } from "../../inventory/aiProvenanceCollector.js";
13
12
  import {
14
13
  expandBomAuditCategories,
14
+ OPT_IN_BOM_AUDIT_CATEGORIES,
15
15
  validateBomAuditCategories,
16
16
  } from "../../inventory/auditCategories.js";
17
17
  import { isHbomLikeBom as isHbomLikeBomDocument } from "../../inventory/hbomAnalysis.js";
18
18
  import { table } from "../../inventory/table.js";
19
+ import {
20
+ buildDocumentAnnotation,
21
+ evidenceProperties,
22
+ warnMissingAnnotator,
23
+ } from "./annotationBuilder.js";
19
24
  import { evaluateRules, loadRules } from "./ruleEngine.js";
20
25
 
21
26
  const __dirname = fileURLToPath(new URL(".", import.meta.url));
@@ -53,6 +58,12 @@ async function loadConfiguredBomAuditRules(options = {}) {
53
58
  );
54
59
  }
55
60
  }
61
+ } else {
62
+ // Without explicit categories every loaded rule would activate, so rules
63
+ // belonging to opt-in-only categories are removed here.
64
+ activeRules = rules.filter(
65
+ (r) => !OPT_IN_BOM_AUDIT_CATEGORIES.includes(r.category),
66
+ );
56
67
  }
57
68
  return {
58
69
  activeRules,
@@ -369,89 +380,75 @@ export function formatAnnotations(findings, bomJson) {
369
380
  if (!findings?.length) {
370
381
  return [];
371
382
  }
372
- const cdxgenAnnotator =
373
- bomJson?.metadata?.tools?.components?.filter((c) => c.name === "cdxgen") ||
374
- [];
375
- if (!cdxgenAnnotator.length) {
376
- if (DEBUG_MODE) {
377
- console.warn(
378
- "Cannot create audit annotations: cdxgen tool component not found in metadata",
379
- );
380
- }
381
- return [];
382
- }
383
- return findings.map((f) => {
384
- const subjects = [bomJson.serialNumber];
385
- const properties = [
386
- { name: "cdx:audit:ruleId", value: f.ruleId },
387
- { name: "cdx:audit:severity", value: f.severity },
388
- { name: "cdx:audit:category", value: f.category },
389
- ];
390
- if (f.name) {
391
- properties.push({ name: "cdx:audit:name", value: f.name });
392
- }
393
- if (f.mitigation) {
394
- properties.push({ name: "cdx:audit:mitigation", value: f.mitigation });
395
- }
396
- if (f.attackTactics?.length) {
397
- properties.push({
398
- name: "cdx:audit:attack:tactics",
399
- value: f.attackTactics.join(","),
400
- });
401
- }
402
- if (f.attackTechniques?.length) {
403
- properties.push({
404
- name: "cdx:audit:attack:techniques",
405
- value: f.attackTechniques.join(","),
406
- });
407
- }
408
- if (f.standards && typeof f.standards === "object") {
409
- for (const [standardName, entries] of Object.entries(f.standards)) {
383
+ let warnedMissingAnnotator = false;
384
+ return findings
385
+ .map((f) => {
386
+ const properties = [
387
+ { name: "cdx:audit:ruleId", value: f.ruleId },
388
+ { name: "cdx:audit:severity", value: f.severity },
389
+ { name: "cdx:audit:category", value: f.category },
390
+ ];
391
+ if (f.name) {
392
+ properties.push({ name: "cdx:audit:name", value: f.name });
393
+ }
394
+ if (f.mitigation) {
395
+ properties.push({ name: "cdx:audit:mitigation", value: f.mitigation });
396
+ }
397
+ if (f.attackTactics?.length) {
410
398
  properties.push({
411
- name: `cdx:audit:standards:${standardName}`,
412
- value: Array.isArray(entries) ? entries.join(",") : String(entries),
399
+ name: "cdx:audit:attack:tactics",
400
+ value: f.attackTactics.join(","),
413
401
  });
414
402
  }
415
- }
416
- if (f?.location?.purl) {
417
- properties.push({
418
- name: "cdx:audit:location:purl",
419
- value: f.location.purl,
420
- });
421
- }
422
- if (f.location?.file) {
423
- properties.push({
424
- name: "cdx:audit:location:file",
425
- value: f.location.file,
426
- });
427
- }
428
- if (f.location?.bomRef) {
429
- properties.push({
430
- name: "cdx:audit:location:bomRef",
431
- value: f.location.bomRef,
432
- });
433
- }
434
- if (f.evidence && typeof f.evidence === "object") {
435
- for (const [key, value] of Object.entries(f.evidence)) {
436
- const propValue =
437
- typeof value === "object" ? JSON.stringify(value) : String(value);
403
+ if (f.attackTechniques?.length) {
438
404
  properties.push({
439
- name: `cdx:audit:evidence:${key}`,
440
- value: propValue,
405
+ name: "cdx:audit:attack:techniques",
406
+ value: f.attackTechniques.join(","),
441
407
  });
442
408
  }
443
- }
444
- return {
445
- subjects,
446
- annotator: {
447
- component: cdxgenAnnotator[0],
448
- },
449
- // An annotation describes the document it is attached to, so it carries
450
- // that document's timestamp rather than the moment the audit ran.
451
- timestamp: bomJson.metadata?.timestamp || getTimestamp(),
452
- text: buildAnnotationText(f.message, properties),
453
- };
454
- });
409
+ if (f.standards && typeof f.standards === "object") {
410
+ for (const [standardName, entries] of Object.entries(f.standards)) {
411
+ properties.push({
412
+ name: `cdx:audit:standards:${standardName}`,
413
+ value: Array.isArray(entries) ? entries.join(",") : String(entries),
414
+ });
415
+ }
416
+ }
417
+ if (f?.location?.purl) {
418
+ properties.push({
419
+ name: "cdx:audit:location:purl",
420
+ value: f.location.purl,
421
+ });
422
+ }
423
+ if (f.location?.file) {
424
+ properties.push({
425
+ name: "cdx:audit:location:file",
426
+ value: f.location.file,
427
+ });
428
+ }
429
+ if (f.location?.bomRef) {
430
+ properties.push({
431
+ name: "cdx:audit:location:bomRef",
432
+ value: f.location.bomRef,
433
+ });
434
+ }
435
+ properties.push(...evidenceProperties("cdx:audit", f.evidence));
436
+ return buildDocumentAnnotation({
437
+ bomJson,
438
+ message: f.message,
439
+ properties,
440
+ });
441
+ })
442
+ .filter((annotation) => {
443
+ if (!annotation) {
444
+ if (!warnedMissingAnnotator) {
445
+ warnMissingAnnotator("audit");
446
+ warnedMissingAnnotator = true;
447
+ }
448
+ return false;
449
+ }
450
+ return true;
451
+ });
455
452
  }
456
453
 
457
454
  /**