@cyclonedx/cdxgen 12.3.1 → 12.3.3

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 (87) hide show
  1. package/README.md +6 -0
  2. package/bin/cdxgen.js +1 -2
  3. package/data/rules/ai-agent-governance.yaml +43 -0
  4. package/data/rules/ci-permissions.yaml +132 -0
  5. package/data/rules/dependency-sources.yaml +65 -5
  6. package/data/rules/mcp-servers.yaml +36 -2
  7. package/data/rules/package-integrity.yaml +22 -0
  8. package/lib/cli/index.js +436 -56
  9. package/lib/cli/index.poku.js +875 -2
  10. package/lib/helpers/agentFormulationParser.js +10 -3
  11. package/lib/helpers/agentFormulationParser.poku.js +42 -0
  12. package/lib/helpers/aiInventory.js +262 -0
  13. package/lib/helpers/aiInventory.poku.js +111 -0
  14. package/lib/helpers/analyzer.js +413 -54
  15. package/lib/helpers/analyzer.poku.js +117 -0
  16. package/lib/helpers/auditCategories.js +76 -0
  17. package/lib/helpers/chromextutils.js +25 -3
  18. package/lib/helpers/chromextutils.poku.js +68 -0
  19. package/lib/helpers/ciParsers/githubActions.js +79 -0
  20. package/lib/helpers/ciParsers/githubActions.poku.js +103 -0
  21. package/lib/helpers/communityAiConfigParser.js +15 -5
  22. package/lib/helpers/communityAiConfigParser.poku.js +71 -0
  23. package/lib/helpers/depsUtils.js +5 -0
  24. package/lib/helpers/depsUtils.poku.js +55 -0
  25. package/lib/helpers/display.js +50 -24
  26. package/lib/helpers/display.poku.js +70 -58
  27. package/lib/helpers/formulationParsers.js +26 -6
  28. package/lib/helpers/jsonLike.js +21 -20
  29. package/lib/helpers/jsonLike.poku.js +34 -0
  30. package/lib/helpers/mcpConfigParser.js +32 -16
  31. package/lib/helpers/mcpConfigParser.poku.js +104 -0
  32. package/lib/helpers/mcpDiscovery.js +13 -23
  33. package/lib/helpers/mcpDiscovery.poku.js +21 -0
  34. package/lib/helpers/propertySanitizer.js +121 -0
  35. package/lib/helpers/utils.js +953 -41
  36. package/lib/helpers/utils.poku.js +901 -1
  37. package/lib/managers/binary.js +16 -0
  38. package/lib/managers/binary.poku.js +1 -0
  39. package/lib/managers/docker.js +240 -16
  40. package/lib/managers/docker.poku.js +1142 -2
  41. package/lib/server/server.js +7 -4
  42. package/lib/server/server.poku.js +36 -1
  43. package/lib/stages/postgen/annotator.js +2 -1
  44. package/lib/stages/postgen/annotator.poku.js +15 -0
  45. package/lib/stages/postgen/auditBom.js +12 -6
  46. package/lib/stages/postgen/auditBom.poku.js +755 -6
  47. package/lib/stages/postgen/postgen.js +229 -6
  48. package/lib/stages/postgen/postgen.poku.js +180 -0
  49. package/package.json +2 -1
  50. package/types/lib/cli/index.d.ts +1 -0
  51. package/types/lib/cli/index.d.ts.map +1 -1
  52. package/types/lib/helpers/agentFormulationParser.d.ts +19 -0
  53. package/types/lib/helpers/agentFormulationParser.d.ts.map +1 -0
  54. package/types/lib/helpers/aiInventory.d.ts +23 -0
  55. package/types/lib/helpers/aiInventory.d.ts.map +1 -0
  56. package/types/lib/helpers/analyzer.d.ts +5 -0
  57. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  58. package/types/lib/helpers/auditCategories.d.ts +12 -0
  59. package/types/lib/helpers/auditCategories.d.ts.map +1 -0
  60. package/types/lib/helpers/chromextutils.d.ts.map +1 -1
  61. package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
  62. package/types/lib/helpers/communityAiConfigParser.d.ts +29 -0
  63. package/types/lib/helpers/communityAiConfigParser.d.ts.map +1 -0
  64. package/types/lib/helpers/depsUtils.d.ts.map +1 -1
  65. package/types/lib/helpers/display.d.ts +1 -0
  66. package/types/lib/helpers/display.d.ts.map +1 -1
  67. package/types/lib/helpers/formulationParsers.d.ts.map +1 -1
  68. package/types/lib/helpers/jsonLike.d.ts +4 -0
  69. package/types/lib/helpers/jsonLike.d.ts.map +1 -0
  70. package/types/lib/helpers/mcp.d.ts +29 -0
  71. package/types/lib/helpers/mcp.d.ts.map +1 -0
  72. package/types/lib/helpers/mcpConfigParser.d.ts +30 -0
  73. package/types/lib/helpers/mcpConfigParser.d.ts.map +1 -0
  74. package/types/lib/helpers/mcpDiscovery.d.ts +5 -0
  75. package/types/lib/helpers/mcpDiscovery.d.ts.map +1 -0
  76. package/types/lib/helpers/propertySanitizer.d.ts +3 -0
  77. package/types/lib/helpers/propertySanitizer.d.ts.map +1 -0
  78. package/types/lib/helpers/utils.d.ts +31 -0
  79. package/types/lib/helpers/utils.d.ts.map +1 -1
  80. package/types/lib/managers/binary.d.ts.map +1 -1
  81. package/types/lib/managers/docker.d.ts +3 -0
  82. package/types/lib/managers/docker.d.ts.map +1 -1
  83. package/types/lib/server/server.d.ts +1 -0
  84. package/types/lib/server/server.d.ts.map +1 -1
  85. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  86. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  87. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
package/lib/cli/index.js CHANGED
@@ -18,10 +18,24 @@ import { parse } from "ssri";
18
18
  import { v4 as uuidv4 } from "uuid";
19
19
  import { parse as loadYaml } from "yaml";
20
20
 
21
+ import {
22
+ AI_INSTRUCTION_FILE_KINDS,
23
+ AI_INVENTORY_PROJECT_TYPES,
24
+ AI_SKILL_FILE_KIND,
25
+ collectAiInventory,
26
+ filterInventoryDependencies,
27
+ filterInventorySubjectsByTypes,
28
+ inventoryPropertyValue,
29
+ MCP_CONFIG_FILE_KIND,
30
+ optionIncludesAiInventoryProjectType,
31
+ summarizeAiInventory,
32
+ } from "../helpers/aiInventory.js";
21
33
  import {
22
34
  detectMcpInventory,
35
+ detectPythonMcpInventory,
23
36
  findJSImportsExports,
24
37
  } from "../helpers/analyzer.js";
38
+ import { expandBomAuditCategories } from "../helpers/auditCategories.js";
25
39
  import { parseCaxaMetadata } from "../helpers/caxa.js";
26
40
  import {
27
41
  CHROME_EXTENSION_PURL_TYPE,
@@ -50,6 +64,7 @@ import {
50
64
  addEvidenceForDotnet,
51
65
  addEvidenceForImports,
52
66
  addPlugin,
67
+ attachIdentityTools,
53
68
  buildGradleCommandArguments,
54
69
  buildObjectForCocoaPod,
55
70
  buildObjectForGradleModule,
@@ -74,6 +89,7 @@ import {
74
89
  executeParallelGradleProperties,
75
90
  executePodCommand,
76
91
  extractJarArchive,
92
+ extractToolRefs,
77
93
  frameworksList,
78
94
  generatePixiLockFile,
79
95
  getAllFiles,
@@ -120,6 +136,7 @@ import {
120
136
  parseCloudBuildData,
121
137
  parseCmakeLikeFile,
122
138
  parseCocoaDependency,
139
+ parseColliderLockData,
123
140
  parseComposerJson,
124
141
  parseComposerLock,
125
142
  parseConanData,
@@ -197,6 +214,9 @@ import {
197
214
  shouldFetchLicense,
198
215
  splitOutputByGradleProjects,
199
216
  } from "../helpers/utils.js";
217
+
218
+ export { summarizeAiInventory } from "../helpers/aiInventory.js";
219
+
200
220
  import {
201
221
  cleanupTempDir,
202
222
  collectInstalledExtensions,
@@ -312,7 +332,10 @@ const createDefaultParentComponent = (
312
332
  group: options.projectGroup || "",
313
333
  name: compName,
314
334
  version: `${options.projectVersion}` || "latest",
315
- type: compName.endsWith(".tar") ? "container" : "application",
335
+ type:
336
+ type === "container" || compName.endsWith(".tar")
337
+ ? "container"
338
+ : "application",
316
339
  };
317
340
  const ppurl = new PackageURL(
318
341
  type,
@@ -327,6 +350,25 @@ const createDefaultParentComponent = (
327
350
  return parentComponent;
328
351
  };
329
352
 
353
+ const shouldIncludeNodeModulesDir = (options = {}, baseProjectTypes = []) => {
354
+ if (options.deep) {
355
+ return true;
356
+ }
357
+ const projectTypes = Array.isArray(options.projectType)
358
+ ? options.projectType
359
+ : options.projectType
360
+ ? [options.projectType]
361
+ : [];
362
+ if (!projectTypes.length) {
363
+ return true;
364
+ }
365
+ return baseProjectTypes.some((projectType) =>
366
+ projectTypes.every((selectedProjectType) =>
367
+ PROJECT_TYPE_ALIASES[projectType]?.includes(selectedProjectType),
368
+ ),
369
+ );
370
+ };
371
+
330
372
  const determineParentComponent = (options) => {
331
373
  let parentComponent;
332
374
  if (options.parentComponent && Object.keys(options.parentComponent).length) {
@@ -1351,17 +1393,21 @@ export async function createJarBom(path, options) {
1351
1393
  let pkgList = [];
1352
1394
  let jarFiles;
1353
1395
  let nsMapping = {};
1354
- if (!options.exclude) {
1355
- options.exclude = [];
1356
- }
1357
- // We can look for jar files within node_modules directory
1358
- if (typeof options.includeNodeModulesDir === "undefined" || options.deep) {
1359
- options.includeNodeModulesDir = true;
1396
+ const searchOptions = {
1397
+ ...options,
1398
+ exclude: [...(options.exclude || [])],
1399
+ };
1400
+ if (typeof searchOptions.includeNodeModulesDir === "undefined") {
1401
+ searchOptions.includeNodeModulesDir = shouldIncludeNodeModulesDir(options, [
1402
+ "jar",
1403
+ "war",
1404
+ "ear",
1405
+ ]);
1360
1406
  }
1361
1407
  // Exclude certain directories during oci sbom generation
1362
1408
  if (hasAnyProjectType(["oci"], options, false)) {
1363
- options.exclude.push("**/android-sdk*/**");
1364
- options.exclude.push("**/.sdkman/**");
1409
+ searchOptions.exclude.push("**/android-sdk*/**");
1410
+ searchOptions.exclude.push("**/.sdkman/**");
1365
1411
  }
1366
1412
  const parentComponent = createDefaultParentComponent(path, "maven", options);
1367
1413
  if (options.useGradleCache) {
@@ -1385,14 +1431,14 @@ export async function createJarBom(path, options) {
1385
1431
  jarFiles = getAllFiles(
1386
1432
  path,
1387
1433
  `${options.multiProject ? "**/" : ""}*.[jw]ar`,
1388
- options,
1434
+ searchOptions,
1389
1435
  );
1390
1436
  }
1391
1437
  // Jenkins plugins
1392
1438
  const hpiFiles = getAllFiles(
1393
1439
  path,
1394
1440
  `${options.multiProject ? "**/" : ""}*.hpi`,
1395
- options,
1441
+ searchOptions,
1396
1442
  );
1397
1443
  if (hpiFiles.length) {
1398
1444
  jarFiles = jarFiles.concat(hpiFiles);
@@ -1447,6 +1493,13 @@ export function createBinaryBom(path, options) {
1447
1493
  const binaryBom = JSON.parse(
1448
1494
  readFileSync(binaryBomFile, { encoding: "utf-8" }),
1449
1495
  );
1496
+ attachIdentityTools(
1497
+ binaryBom?.components,
1498
+ extractToolRefs(
1499
+ binaryBom?.metadata?.tools,
1500
+ (tool) => tool?.name !== "cdxgen",
1501
+ ),
1502
+ );
1450
1503
  return {
1451
1504
  bomJson: binaryBom,
1452
1505
  dependencies: binaryBom.dependencies,
@@ -1874,6 +1927,10 @@ export async function createJavaBom(path, options) {
1874
1927
  ) {
1875
1928
  tools = bomJsonObj.metadata.tools;
1876
1929
  }
1930
+ const toolRefs = extractToolRefs(
1931
+ bomJsonObj?.metadata?.tools,
1932
+ (tool) => tool?.name !== "cdxgen",
1933
+ );
1877
1934
  if (
1878
1935
  bomJsonObj.metadata?.component &&
1879
1936
  !Object.keys(parentComponent).length
@@ -1910,6 +1967,7 @@ export async function createJavaBom(path, options) {
1910
1967
  name: "SrcFile",
1911
1968
  value: srcPomFile,
1912
1969
  });
1970
+ attachIdentityTools(acomp, toolRefs);
1913
1971
  }
1914
1972
  }
1915
1973
  pkgList = pkgList.concat(bomJsonObj.components);
@@ -2717,16 +2775,32 @@ export async function createJavaBom(path, options) {
2717
2775
  * @param {Object} options Parse options from the cli
2718
2776
  * @returns {Promise<Object>} Promise resolving to BOM object
2719
2777
  */
2778
+ function getRequestedAiInventoryTypes(options) {
2779
+ return AI_INVENTORY_PROJECT_TYPES.filter((type) =>
2780
+ optionIncludesAiInventoryProjectType(options?.projectType, type),
2781
+ );
2782
+ }
2783
+
2784
+ function getExcludedAiInventoryTypes(options) {
2785
+ return AI_INVENTORY_PROJECT_TYPES.filter((type) =>
2786
+ optionIncludesAiInventoryProjectType(options?.excludeType, type),
2787
+ );
2788
+ }
2789
+
2790
+ function getExactAiInventoryType(options) {
2791
+ const requestedAiInventoryTypes = getRequestedAiInventoryTypes(options);
2792
+ return requestedAiInventoryTypes.length === 1 &&
2793
+ Array.isArray(options?.projectType) &&
2794
+ options.projectType.length === 1
2795
+ ? requestedAiInventoryTypes[0]
2796
+ : undefined;
2797
+ }
2798
+
2720
2799
  function shouldDetectMcpInventory(options, allImports = {}) {
2721
2800
  if (hasAnyProjectType(["mcp"], options, false)) {
2722
2801
  return true;
2723
2802
  }
2724
- const auditCategories = Array.isArray(options?.bomAuditCategories)
2725
- ? options.bomAuditCategories
2726
- : String(options?.bomAuditCategories || "")
2727
- .split(",")
2728
- .map((category) => category.trim())
2729
- .filter(Boolean);
2803
+ const auditCategories = expandBomAuditCategories(options?.bomAuditCategories);
2730
2804
  if (
2731
2805
  auditCategories.some((category) =>
2732
2806
  ["mcp-server", "ai-agent"].includes(category),
@@ -2740,6 +2814,91 @@ function shouldDetectMcpInventory(options, allImports = {}) {
2740
2814
  });
2741
2815
  }
2742
2816
 
2817
+ function summarizeAiInventoryNames(subjects, discoveryPath, kindSet) {
2818
+ return [
2819
+ ...new Set(
2820
+ (subjects || [])
2821
+ .filter((subject) =>
2822
+ kindSet.has(inventoryPropertyValue(subject, "cdx:file:kind")),
2823
+ )
2824
+ .map((subject) => inventoryPropertyValue(subject, "SrcFile"))
2825
+ .filter(Boolean)
2826
+ .map(
2827
+ (filePath) => relative(discoveryPath, filePath) || basename(filePath),
2828
+ ),
2829
+ ),
2830
+ ].sort();
2831
+ }
2832
+
2833
+ function summarizeAiInventoryServiceNames(services) {
2834
+ return [
2835
+ ...new Set(
2836
+ (services || []).map((service) => service?.name).filter(Boolean),
2837
+ ),
2838
+ ].sort();
2839
+ }
2840
+
2841
+ function formatAiInventorySummaryLine(label, count, nameList) {
2842
+ return ` ${label.padEnd(20)} ${count}${nameList.length ? ` (${nameList.join(", ")})` : ""}`;
2843
+ }
2844
+
2845
+ function emitAiInventorySummary(aiInventory, discoveryPath) {
2846
+ const summary = summarizeAiInventory(aiInventory);
2847
+ const totalInventory =
2848
+ summary.instructionCount +
2849
+ summary.skillCount +
2850
+ summary.mcpConfigCount +
2851
+ summary.mcpServiceCount;
2852
+ if (!totalInventory) {
2853
+ return;
2854
+ }
2855
+ const instructionNames = summarizeAiInventoryNames(
2856
+ aiInventory.components,
2857
+ discoveryPath,
2858
+ AI_INSTRUCTION_FILE_KINDS,
2859
+ );
2860
+ const skillNames = summarizeAiInventoryNames(
2861
+ aiInventory.components,
2862
+ discoveryPath,
2863
+ new Set([AI_SKILL_FILE_KIND]),
2864
+ );
2865
+ const mcpConfigNames = summarizeAiInventoryNames(
2866
+ aiInventory.components,
2867
+ discoveryPath,
2868
+ new Set([MCP_CONFIG_FILE_KIND]),
2869
+ );
2870
+ const mcpServiceNames = summarizeAiInventoryServiceNames(
2871
+ aiInventory.services,
2872
+ );
2873
+ console.warn(
2874
+ [
2875
+ "AI Inventory Summary:",
2876
+ formatAiInventorySummaryLine(
2877
+ "AI instruction files:",
2878
+ summary.instructionCount,
2879
+ instructionNames,
2880
+ ),
2881
+ formatAiInventorySummaryLine(
2882
+ "Skill files:",
2883
+ summary.skillCount,
2884
+ skillNames,
2885
+ ),
2886
+ formatAiInventorySummaryLine(
2887
+ "MCP configs:",
2888
+ summary.mcpConfigCount,
2889
+ mcpConfigNames,
2890
+ ),
2891
+ formatAiInventorySummaryLine(
2892
+ "MCP services:",
2893
+ summary.mcpServiceCount,
2894
+ mcpServiceNames,
2895
+ ),
2896
+ "",
2897
+ "Run --bom-audit --bom-audit-categories ai-inventory to audit these surfaces.",
2898
+ ].join("\n"),
2899
+ );
2900
+ }
2901
+
2743
2902
  export async function createNodejsBom(path, options) {
2744
2903
  let pkgList = [];
2745
2904
  let manifestFiles = [];
@@ -2747,6 +2906,15 @@ export async function createNodejsBom(path, options) {
2747
2906
  let parentComponent = {};
2748
2907
  const parentSubComponents = [];
2749
2908
  let ppurl = "";
2909
+ const requestedAiInventoryTypes = getRequestedAiInventoryTypes(options);
2910
+ const excludedAiInventoryTypes = getExcludedAiInventoryTypes(options);
2911
+ const exactAiInventoryType = getExactAiInventoryType(options);
2912
+ const includedAiInventoryTypes = exactAiInventoryType
2913
+ ? requestedAiInventoryTypes
2914
+ : AI_INVENTORY_PROJECT_TYPES.filter(
2915
+ (type) => !excludedAiInventoryTypes.includes(type),
2916
+ );
2917
+ let aiInventory = { components: [], dependencies: [], services: [] };
2750
2918
  // Docker mode requires special handling
2751
2919
  if (hasAnyProjectType(["docker", "oci", "container", "os"], options, false)) {
2752
2920
  const pkgJsonFiles = getAllFiles(path, "**/package.json", options);
@@ -2758,11 +2926,27 @@ export async function createNodejsBom(path, options) {
2758
2926
  pkgList = pkgList.concat(dlist);
2759
2927
  }
2760
2928
  }
2929
+ if (includedAiInventoryTypes.length) {
2930
+ aiInventory = collectAiInventory(
2931
+ path,
2932
+ options,
2933
+ includedAiInventoryTypes,
2934
+ );
2935
+ }
2936
+ if (aiInventory.components?.length) {
2937
+ pkgList = trimComponents(pkgList.concat(aiInventory.components));
2938
+ }
2761
2939
  return buildBomNSData(options, pkgList, "npm", {
2762
2940
  allImports: {},
2763
2941
  src: path,
2764
2942
  filename: "package.json",
2943
+ dependencies: mergeDependencies(
2944
+ [],
2945
+ aiInventory.dependencies,
2946
+ parentComponent,
2947
+ ),
2765
2948
  parentComponent,
2949
+ services: aiInventory.services,
2766
2950
  });
2767
2951
  }
2768
2952
  }
@@ -2785,6 +2969,40 @@ export async function createNodejsBom(path, options) {
2785
2969
  mcpInventory = detectMcpInventory(path, options.deep);
2786
2970
  }
2787
2971
  }
2972
+ if (includedAiInventoryTypes.length) {
2973
+ aiInventory = collectAiInventory(path, options, includedAiInventoryTypes);
2974
+ }
2975
+ if (excludedAiInventoryTypes.includes("mcp")) {
2976
+ mcpInventory = { components: [], dependencies: [], services: [] };
2977
+ }
2978
+ const aiInventorySummary = summarizeAiInventory(aiInventory);
2979
+ if (!exactAiInventoryType) {
2980
+ if (aiInventorySummary.instructionCount || aiInventorySummary.skillCount) {
2981
+ thoughtLog(
2982
+ `I found ${aiInventorySummary.instructionCount + aiInventorySummary.skillCount} AI skill/instruction file component(s). Use '--exclude-type ai-skill' for a package-only BOM, or '--bom-audit --bom-audit-categories ai-inventory' for review-friendly reporting.`,
2983
+ );
2984
+ }
2985
+ if (aiInventorySummary.mcpConfigCount) {
2986
+ thoughtLog(
2987
+ `I found ${aiInventorySummary.mcpConfigCount} MCP config component(s). Use '--exclude-type mcp' to drop them, or '--bom-audit --bom-audit-categories ai-inventory --tlp-classification AMBER' to keep and flag them.`,
2988
+ );
2989
+ }
2990
+ emitAiInventorySummary(aiInventory, path);
2991
+ }
2992
+ if (exactAiInventoryType === "ai-skill") {
2993
+ const exactComponents = trimComponents([...(aiInventory.components || [])]);
2994
+ const exactDependencies = mergeDependencies([], aiInventory.dependencies);
2995
+ const exactServices = mergeServices([], aiInventory.services);
2996
+ parentComponent = createDefaultParentComponent(path, "generic", options);
2997
+ return buildBomNSData(options, exactComponents, "generic", {
2998
+ dependencies: exactDependencies,
2999
+ filename: path,
3000
+ parentComponent,
3001
+ projectType: exactAiInventoryType,
3002
+ services: exactServices,
3003
+ src: path,
3004
+ });
3005
+ }
2788
3006
  let yarnLockFiles = getAllFiles(
2789
3007
  path,
2790
3008
  `${options.multiProject ? "**/" : ""}yarn.lock`,
@@ -3675,12 +3893,37 @@ export async function createNodejsBom(path, options) {
3675
3893
  parentComponent,
3676
3894
  );
3677
3895
  }
3896
+ if (aiInventory.components?.length) {
3897
+ pkgList = trimComponents(pkgList.concat(aiInventory.components));
3898
+ }
3899
+ if (aiInventory.dependencies?.length) {
3900
+ dependencies = mergeDependencies(
3901
+ dependencies,
3902
+ aiInventory.dependencies,
3903
+ parentComponent,
3904
+ );
3905
+ }
3906
+ const inventoryServices = mergeServices(
3907
+ mergeServices([], mcpInventory.services || []),
3908
+ aiInventory.services || [],
3909
+ );
3910
+ if (exactAiInventoryType === "mcp") {
3911
+ pkgList = trimComponents(filterInventorySubjectsByTypes(pkgList, ["mcp"]));
3912
+ dependencies = filterInventoryDependencies(
3913
+ dependencies,
3914
+ pkgList,
3915
+ inventoryServices,
3916
+ );
3917
+ if (!parentComponent || !Object.keys(parentComponent).length) {
3918
+ parentComponent = createDefaultParentComponent(path, "generic", options);
3919
+ }
3920
+ }
3678
3921
  return buildBomNSData(options, pkgList, "npm", {
3679
3922
  src: path,
3680
3923
  filename: manifestFiles.join(", "),
3681
3924
  dependencies,
3682
3925
  parentComponent,
3683
- services: mergeServices([], mcpInventory.services || []),
3926
+ services: inventoryServices,
3684
3927
  });
3685
3928
  }
3686
3929
 
@@ -3859,6 +4102,16 @@ export async function createPythonBom(path, options) {
3859
4102
  let dependencies = [];
3860
4103
  let pkgList = [];
3861
4104
  let formulationList = [];
4105
+ const requestedAiInventoryTypes = getRequestedAiInventoryTypes(options);
4106
+ const excludedAiInventoryTypes = getExcludedAiInventoryTypes(options);
4107
+ const includedAiInventoryTypes = AI_INVENTORY_PROJECT_TYPES.filter(
4108
+ (type) =>
4109
+ (!requestedAiInventoryTypes.length ||
4110
+ requestedAiInventoryTypes.includes(type)) &&
4111
+ !excludedAiInventoryTypes.includes(type),
4112
+ );
4113
+ let aiInventory = { components: [], dependencies: [], services: [] };
4114
+ let mcpInventory = { components: [], dependencies: [], services: [] };
3862
4115
  const tempDir = safeMkdtempSync(join(getTmpDir(), "cdxgen-venv-"));
3863
4116
  let parentComponent = createDefaultParentComponent(path, "pypi", options);
3864
4117
  // We are checking only the root here for pipenv
@@ -4350,6 +4603,24 @@ export async function createPythonBom(path, options) {
4350
4603
  pkgList = pkgList.concat(dlist);
4351
4604
  }
4352
4605
  }
4606
+ if (includedAiInventoryTypes.length) {
4607
+ aiInventory = collectAiInventory(path, options, includedAiInventoryTypes);
4608
+ if (includedAiInventoryTypes.includes("mcp")) {
4609
+ mcpInventory = detectPythonMcpInventory(path, options.deep);
4610
+ }
4611
+ }
4612
+ const aiInventorySummary = summarizeAiInventory(aiInventory);
4613
+ if (aiInventorySummary.instructionCount || aiInventorySummary.skillCount) {
4614
+ thoughtLog(
4615
+ `I found ${aiInventorySummary.instructionCount + aiInventorySummary.skillCount} AI skill/instruction file component(s). Use '--exclude-type ai-skill' for a package-only BOM, or '--bom-audit --bom-audit-categories ai-inventory' for review-friendly reporting.`,
4616
+ );
4617
+ }
4618
+ if (aiInventorySummary.mcpConfigCount) {
4619
+ thoughtLog(
4620
+ `I found ${aiInventorySummary.mcpConfigCount} MCP config component(s). Use '--exclude-type mcp' to drop them, or '--bom-audit --bom-audit-categories ai-inventory --tlp-classification AMBER' to keep and flag them.`,
4621
+ );
4622
+ }
4623
+ emitAiInventorySummary(aiInventory, path);
4353
4624
  // Check and complete the dependency tree
4354
4625
  if (
4355
4626
  isFeatureEnabled(options, "safe-pip-install") &&
@@ -4398,6 +4669,30 @@ export async function createPythonBom(path, options) {
4398
4669
  if (shouldFetchLicense()) {
4399
4670
  pkgList = await getPyMetadata(pkgList, false);
4400
4671
  }
4672
+ if (mcpInventory.components?.length) {
4673
+ pkgList = trimComponents(pkgList.concat(mcpInventory.components));
4674
+ }
4675
+ if (mcpInventory.dependencies?.length) {
4676
+ dependencies = mergeDependencies(
4677
+ dependencies,
4678
+ mcpInventory.dependencies,
4679
+ parentComponent,
4680
+ );
4681
+ }
4682
+ if (aiInventory.components?.length) {
4683
+ pkgList = trimComponents(pkgList.concat(aiInventory.components));
4684
+ }
4685
+ if (aiInventory.dependencies?.length) {
4686
+ dependencies = mergeDependencies(
4687
+ dependencies,
4688
+ aiInventory.dependencies,
4689
+ parentComponent,
4690
+ );
4691
+ }
4692
+ const inventoryServices = mergeServices(
4693
+ mergeServices([], mcpInventory.services || []),
4694
+ aiInventory.services || [],
4695
+ );
4401
4696
  return buildBomNSData(options, pkgList, "pypi", {
4402
4697
  allImports,
4403
4698
  src: path,
@@ -4405,6 +4700,7 @@ export async function createPythonBom(path, options) {
4405
4700
  dependencies,
4406
4701
  parentComponent,
4407
4702
  formulationList,
4703
+ services: inventoryServices,
4408
4704
  });
4409
4705
  }
4410
4706
 
@@ -5161,6 +5457,11 @@ export function createCppBom(path, options) {
5161
5457
  let parentComponent;
5162
5458
  let dependencies = [];
5163
5459
  const addedParentComponentsMap = {};
5460
+ const colliderLockFiles = getAllFiles(
5461
+ path,
5462
+ `${options.multiProject ? "**/" : ""}collider.lock`,
5463
+ options,
5464
+ );
5164
5465
  const conanLockFiles = getAllFiles(
5165
5466
  path,
5166
5467
  `${options.multiProject ? "**/" : ""}conan.lock`,
@@ -5237,6 +5538,39 @@ export function createCppBom(path, options) {
5237
5538
  }
5238
5539
  }
5239
5540
  }
5541
+ if (colliderLockFiles.length) {
5542
+ for (const f of colliderLockFiles) {
5543
+ if (DEBUG_MODE) {
5544
+ console.log(`Parsing ${f}`);
5545
+ }
5546
+ const colliderLockData = readFileSync(f, { encoding: "utf-8" });
5547
+ const {
5548
+ pkgList: colliderPkgList,
5549
+ dependencies: colliderDependencies,
5550
+ parentComponentDependencies: parentCompDeps,
5551
+ } = parseColliderLockData(colliderLockData, f);
5552
+
5553
+ if (colliderPkgList.length) {
5554
+ pkgList = pkgList.concat(colliderPkgList);
5555
+ }
5556
+
5557
+ if (Object.keys(colliderDependencies).length) {
5558
+ dependencies = mergeDependencies(
5559
+ dependencies,
5560
+ Object.keys(colliderDependencies).map((dependentBomRef) => ({
5561
+ ref: dependentBomRef,
5562
+ dependsOn: colliderDependencies[dependentBomRef],
5563
+ })),
5564
+ );
5565
+ }
5566
+
5567
+ if (parentCompDeps.length) {
5568
+ parentComponentDependencies = [
5569
+ ...new Set(parentComponentDependencies.concat(parentCompDeps)),
5570
+ ];
5571
+ }
5572
+ }
5573
+ }
5240
5574
  if (cmakeLikeFiles.length) {
5241
5575
  for (const f of cmakeLikeFiles) {
5242
5576
  if (DEBUG_MODE) {
@@ -6575,24 +6909,26 @@ export async function createContainerSpecLikeBom(path, options) {
6575
6909
  export function createPHPBom(path, options) {
6576
6910
  let dependencies = [];
6577
6911
  let parentComponent = {};
6578
- // We can look for composer files within node_modules directory
6579
- if (typeof options.includeNodeModulesDir === "undefined" || options.deep) {
6580
- options.includeNodeModulesDir = true;
6581
- }
6912
+ const searchOptions = {
6913
+ ...options,
6914
+ includeNodeModulesDir:
6915
+ typeof options.includeNodeModulesDir === "undefined"
6916
+ ? shouldIncludeNodeModulesDir(options, ["php"])
6917
+ : options.includeNodeModulesDir,
6918
+ };
6919
+ const composerLockSearchOptions = {
6920
+ ...searchOptions,
6921
+ exclude: [...(options.exclude || []), "**/vendor/**"],
6922
+ };
6582
6923
  const composerJsonFiles = getAllFiles(
6583
6924
  path,
6584
6925
  `${options.multiProject ? "**/" : ""}composer.json`,
6585
- options,
6926
+ searchOptions,
6586
6927
  );
6587
- if (!options.exclude) {
6588
- options.exclude = [];
6589
- }
6590
- // Ignore vendor directories for lock files
6591
- options.exclude.push("**/vendor/**");
6592
6928
  let composerLockFiles = getAllFiles(
6593
6929
  path,
6594
6930
  `${options.multiProject ? "**/" : ""}composer.lock`,
6595
- options,
6931
+ composerLockSearchOptions,
6596
6932
  );
6597
6933
  let pkgList = [];
6598
6934
  const composerJsonMode = composerJsonFiles.length;
@@ -6655,7 +6991,7 @@ export function createPHPBom(path, options) {
6655
6991
  composerLockFiles = getAllFiles(
6656
6992
  path,
6657
6993
  `${options.multiProject ? "**/" : ""}composer.lock`,
6658
- options,
6994
+ composerLockSearchOptions,
6659
6995
  );
6660
6996
  if (composerLockFiles.length) {
6661
6997
  // Look for any root composer.json to capture the parentComponent
@@ -7803,6 +8139,7 @@ export async function createMultiXBom(pathList, options) {
7803
8139
  binPaths,
7804
8140
  executables,
7805
8141
  sharedLibs,
8142
+ tools,
7806
8143
  } = await getOSPackages(
7807
8144
  options.allLayersExplodedDir,
7808
8145
  options.exportData?.inspectData?.Config,
@@ -7833,6 +8170,13 @@ export async function createMultiXBom(pathList, options) {
7833
8170
  if (allTypes?.length) {
7834
8171
  options.allOSComponentTypes = allTypes;
7835
8172
  }
8173
+ if (tools?.length) {
8174
+ options.tools = (
8175
+ Array.isArray(options.tools)
8176
+ ? options.tools
8177
+ : options.tools?.components || []
8178
+ ).concat(tools);
8179
+ }
7836
8180
  components = components.concat(osPackages);
7837
8181
  components = components.concat(executables);
7838
8182
  components = components.concat(sharedLibs);
@@ -7882,22 +8226,39 @@ export async function createMultiXBom(pathList, options) {
7882
8226
  if (pathList.length > 2) {
7883
8227
  thoughtLog(`Let's thoroughly check the path ${path}.`);
7884
8228
  }
7885
- // Node.js
7886
- if (hasAnyProjectType(["oci", "js"], options)) {
8229
+ // Node.js and AI inventory
8230
+ if (hasAnyProjectType(["oci", "js", "mcp", "ai-skill"], options)) {
8231
+ const exactAiInventoryType = getExactAiInventoryType(options);
7887
8232
  if (!hasAnyProjectType(["oci"], options, false)) {
7888
- thoughtLog(
7889
- "**JS**: Now looking for JavaScript projects (npm, yarn, pnpm) and files.",
7890
- );
8233
+ if (exactAiInventoryType === "mcp") {
8234
+ thoughtLog(
8235
+ "**MCP**: Looking for MCP services, MCP configs, and related AI control-plane artifacts.",
8236
+ );
8237
+ } else if (exactAiInventoryType === "ai-skill") {
8238
+ thoughtLog(
8239
+ "**AI-SKILL**: Looking for AI instruction, skill, and agent-definition files that can influence build or release flows.",
8240
+ );
8241
+ } else {
8242
+ thoughtLog(
8243
+ "**JS**: Now looking for JavaScript projects (npm, yarn, pnpm) and files.",
8244
+ );
8245
+ }
7891
8246
  }
7892
- setProjectTypeActivityContext("js", path);
8247
+ setProjectTypeActivityContext(exactAiInventoryType || "js", path);
7893
8248
  bomData = await createNodejsBom(path, options);
7894
8249
  if (bomData?.bomJson?.components?.length) {
7895
- thoughtLog(
7896
- `I found ${bomData.bomJson.components.length} npm packages. Let's keep looking.`,
7897
- );
8250
+ if (exactAiInventoryType) {
8251
+ thoughtLog(
8252
+ `I found ${bomData.bomJson.components.length} ${exactAiInventoryType} component(s). Let's keep looking.`,
8253
+ );
8254
+ } else {
8255
+ thoughtLog(
8256
+ `I found ${bomData.bomJson.components.length} npm packages. Let's keep looking.`,
8257
+ );
8258
+ }
7898
8259
  if (DEBUG_MODE) {
7899
8260
  console.log(
7900
- `Found ${bomData.bomJson.components.length} npm packages at ${path}`,
8261
+ `Found ${bomData.bomJson.components.length} ${exactAiInventoryType || "npm"} components at ${path}`,
7901
8262
  );
7902
8263
  }
7903
8264
  components = components.concat(bomData.bomJson.components);
@@ -8890,6 +9251,11 @@ export async function createXBom(path, options) {
8890
9251
  `${options.multiProject ? "**/" : ""}conan.lock`,
8891
9252
  options,
8892
9253
  );
9254
+ const colliderLockFiles = getAllFiles(
9255
+ path,
9256
+ `${options.multiProject ? "**/" : ""}collider.lock`,
9257
+ options,
9258
+ );
8893
9259
  const conanFiles = getAllFiles(
8894
9260
  path,
8895
9261
  `${options.multiProject ? "**/" : ""}conanfile.txt`,
@@ -8906,6 +9272,7 @@ export async function createXBom(path, options) {
8906
9272
  options,
8907
9273
  );
8908
9274
  if (
9275
+ colliderLockFiles.length ||
8909
9276
  conanLockFiles.length ||
8910
9277
  conanFiles.length ||
8911
9278
  cmakeListFiles.length ||
@@ -9075,6 +9442,10 @@ export async function createBom(path, options) {
9075
9442
  }
9076
9443
  let exportData;
9077
9444
  let isContainerMode = false;
9445
+ const isLocalDirectoryInput =
9446
+ !options.projectType?.includes("universal") &&
9447
+ safeExistsSync(path) &&
9448
+ lstatSync(path).isDirectory();
9078
9449
  // Docker and image archive support
9079
9450
  // TODO: Support any source archive
9080
9451
  if (path.endsWith(".tar") || path.endsWith(".tar.gz")) {
@@ -9087,6 +9458,22 @@ export async function createBom(path, options) {
9087
9458
  return {};
9088
9459
  }
9089
9460
  isContainerMode = true;
9461
+ } else if (
9462
+ isLocalDirectoryInput &&
9463
+ (hasAnyProjectType(["oci-dir"], options, false) ||
9464
+ hasAnyProjectType(["oci"], options, false))
9465
+ ) {
9466
+ isContainerMode = true;
9467
+ exportData = {
9468
+ inspectData: undefined,
9469
+ lastWorkingDir: "",
9470
+ allLayersDir: path,
9471
+ allLayersExplodedDir: path,
9472
+ };
9473
+ if (safeExistsSync(join(path, "all-layers"))) {
9474
+ exportData.allLayersExplodedDir = join(path, "all-layers");
9475
+ }
9476
+ exportData.pkgPathList = getPkgPathList(exportData, undefined);
9090
9477
  } else if (
9091
9478
  (options.projectType &&
9092
9479
  !options.projectType?.includes("universal") &&
@@ -9114,21 +9501,6 @@ export async function createBom(path, options) {
9114
9501
  console.log(path, "doesn't appear to be a valid container image.");
9115
9502
  }
9116
9503
  }
9117
- } else if (
9118
- !options.projectType?.includes("universal") &&
9119
- hasAnyProjectType(["oci-dir"], options, false)
9120
- ) {
9121
- isContainerMode = true;
9122
- exportData = {
9123
- inspectData: undefined,
9124
- lastWorkingDir: "",
9125
- allLayersDir: path,
9126
- allLayersExplodedDir: path,
9127
- };
9128
- if (safeExistsSync(join(path, "all-layers"))) {
9129
- exportData.allLayersDir = join(path, "all-layers");
9130
- }
9131
- exportData.pkgPathList = getPkgPathList(exportData, undefined);
9132
9504
  }
9133
9505
  if (isContainerMode) {
9134
9506
  options.multiProject = true;
@@ -9238,6 +9610,12 @@ export async function createBom(path, options) {
9238
9610
  ) {
9239
9611
  return await createNodejsBom(path, options);
9240
9612
  }
9613
+ if (PROJECT_TYPE_ALIASES["mcp"].includes(projectType[0])) {
9614
+ return await createNodejsBom(path, options);
9615
+ }
9616
+ if (PROJECT_TYPE_ALIASES["ai-skill"].includes(projectType[0])) {
9617
+ return await createNodejsBom(path, options);
9618
+ }
9241
9619
  if (
9242
9620
  PROJECT_TYPE_ALIASES["py"].includes(projectType[0]) ||
9243
9621
  projectType?.[0]?.startsWith("python")
@@ -9402,6 +9780,7 @@ export async function submitBom(args, bomContents) {
9402
9780
  // See issue #1963 regarding CRLF hardening
9403
9781
  return await got(serverUrl, {
9404
9782
  method: "PUT",
9783
+ followRedirect: false,
9405
9784
  https: {
9406
9785
  rejectUnauthorized: !args.skipDtTlsCheck,
9407
9786
  },
@@ -9427,11 +9806,12 @@ export async function submitBom(args, bomContents) {
9427
9806
  try {
9428
9807
  return await got(serverUrl, {
9429
9808
  method: "POST",
9809
+ followRedirect: false,
9430
9810
  https: {
9431
9811
  rejectUnauthorized: !args.skipDtTlsCheck,
9432
9812
  },
9433
9813
  headers: {
9434
- "X-Api-Key": args.apiKey,
9814
+ "X-Api-Key": (args.apiKey || "").replace(/[\r\n]/g, ""),
9435
9815
  "Content-Type": "application/json",
9436
9816
  "user-agent": `@CycloneDX/cdxgen ${CDXGEN_VERSION}`,
9437
9817
  },