@cyclonedx/cdxgen 12.2.0 → 12.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/README.md +242 -90
  2. package/bin/audit.js +191 -0
  3. package/bin/cdxgen.js +532 -168
  4. package/bin/convert.js +99 -0
  5. package/bin/evinse.js +23 -0
  6. package/bin/repl.js +339 -8
  7. package/bin/sign.js +8 -0
  8. package/bin/validate.js +8 -0
  9. package/bin/verify.js +8 -0
  10. package/data/container-knowledge-index.json +125 -0
  11. package/data/gtfobins-index.json +6296 -0
  12. package/data/lolbas-index.json +150 -0
  13. package/data/queries-darwin.json +63 -3
  14. package/data/queries-win.json +45 -3
  15. package/data/queries.json +74 -2
  16. package/data/rules/chrome-extensions.yaml +240 -0
  17. package/data/rules/ci-permissions.yaml +478 -18
  18. package/data/rules/container-risk.yaml +270 -0
  19. package/data/rules/obom-runtime.yaml +891 -0
  20. package/data/rules/package-integrity.yaml +49 -0
  21. package/data/spdx-export.schema.json +6794 -0
  22. package/data/spdx-model-v3.0.1.jsonld +15999 -0
  23. package/lib/audit/index.js +1924 -0
  24. package/lib/audit/index.poku.js +1488 -0
  25. package/lib/audit/progress.js +137 -0
  26. package/lib/audit/progress.poku.js +188 -0
  27. package/lib/audit/reporters.js +618 -0
  28. package/lib/audit/scoring.js +310 -0
  29. package/lib/audit/scoring.poku.js +341 -0
  30. package/lib/audit/targets.js +260 -0
  31. package/lib/audit/targets.poku.js +331 -0
  32. package/lib/cli/index.js +276 -68
  33. package/lib/cli/index.poku.js +368 -0
  34. package/lib/helpers/analyzer.js +1052 -5
  35. package/lib/helpers/analyzer.poku.js +301 -0
  36. package/lib/helpers/annotationFormatter.js +49 -0
  37. package/lib/helpers/annotationFormatter.poku.js +44 -0
  38. package/lib/helpers/bomUtils.js +36 -0
  39. package/lib/helpers/bomUtils.poku.js +51 -0
  40. package/lib/helpers/caxa.js +2 -2
  41. package/lib/helpers/chromextutils.js +1153 -0
  42. package/lib/helpers/chromextutils.poku.js +493 -0
  43. package/lib/helpers/ciParsers/githubActions.js +1632 -45
  44. package/lib/helpers/ciParsers/githubActions.poku.js +853 -1
  45. package/lib/helpers/containerRisk.js +186 -0
  46. package/lib/helpers/containerRisk.poku.js +52 -0
  47. package/lib/helpers/depsUtils.js +16 -0
  48. package/lib/helpers/depsUtils.poku.js +58 -1
  49. package/lib/helpers/display.js +245 -61
  50. package/lib/helpers/display.poku.js +162 -2
  51. package/lib/helpers/exportUtils.js +123 -0
  52. package/lib/helpers/exportUtils.poku.js +60 -0
  53. package/lib/helpers/formulationParsers.js +69 -0
  54. package/lib/helpers/formulationParsers.poku.js +44 -0
  55. package/lib/helpers/gtfobins.js +189 -0
  56. package/lib/helpers/gtfobins.poku.js +49 -0
  57. package/lib/helpers/lolbas.js +267 -0
  58. package/lib/helpers/lolbas.poku.js +39 -0
  59. package/lib/helpers/osqueryTransform.js +84 -0
  60. package/lib/helpers/osqueryTransform.poku.js +49 -0
  61. package/lib/helpers/provenanceUtils.js +193 -0
  62. package/lib/helpers/provenanceUtils.poku.js +145 -0
  63. package/lib/helpers/pylockutils.js +281 -0
  64. package/lib/helpers/pylockutils.poku.js +48 -0
  65. package/lib/helpers/registryProvenance.js +793 -0
  66. package/lib/helpers/registryProvenance.poku.js +452 -0
  67. package/lib/helpers/remote/dependency-track.js +84 -0
  68. package/lib/helpers/remote/dependency-track.poku.js +119 -0
  69. package/lib/helpers/source.js +1267 -0
  70. package/lib/helpers/source.poku.js +771 -0
  71. package/lib/helpers/spdxUtils.js +97 -0
  72. package/lib/helpers/spdxUtils.poku.js +70 -0
  73. package/lib/helpers/table.js +384 -0
  74. package/lib/helpers/table.poku.js +186 -0
  75. package/lib/helpers/unicodeScan.js +147 -0
  76. package/lib/helpers/unicodeScan.poku.js +45 -0
  77. package/lib/helpers/utils.js +882 -136
  78. package/lib/helpers/utils.poku.js +995 -91
  79. package/lib/managers/binary.js +29 -5
  80. package/lib/managers/docker.js +179 -52
  81. package/lib/managers/docker.poku.js +327 -28
  82. package/lib/managers/oci.js +107 -23
  83. package/lib/managers/oci.poku.js +132 -0
  84. package/lib/server/openapi.yaml +50 -0
  85. package/lib/server/server.js +228 -331
  86. package/lib/server/server.poku.js +220 -5
  87. package/lib/stages/postgen/annotator.js +7 -0
  88. package/lib/stages/postgen/annotator.poku.js +40 -0
  89. package/lib/stages/postgen/auditBom.js +20 -5
  90. package/lib/stages/postgen/auditBom.poku.js +1729 -67
  91. package/lib/stages/postgen/postgen.js +40 -0
  92. package/lib/stages/postgen/postgen.poku.js +47 -0
  93. package/lib/stages/postgen/ruleEngine.js +80 -2
  94. package/lib/stages/postgen/spdxConverter.js +796 -0
  95. package/lib/stages/postgen/spdxConverter.poku.js +341 -0
  96. package/lib/validator/bomValidator.js +232 -0
  97. package/lib/validator/bomValidator.poku.js +70 -0
  98. package/lib/validator/complianceRules.js +70 -7
  99. package/lib/validator/complianceRules.poku.js +30 -0
  100. package/lib/validator/reporters/annotations.js +2 -2
  101. package/lib/validator/reporters/console.js +13 -2
  102. package/lib/validator/reporters.poku.js +13 -0
  103. package/package.json +10 -8
  104. package/types/bin/audit.d.ts +3 -0
  105. package/types/bin/audit.d.ts.map +1 -0
  106. package/types/bin/convert.d.ts +3 -0
  107. package/types/bin/convert.d.ts.map +1 -0
  108. package/types/bin/repl.d.ts.map +1 -1
  109. package/types/lib/audit/index.d.ts +115 -0
  110. package/types/lib/audit/index.d.ts.map +1 -0
  111. package/types/lib/audit/progress.d.ts +27 -0
  112. package/types/lib/audit/progress.d.ts.map +1 -0
  113. package/types/lib/audit/reporters.d.ts +35 -0
  114. package/types/lib/audit/reporters.d.ts.map +1 -0
  115. package/types/lib/audit/scoring.d.ts +35 -0
  116. package/types/lib/audit/scoring.d.ts.map +1 -0
  117. package/types/lib/audit/targets.d.ts +63 -0
  118. package/types/lib/audit/targets.d.ts.map +1 -0
  119. package/types/lib/cli/index.d.ts +8 -0
  120. package/types/lib/cli/index.d.ts.map +1 -1
  121. package/types/lib/helpers/analyzer.d.ts +13 -0
  122. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  123. package/types/lib/helpers/annotationFormatter.d.ts +23 -0
  124. package/types/lib/helpers/annotationFormatter.d.ts.map +1 -0
  125. package/types/lib/helpers/bomUtils.d.ts +5 -0
  126. package/types/lib/helpers/bomUtils.d.ts.map +1 -0
  127. package/types/lib/helpers/chromextutils.d.ts +97 -0
  128. package/types/lib/helpers/chromextutils.d.ts.map +1 -0
  129. package/types/lib/helpers/ciParsers/githubActions.d.ts +3 -8
  130. package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
  131. package/types/lib/helpers/containerRisk.d.ts +17 -0
  132. package/types/lib/helpers/containerRisk.d.ts.map +1 -0
  133. package/types/lib/helpers/depsUtils.d.ts.map +1 -1
  134. package/types/lib/helpers/display.d.ts +4 -1
  135. package/types/lib/helpers/display.d.ts.map +1 -1
  136. package/types/lib/helpers/exportUtils.d.ts +40 -0
  137. package/types/lib/helpers/exportUtils.d.ts.map +1 -0
  138. package/types/lib/helpers/formulationParsers.d.ts.map +1 -1
  139. package/types/lib/helpers/gtfobins.d.ts +17 -0
  140. package/types/lib/helpers/gtfobins.d.ts.map +1 -0
  141. package/types/lib/helpers/lolbas.d.ts +16 -0
  142. package/types/lib/helpers/lolbas.d.ts.map +1 -0
  143. package/types/lib/helpers/osqueryTransform.d.ts +7 -0
  144. package/types/lib/helpers/osqueryTransform.d.ts.map +1 -0
  145. package/types/lib/helpers/provenanceUtils.d.ts +90 -0
  146. package/types/lib/helpers/provenanceUtils.d.ts.map +1 -0
  147. package/types/lib/helpers/pylockutils.d.ts +51 -0
  148. package/types/lib/helpers/pylockutils.d.ts.map +1 -0
  149. package/types/lib/helpers/registryProvenance.d.ts +17 -0
  150. package/types/lib/helpers/registryProvenance.d.ts.map +1 -0
  151. package/types/lib/helpers/remote/dependency-track.d.ts +16 -0
  152. package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -0
  153. package/types/lib/helpers/source.d.ts +141 -0
  154. package/types/lib/helpers/source.d.ts.map +1 -0
  155. package/types/lib/helpers/spdxUtils.d.ts +2 -0
  156. package/types/lib/helpers/spdxUtils.d.ts.map +1 -0
  157. package/types/lib/helpers/table.d.ts +6 -0
  158. package/types/lib/helpers/table.d.ts.map +1 -0
  159. package/types/lib/helpers/unicodeScan.d.ts +46 -0
  160. package/types/lib/helpers/unicodeScan.d.ts.map +1 -0
  161. package/types/lib/helpers/utils.d.ts +30 -11
  162. package/types/lib/helpers/utils.d.ts.map +1 -1
  163. package/types/lib/managers/binary.d.ts.map +1 -1
  164. package/types/lib/managers/docker.d.ts.map +1 -1
  165. package/types/lib/managers/oci.d.ts.map +1 -1
  166. package/types/lib/server/server.d.ts +0 -35
  167. package/types/lib/server/server.d.ts.map +1 -1
  168. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  169. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  170. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  171. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  172. package/types/lib/stages/postgen/spdxConverter.d.ts +11 -0
  173. package/types/lib/stages/postgen/spdxConverter.d.ts.map +1 -0
  174. package/types/lib/validator/bomValidator.d.ts +1 -0
  175. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  176. package/types/lib/validator/complianceRules.d.ts.map +1 -1
  177. package/types/lib/validator/reporters/console.d.ts.map +1 -1
  178. package/types/bin/dependencies.d.ts +0 -3
  179. package/types/bin/dependencies.d.ts.map +0 -1
  180. package/types/bin/licenses.d.ts +0 -3
  181. package/types/bin/licenses.d.ts.map +0 -1
@@ -1,15 +1,18 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+
4
+ import esmock from "esmock";
1
5
  import { afterEach, assert, beforeEach, describe, it } from "poku";
6
+ import sinon from "sinon";
2
7
 
3
- import { isWin } from "../helpers/utils.js";
4
8
  import {
5
- getQueryParams,
6
9
  isAllowedHost,
7
10
  isAllowedPath,
8
11
  isAllowedWinPath,
9
- parseQueryString,
10
- parseValue,
11
12
  validateAndRejectGitSource,
12
- } from "./server.js";
13
+ } from "../helpers/source.js";
14
+ import { isWin } from "../helpers/utils.js";
15
+ import { getQueryParams, parseQueryString, parseValue } from "./server.js";
13
16
 
14
17
  function nullProtoObj(obj) {
15
18
  if (obj === null || typeof obj !== "object") {
@@ -105,6 +108,34 @@ describe("parseQueryString tests", () => {
105
108
  const result = parseQueryString({}, {}, options);
106
109
  checkEqual(result.installDeps, false);
107
110
  });
111
+
112
+ it("parses parentProjectName and parentProjectVersion", () => {
113
+ const q = {
114
+ parentProjectName: "parent-app",
115
+ parentProjectVersion: "1.2.3",
116
+ };
117
+ const result = parseQueryString(q, {}, {});
118
+ checkEqual(result.parentProjectName, "parent-app");
119
+ checkEqual(result.parentProjectVersion, "1.2.3");
120
+ });
121
+
122
+ it("parses autoCreate and isLatest boolean options", () => {
123
+ const q = {
124
+ autoCreate: "false",
125
+ isLatest: "true",
126
+ };
127
+ const result = parseQueryString(q, {}, {});
128
+ checkEqual(result.autoCreate, false);
129
+ checkEqual(result.isLatest, true);
130
+ });
131
+
132
+ it("parses format for SPDX export requests", () => {
133
+ const q = {
134
+ format: "spdx",
135
+ };
136
+ const result = parseQueryString(q, {}, {});
137
+ checkEqual(result.format, "spdx");
138
+ });
108
139
  });
109
140
 
110
141
  describe("isAllowedHost()", () => {
@@ -586,3 +617,187 @@ it("should correctly normalize and validate various git@ (SCP-like) formats", ()
586
617
  checkEqual(deniedRes.error, "Host Not Allowed");
587
618
  delete process.env.CDXGEN_SERVER_ALLOWED_HOSTS;
588
619
  });
620
+
621
+ describe("gitClone() hardening tests", () => {
622
+ it("passes core.hooksPath=/dev/null and --template= flags to git", async () => {
623
+ const spawnStub = sinon.stub().returns({ status: 0, stderr: "" });
624
+ const mkdtempStub = sinon
625
+ .stub()
626
+ .returns(path.join(os.tmpdir(), "fake-repo"));
627
+
628
+ const { gitClone } = await esmock("../helpers/source.js", {
629
+ "../helpers/utils.js": {
630
+ safeSpawnSync: spawnStub,
631
+ isSecureMode: false,
632
+ hasDangerousUnicode: sinon.stub().returns(false),
633
+ getTmpDir: sinon.stub().returns(os.tmpdir()),
634
+ },
635
+ "node:fs": {
636
+ mkdtempSync: mkdtempStub,
637
+ existsSync: sinon.stub().returns(false),
638
+ readdirSync: sinon.stub().returns([]),
639
+ statSync: sinon.stub().returns({ isDirectory: () => true }),
640
+ readFileSync: sinon.stub().returns(""),
641
+ },
642
+ });
643
+
644
+ gitClone("https://example.com/repo.git");
645
+
646
+ sinon.assert.calledOnce(spawnStub);
647
+ const [cmd, args] = spawnStub.firstCall.args;
648
+ assert.strictEqual(cmd, "git");
649
+
650
+ // core.hooksPath=/dev/null must be present as a -c flag
651
+ const hooksPathIdx = args.indexOf("core.hooksPath=/dev/null");
652
+ assert.ok(
653
+ hooksPathIdx > 0 && args[hooksPathIdx - 1] === "-c",
654
+ "expected -c core.hooksPath=/dev/null in git args",
655
+ );
656
+
657
+ // --template= must appear after "clone"
658
+ const cloneIdx = args.indexOf("clone");
659
+ assert.ok(cloneIdx >= 0, "expected 'clone' subcommand in git args");
660
+ assert.ok(
661
+ args.slice(cloneIdx).includes("--template="),
662
+ "expected --template= after clone in git args",
663
+ );
664
+ });
665
+
666
+ it("uses GIT_CONFIG_GLOBAL=/dev/null instead of invalid GIT_CONFIG_NOGLOBAL in secure mode", async () => {
667
+ const spawnStub = sinon.stub().returns({ status: 0, stderr: "" });
668
+ const mkdtempStub = sinon
669
+ .stub()
670
+ .returns(path.join(os.tmpdir(), "fake-repo"));
671
+
672
+ const { gitClone } = await esmock("../helpers/source.js", {
673
+ "../helpers/utils.js": {
674
+ safeSpawnSync: spawnStub,
675
+ isSecureMode: true,
676
+ hasDangerousUnicode: sinon.stub().returns(false),
677
+ getTmpDir: sinon.stub().returns(os.tmpdir()),
678
+ },
679
+ "node:fs": {
680
+ mkdtempSync: mkdtempStub,
681
+ existsSync: sinon.stub().returns(false),
682
+ readdirSync: sinon.stub().returns([]),
683
+ statSync: sinon.stub().returns({ isDirectory: () => true }),
684
+ readFileSync: sinon.stub().returns(""),
685
+ },
686
+ });
687
+
688
+ gitClone("https://example.com/repo.git");
689
+
690
+ sinon.assert.calledOnce(spawnStub);
691
+ const opts = spawnStub.firstCall.args[2];
692
+ assert.ok(opts.env, "expected env to be set");
693
+ assert.ok(
694
+ !("GIT_CONFIG_NOGLOBAL" in opts.env),
695
+ "GIT_CONFIG_NOGLOBAL must not be set (it is not a valid Git env var)",
696
+ );
697
+ assert.strictEqual(
698
+ opts.env.GIT_CONFIG_GLOBAL,
699
+ "/dev/null",
700
+ "GIT_CONFIG_GLOBAL must be /dev/null in secure mode",
701
+ );
702
+ assert.strictEqual(
703
+ opts.env.GIT_CONFIG_NOSYSTEM,
704
+ "1",
705
+ "GIT_CONFIG_NOSYSTEM must be '1' in secure mode",
706
+ );
707
+ });
708
+
709
+ it("sets GIT_TERMINAL_PROMPT=0 in both secure and non-secure mode", async () => {
710
+ for (const secureMode of [false, true]) {
711
+ const spawnStub = sinon.stub().returns({ status: 0, stderr: "" });
712
+ const mkdtempStub = sinon
713
+ .stub()
714
+ .returns(path.join(os.tmpdir(), "fake-repo"));
715
+
716
+ const { gitClone } = await esmock("../helpers/source.js", {
717
+ "../helpers/utils.js": {
718
+ safeSpawnSync: spawnStub,
719
+ isSecureMode: secureMode,
720
+ hasDangerousUnicode: sinon.stub().returns(false),
721
+ getTmpDir: sinon.stub().returns(os.tmpdir()),
722
+ },
723
+ "node:fs": {
724
+ mkdtempSync: mkdtempStub,
725
+ existsSync: sinon.stub().returns(false),
726
+ readdirSync: sinon.stub().returns([]),
727
+ statSync: sinon.stub().returns({ isDirectory: () => true }),
728
+ readFileSync: sinon.stub().returns(""),
729
+ },
730
+ });
731
+
732
+ gitClone("https://example.com/repo.git");
733
+
734
+ const opts = spawnStub.firstCall.args[2];
735
+ assert.strictEqual(
736
+ opts.env.GIT_TERMINAL_PROMPT,
737
+ "0",
738
+ `GIT_TERMINAL_PROMPT must be '0' when isSecureMode=${secureMode}`,
739
+ );
740
+ }
741
+ });
742
+
743
+ it("inserts --branch before repoUrl when a valid branch is specified", async () => {
744
+ const spawnStub = sinon.stub().returns({ status: 0, stderr: "" });
745
+ const mkdtempStub = sinon
746
+ .stub()
747
+ .returns(path.join(os.tmpdir(), "fake-repo"));
748
+
749
+ const { gitClone } = await esmock("../helpers/source.js", {
750
+ "../helpers/utils.js": {
751
+ safeSpawnSync: spawnStub,
752
+ isSecureMode: false,
753
+ hasDangerousUnicode: sinon.stub().returns(false),
754
+ getTmpDir: sinon.stub().returns(os.tmpdir()),
755
+ },
756
+ "node:fs": {
757
+ mkdtempSync: mkdtempStub,
758
+ existsSync: sinon.stub().returns(false),
759
+ readdirSync: sinon.stub().returns([]),
760
+ statSync: sinon.stub().returns({ isDirectory: () => true }),
761
+ readFileSync: sinon.stub().returns(""),
762
+ },
763
+ });
764
+
765
+ gitClone("https://example.com/repo.git", "main");
766
+
767
+ const [, args] = spawnStub.firstCall.args;
768
+ const branchIdx = args.indexOf("--branch");
769
+ assert.ok(branchIdx >= 0, "expected --branch in git args");
770
+ assert.strictEqual(args[branchIdx + 1], "main");
771
+ });
772
+
773
+ it("skips --branch when the branch name starts with a dash", async () => {
774
+ const spawnStub = sinon.stub().returns({ status: 0, stderr: "" });
775
+ const mkdtempStub = sinon
776
+ .stub()
777
+ .returns(path.join(os.tmpdir(), "fake-repo"));
778
+
779
+ const { gitClone } = await esmock("../helpers/source.js", {
780
+ "../helpers/utils.js": {
781
+ safeSpawnSync: spawnStub,
782
+ isSecureMode: false,
783
+ hasDangerousUnicode: sinon.stub().returns(false),
784
+ getTmpDir: sinon.stub().returns(os.tmpdir()),
785
+ },
786
+ "node:fs": {
787
+ mkdtempSync: mkdtempStub,
788
+ existsSync: sinon.stub().returns(false),
789
+ readdirSync: sinon.stub().returns([]),
790
+ statSync: sinon.stub().returns({ isDirectory: () => true }),
791
+ readFileSync: sinon.stub().returns(""),
792
+ },
793
+ });
794
+
795
+ gitClone("https://example.com/repo.git", "--malicious");
796
+
797
+ const [, args] = spawnStub.firstCall.args;
798
+ assert.ok(
799
+ !args.includes("--branch"),
800
+ "must not include --branch for dash-prefixed branch names",
801
+ );
802
+ });
803
+ });
@@ -2,6 +2,7 @@ import { readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
 
4
4
  import { thoughtLog } from "../../helpers/logger.js";
5
+ import { getTrustedPublishingComponentCounts } from "../../helpers/provenanceUtils.js";
5
6
  import { dirNameStr } from "../../helpers/utils.js";
6
7
 
7
8
  // Tags per BOM type.
@@ -295,6 +296,9 @@ export function textualMetadata(bomJson) {
295
296
  c?.purl?.startsWith("pkg:swid"),
296
297
  ).length;
297
298
  const githubStats = getGitHubWorkflowStats(bomJson?.components);
299
+ const trustedPublishingCounts = getTrustedPublishingComponentCounts(
300
+ bomJson?.components,
301
+ );
298
302
  const isGitHubBom = bomType === "SBOM";
299
303
  if (metadata?.timestamp) {
300
304
  text = `This ${bomTypeDescription} document was created on ${humanifyTimestamp(metadata.timestamp)}`;
@@ -469,6 +473,9 @@ export function textualMetadata(bomJson) {
469
473
  if (!isGitHubBom) {
470
474
  text = `${text} There are ${bomJson.components.length} components.`;
471
475
  }
476
+ if (trustedPublishingCounts.total > 0) {
477
+ text = `${text} Trusted publishing metadata is present for ${trustedPublishingCounts.npm} npm component(s) and ${trustedPublishingCounts.pypi} PyPI component(s).`;
478
+ }
472
479
  } else {
473
480
  text = `${text} BOM file is empty without components.`;
474
481
  thoughtLog(
@@ -263,6 +263,46 @@ it("textualMetadata tests", () => {
263
263
  }),
264
264
  "This Operations Bill-of-Materials (OBOM) document was created on Monday, November 11, 2024 with cdxgen. The lifecycles phases represented are: pre-build and operations. The document describes an operating system named 'Microsoft Windows 11 Pro' with version '22H2'. BOM file is empty without components. The OS uses the x64 architecture and has the build version '10.0.22621'.",
265
265
  );
266
+
267
+ assert.ok(
268
+ textualMetadata({
269
+ metadata: {
270
+ component: {
271
+ name: "trusted-demo",
272
+ type: "application",
273
+ version: "1.0.0",
274
+ },
275
+ timestamp: "2026-01-01T00:00:00Z",
276
+ tools: {
277
+ components: [{ name: "cdxgen" }],
278
+ },
279
+ },
280
+ components: [
281
+ {
282
+ name: "left-pad",
283
+ properties: [
284
+ {
285
+ name: "cdx:npm:trustedPublishing",
286
+ value: "true",
287
+ },
288
+ ],
289
+ type: "library",
290
+ },
291
+ {
292
+ name: "requests",
293
+ properties: [
294
+ {
295
+ name: "cdx:pypi:trustedPublishing",
296
+ value: "true",
297
+ },
298
+ ],
299
+ type: "library",
300
+ },
301
+ ],
302
+ }).includes(
303
+ "Trusted publishing metadata is present for 1 npm component(s) and 1 PyPI component(s).",
304
+ ),
305
+ );
266
306
  });
267
307
 
268
308
  it("extractTags tests", () => {
@@ -5,8 +5,8 @@
5
5
  import { join, resolve } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
 
8
- import { table } from "table";
9
-
8
+ import { buildAnnotationText } from "../../helpers/annotationFormatter.js";
9
+ import { table } from "../../helpers/table.js";
10
10
  import {
11
11
  DEBUG_MODE,
12
12
  getTimestamp,
@@ -16,7 +16,6 @@ import { evaluateRules, loadRules } from "./ruleEngine.js";
16
16
 
17
17
  const __dirname = fileURLToPath(new URL(".", import.meta.url));
18
18
  const BUILTIN_RULES_DIR = join(__dirname, "..", "..", "..", "data", "rules");
19
- const CODE_BLOCK = "```";
20
19
 
21
20
  /**
22
21
  * Audit BOM formulation section using JSONata-powered rule engine
@@ -90,6 +89,7 @@ export function formatConsoleOutput(findings) {
90
89
  columnDefault: { wrapWord: true, width: 100 },
91
90
  columns: [
92
91
  { width: 10 },
92
+ { width: 26 },
93
93
  { width: 35 },
94
94
  { width: 50 },
95
95
  { width: 50 },
@@ -100,10 +100,13 @@ export function formatConsoleOutput(findings) {
100
100
  content: "BOM Audit Findings\nGenerated with \u2665 by cdxgen",
101
101
  },
102
102
  };
103
- const data = [["Rule", "Message", "Description", "Ref", "File"]];
103
+ const data = [["Rule", "ATT&CK", "Message", "Description", "Ref", "File"]];
104
104
  for (const f of findings) {
105
105
  const line = [];
106
106
  line.push(f.ruleId);
107
+ line.push(
108
+ [...(f.attackTactics || []), ...(f.attackTechniques || [])].join(", "),
109
+ );
107
110
  line.push(f.message);
108
111
  line.push(f.description || "");
109
112
  line.push(f.location?.purl || f.location?.bomRef || "");
@@ -144,6 +147,18 @@ export function formatAnnotations(findings, bomJson) {
144
147
  if (f.mitigation) {
145
148
  properties.push({ name: "cdx:audit:mitigation", value: f.mitigation });
146
149
  }
150
+ if (f.attackTactics?.length) {
151
+ properties.push({
152
+ name: "cdx:audit:attack:tactics",
153
+ value: f.attackTactics.join(","),
154
+ });
155
+ }
156
+ if (f.attackTechniques?.length) {
157
+ properties.push({
158
+ name: "cdx:audit:attack:techniques",
159
+ value: f.attackTechniques.join(","),
160
+ });
161
+ }
147
162
  if (f?.location?.purl) {
148
163
  properties.push({
149
164
  name: "cdx:audit:location:purl",
@@ -178,7 +193,7 @@ export function formatAnnotations(findings, bomJson) {
178
193
  component: cdxgenAnnotator[0],
179
194
  },
180
195
  timestamp: getTimestamp(),
181
- text: `${f.message}\n${CODE_BLOCK}\n${JSON.stringify(properties)}\n${CODE_BLOCK}`,
196
+ text: buildAnnotationText(f.message, properties),
182
197
  };
183
198
  });
184
199
  }