@cdxgen/cdxgen 13.4.0 → 13.5.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 (208) hide show
  1. package/README.md +7 -7
  2. package/bin/cdxgen-secure.js +15 -0
  3. package/bin/cdxgen.js +25 -5
  4. package/bin/evinse.js +5 -0
  5. package/bin/hbom.js +15 -7
  6. package/bin/repl.js +29 -0
  7. package/data/cbomosdb-queries.json +13 -1
  8. package/data/component-tags.json +8 -0
  9. package/data/queries-darwin.json +25 -1
  10. package/data/queries-win.json +19 -1
  11. package/data/queries.json +41 -0
  12. package/data/remediations.json +283 -1
  13. package/data/rules/cbom-security.yaml +235 -4
  14. package/data/rules/go-binaries.yaml +46 -0
  15. package/data/rules/golem-go.yaml +80 -0
  16. package/data/rules/hbom-compliance.yaml +40 -22
  17. package/data/rules/hbom-performance.yaml +7 -1
  18. package/data/rules/host-topology.yaml +1 -1
  19. package/data/rules/obom-runtime.yaml +31 -2
  20. package/data/rules/php-runtime.yaml +143 -0
  21. package/lib/audit/index.js +14 -0
  22. package/lib/cli/bomAssembly.js +28 -3
  23. package/lib/cli/cliOptions.js +110 -3
  24. package/lib/cli/index.js +73 -4
  25. package/lib/cli/jvmBom.js +126 -20
  26. package/lib/cli/managedBom.js +711 -71
  27. package/lib/cli/nativeBom.js +225 -28
  28. package/lib/core/env.js +30 -8
  29. package/lib/core/fs.js +28 -19
  30. package/lib/core/httpClient.js +74 -45
  31. package/lib/core/projectConfig.js +1 -0
  32. package/lib/core/projectTooling.js +97 -0
  33. package/lib/core/propertySanitizer.js +72 -0
  34. package/lib/ecosystems/golem.js +550 -6
  35. package/lib/ecosystems/gradleutils.js +30 -20
  36. package/lib/ecosystems/parsers-dotnet.js +117 -18
  37. package/lib/ecosystems/parsers-go.js +1090 -367
  38. package/lib/ecosystems/parsers-jvm.js +259 -17
  39. package/lib/ecosystems/parsers-misc.js +229 -44
  40. package/lib/ecosystems/parsers-python.js +11 -10
  41. package/lib/ecosystems/rubyutils.js +6 -3
  42. package/lib/ecosystems/rusi.js +10 -0
  43. package/lib/ecosystems/scalacliutils.js +2 -1
  44. package/lib/ecosystems/scalasem.js +3 -1
  45. package/lib/evinser/evinser.js +126 -18
  46. package/lib/evinser/swiftsem.js +68 -5
  47. package/lib/inventory/agentSkillSpec.js +17 -0
  48. package/lib/inventory/apkInstalledDb.js +243 -0
  49. package/lib/inventory/atomUtils.js +33 -0
  50. package/lib/inventory/auditCategories.js +3 -3
  51. package/lib/inventory/cacheDir.js +52 -9
  52. package/lib/inventory/certificates.js +2371 -0
  53. package/lib/inventory/communityAiConfigParser.js +4 -9
  54. package/lib/inventory/deps.js +6 -4
  55. package/lib/inventory/display.js +200 -3
  56. package/lib/inventory/dosai.js +500 -58
  57. package/lib/inventory/dynamic.js +45 -3
  58. package/lib/inventory/envcontext.js +168 -5
  59. package/lib/inventory/evidenceUtils.js +436 -23
  60. package/lib/inventory/formulationParsers.js +3 -2
  61. package/lib/inventory/goBuildInfo.js +140 -0
  62. package/lib/inventory/hbom.js +67 -7
  63. package/lib/inventory/hbomAnalysis.js +16 -0
  64. package/lib/inventory/hostTopology.js +21 -0
  65. package/lib/inventory/osPackagePurl.js +244 -0
  66. package/lib/inventory/osPackageResolver.js +89 -87
  67. package/lib/inventory/osinfo.js +79 -31
  68. package/lib/inventory/osqueryTransform.js +114 -5
  69. package/lib/inventory/phpBuildInfo.js +487 -0
  70. package/lib/inventory/phpHostProbe.js +641 -0
  71. package/lib/inventory/rootfsCerts.js +426 -0
  72. package/lib/inventory/source.js +207 -3
  73. package/lib/inventory/spdx.js +144 -0
  74. package/lib/inventory/traceRunner.js +5 -1
  75. package/lib/inventory/tracedCrypto.js +129 -0
  76. package/lib/managers/binary.js +529 -183
  77. package/lib/managers/docker.js +166 -12
  78. package/lib/parsers/email.js +277 -0
  79. package/lib/parsers/largeJson.js +34 -2
  80. package/lib/server/server.js +186 -1
  81. package/lib/stages/postgen/annotator.js +17 -1
  82. package/lib/stages/postgen/introspection/reflect.js +4 -1
  83. package/lib/stages/postgen/postgen.js +5 -5
  84. package/lib/stages/postgen/ruleEngine.js +58 -1
  85. package/lib/stages/postgen/specVersionCompat.js +30 -0
  86. package/lib/stages/pregen/pregen.js +117 -5
  87. package/lib/validator/bomValidator.js +26 -5
  88. package/lib/validator/generated/runtime.mjs +13 -0
  89. package/lib/validator/generated/validate-1.6.mjs +26 -7
  90. package/lib/validator/generated/validate-1.7.mjs +31 -12
  91. package/lib/validator/generated/validate-2.0.mjs +30 -11
  92. package/package.json +17 -17
  93. package/types/bin/cdxgen-secure.d.ts +3 -0
  94. package/types/bin/cdxgen-secure.d.ts.map +1 -0
  95. package/types/bin/repl.d.ts.map +1 -1
  96. package/types/lib/audit/index.d.ts.map +1 -1
  97. package/types/lib/cli/bomAssembly.d.ts +9 -2
  98. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  99. package/types/lib/cli/cliOptions.d.ts +43 -0
  100. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  101. package/types/lib/cli/index.d.ts.map +1 -1
  102. package/types/lib/cli/jvmBom.d.ts +20 -0
  103. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  104. package/types/lib/cli/managedBom.d.ts.map +1 -1
  105. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  106. package/types/lib/core/env.d.ts +18 -2
  107. package/types/lib/core/env.d.ts.map +1 -1
  108. package/types/lib/core/fs.d.ts.map +1 -1
  109. package/types/lib/core/httpClient.d.ts.map +1 -1
  110. package/types/lib/core/projectConfig.d.ts.map +1 -1
  111. package/types/lib/core/projectTooling.d.ts +41 -0
  112. package/types/lib/core/projectTooling.d.ts.map +1 -0
  113. package/types/lib/core/propertySanitizer.d.ts +26 -0
  114. package/types/lib/core/propertySanitizer.d.ts.map +1 -1
  115. package/types/lib/ecosystems/cppEvidence.d.ts +1 -1
  116. package/types/lib/ecosystems/golem.d.ts +81 -1
  117. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  118. package/types/lib/ecosystems/gradleutils.d.ts +10 -8
  119. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  120. package/types/lib/ecosystems/parsers-dotnet.d.ts +40 -4
  121. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  122. package/types/lib/ecosystems/parsers-go.d.ts +85 -13
  123. package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
  124. package/types/lib/ecosystems/parsers-jvm.d.ts +28 -2
  125. package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
  126. package/types/lib/ecosystems/parsers-misc.d.ts +18 -58
  127. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  128. package/types/lib/ecosystems/parsers-python.d.ts +4 -3
  129. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  130. package/types/lib/ecosystems/pythonutils.d.ts +1 -1
  131. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  132. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  133. package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -1
  134. package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
  135. package/types/lib/evinser/evinser.d.ts +25 -0
  136. package/types/lib/evinser/evinser.d.ts.map +1 -1
  137. package/types/lib/evinser/swiftsem.d.ts +10 -0
  138. package/types/lib/evinser/swiftsem.d.ts.map +1 -1
  139. package/types/lib/inventory/agentSkillSpec.d.ts +13 -0
  140. package/types/lib/inventory/agentSkillSpec.d.ts.map +1 -0
  141. package/types/lib/inventory/apkInstalledDb.d.ts +42 -0
  142. package/types/lib/inventory/apkInstalledDb.d.ts.map +1 -0
  143. package/types/lib/inventory/atomUtils.d.ts +14 -0
  144. package/types/lib/inventory/atomUtils.d.ts.map +1 -1
  145. package/types/lib/inventory/cacheDir.d.ts +12 -4
  146. package/types/lib/inventory/cacheDir.d.ts.map +1 -1
  147. package/types/lib/inventory/certificates.d.ts +205 -0
  148. package/types/lib/inventory/certificates.d.ts.map +1 -0
  149. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  150. package/types/lib/inventory/deps.d.ts +2 -1
  151. package/types/lib/inventory/deps.d.ts.map +1 -1
  152. package/types/lib/inventory/display.d.ts +18 -0
  153. package/types/lib/inventory/display.d.ts.map +1 -1
  154. package/types/lib/inventory/dosai.d.ts +145 -6
  155. package/types/lib/inventory/dosai.d.ts.map +1 -1
  156. package/types/lib/inventory/dynamic.d.ts +6 -1
  157. package/types/lib/inventory/dynamic.d.ts.map +1 -1
  158. package/types/lib/inventory/envcontext.d.ts +40 -2
  159. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  160. package/types/lib/inventory/evidenceUtils.d.ts +29 -10
  161. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  162. package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
  163. package/types/lib/inventory/goBuildInfo.d.ts +10 -0
  164. package/types/lib/inventory/goBuildInfo.d.ts.map +1 -0
  165. package/types/lib/inventory/hbom.d.ts.map +1 -1
  166. package/types/lib/inventory/hbomAnalysis.d.ts +10 -0
  167. package/types/lib/inventory/hbomAnalysis.d.ts.map +1 -1
  168. package/types/lib/inventory/hostTopology.d.ts.map +1 -1
  169. package/types/lib/inventory/osPackagePurl.d.ts +99 -0
  170. package/types/lib/inventory/osPackagePurl.d.ts.map +1 -0
  171. package/types/lib/inventory/osPackageResolver.d.ts +6 -2
  172. package/types/lib/inventory/osPackageResolver.d.ts.map +1 -1
  173. package/types/lib/inventory/osinfo.d.ts +31 -6
  174. package/types/lib/inventory/osinfo.d.ts.map +1 -1
  175. package/types/lib/inventory/osqueryTransform.d.ts +51 -0
  176. package/types/lib/inventory/osqueryTransform.d.ts.map +1 -1
  177. package/types/lib/inventory/phpBuildInfo.d.ts +142 -0
  178. package/types/lib/inventory/phpBuildInfo.d.ts.map +1 -0
  179. package/types/lib/inventory/phpHostProbe.d.ts +35 -0
  180. package/types/lib/inventory/phpHostProbe.d.ts.map +1 -0
  181. package/types/lib/inventory/rootfsCerts.d.ts +27 -0
  182. package/types/lib/inventory/rootfsCerts.d.ts.map +1 -0
  183. package/types/lib/inventory/source.d.ts +28 -0
  184. package/types/lib/inventory/source.d.ts.map +1 -1
  185. package/types/lib/inventory/spdx.d.ts +15 -0
  186. package/types/lib/inventory/spdx.d.ts.map +1 -1
  187. package/types/lib/inventory/traceRunner.d.ts +4 -1
  188. package/types/lib/inventory/traceRunner.d.ts.map +1 -1
  189. package/types/lib/inventory/tracedCrypto.d.ts +22 -0
  190. package/types/lib/inventory/tracedCrypto.d.ts.map +1 -1
  191. package/types/lib/managers/binary.d.ts +10 -0
  192. package/types/lib/managers/binary.d.ts.map +1 -1
  193. package/types/lib/managers/docker.d.ts +12 -0
  194. package/types/lib/managers/docker.d.ts.map +1 -1
  195. package/types/lib/parsers/email.d.ts +19 -0
  196. package/types/lib/parsers/email.d.ts.map +1 -0
  197. package/types/lib/parsers/largeJson.d.ts +13 -0
  198. package/types/lib/parsers/largeJson.d.ts.map +1 -1
  199. package/types/lib/server/server.d.ts +8 -0
  200. package/types/lib/server/server.d.ts.map +1 -1
  201. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  202. package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -1
  203. package/types/lib/stages/postgen/ruleEngine.d.ts +7 -4
  204. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  205. package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
  206. package/types/lib/stages/pregen/pregen.d.ts +14 -1
  207. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
  208. package/types/lib/validator/bomValidator.d.ts.map +1 -1
@@ -5,6 +5,7 @@ import process from "node:process";
5
5
  import { Purl } from "@cdxgen/cdx-purl";
6
6
 
7
7
  import { DEBUG_MODE, readEnvironmentVariable } from "../core/activity.js";
8
+ import { recordDegradation } from "../core/buildLedger.js";
8
9
  import { PROJECT_TYPE_ALIASES } from "../core/env.js";
9
10
  import {
10
11
  getAllFiles,
@@ -15,6 +16,7 @@ import {
15
16
  safeRmSync,
16
17
  safeWriteSync,
17
18
  } from "../core/fs.js";
19
+ import { isProjectToolingAllowed } from "../core/projectTooling.js";
18
20
  import { hasCppSources } from "../ecosystems/cppEvidence.js";
19
21
  import {
20
22
  createIncludeAttributor,
@@ -23,11 +25,11 @@ import {
23
25
  import {
24
26
  analyzeGolemProject,
25
27
  collectGolemEvidence,
28
+ collectGolemServices,
26
29
  isGolemGoLanguage,
27
30
  } from "../ecosystems/golem.js";
28
31
  import {
29
32
  collectGradleDependencies,
30
- getGradleCommand,
31
33
  getMavenCommand,
32
34
  } from "../ecosystems/gradleutils.js";
33
35
  import {
@@ -52,6 +54,7 @@ import {
52
54
  isScalasemLanguage,
53
55
  } from "../ecosystems/scalasem.js";
54
56
  import {
57
+ atomBuildToolArgs,
55
58
  atomCompileCommandsArgs,
56
59
  buildAtomCommandEnv,
57
60
  executeAtom,
@@ -77,7 +80,10 @@ import {
77
80
  collectDosaiServicesFromMethods,
78
81
  createDosaiDataFlowSlice,
79
82
  createDosaiMethodsSlice,
80
- dosaiRunWasStopped,
83
+ dosaiFailureReported,
84
+ dosaiOmittedSections,
85
+ dosaiReportFileOmittedSections,
86
+ dosaiSectionsCdxgenReads,
81
87
  isDosaiDotnetLanguage,
82
88
  normalizeDosaiServiceMap,
83
89
  persistDosaiSemanticsReport,
@@ -401,9 +407,25 @@ export async function catalogMavenDeps(
401
407
  options = {},
402
408
  ) {
403
409
  let jarNSMapping = readNamespaceMap(dirPath, options);
404
- if (!jarNSMapping) {
410
+ if (!jarNSMapping && !isProjectToolingAllowed(options)) {
411
+ // `mvn dependency:copy-dependencies` evaluates the project's build and
412
+ // its `.mvn` configuration, so only the local repository is read.
413
+ console.log(
414
+ options.withDeepJarCollector
415
+ ? "Skipping 'mvn dependency:copy-dependencies' since dependency installation is disabled. Only the jars already in the local Maven repository are used."
416
+ : "Skipping 'mvn dependency:copy-dependencies' since dependency installation is disabled. Pass --with-deep-jar-collector to use the jars already in the local Maven repository.",
417
+ );
418
+ if (options.withDeepJarCollector) {
419
+ jarNSMapping = await collectMvnDependencies(
420
+ undefined,
421
+ undefined,
422
+ true,
423
+ true,
424
+ );
425
+ }
426
+ } else if (!jarNSMapping) {
405
427
  console.log("About to collect jar dependencies for the path", dirPath);
406
- const mavenCmd = getMavenCommand(dirPath, dirPath);
428
+ const mavenCmd = getMavenCommand(dirPath, dirPath, options);
407
429
  // collect all jars including from the cache if data-flow mode is enabled
408
430
  jarNSMapping = await collectMvnDependencies(
409
431
  mavenCmd,
@@ -457,10 +479,10 @@ export async function catalogGradleDeps(
457
479
  console.log(
458
480
  "About to collect jar dependencies from the gradle cache. This would take a while ...",
459
481
  );
460
- const gradleCmd = getGradleCommand(dirPath, dirPath);
461
- // collect all jars including from the cache if data-flow mode is enabled
482
+ // Only the Gradle cache is read, so no Gradle command is resolved: that
483
+ // would probe the project's own gradlew.
462
484
  jarNSMapping = await collectGradleDependencies(
463
- gradleCmd,
485
+ undefined,
464
486
  dirPath,
465
487
  false,
466
488
  true,
@@ -645,9 +667,9 @@ export async function createSlice(
645
667
  resolve(slicesFile),
646
668
  options,
647
669
  );
648
- // A stopped run has already said why; it says nothing about whether the
649
- // project is supported (issue 4438).
650
- if (!sliceResult && !dosaiRunWasStopped(resolve(slicesFile))) {
670
+ // A stopped run, or one the .NET host refused to start, has already said
671
+ // why; it says nothing about whether the project is supported (issue 4438).
672
+ if (!sliceResult && !dosaiFailureReported(resolve(slicesFile))) {
651
673
  console.warn(
652
674
  `Unable to generate ${sliceType} slice using dosai. Check if this is a supported .NET project.`,
653
675
  );
@@ -658,6 +680,13 @@ export async function createSlice(
658
680
  slicesFile,
659
681
  };
660
682
  }
683
+ const buildToolArgs = atomBuildToolArgs(language, options);
684
+ if (!buildToolArgs) {
685
+ console.log(
686
+ `Skipping the ${sliceType} slice for ${resolve(filePath)} since dependency installation is disabled and the installed atom cannot be stopped from running Maven. Upgrade atom to slice this project without its build.`,
687
+ );
688
+ return { tempDir: sliceOutputDir, tempDirOwned };
689
+ }
661
690
  console.log(
662
691
  `Creating ${sliceType} slice for ${resolve(filePath)}. Please wait ...`,
663
692
  );
@@ -706,6 +735,7 @@ export async function createSlice(
706
735
  args.push(readEnvironmentVariable("ATOM_SLICE_DEPTH"));
707
736
  }
708
737
  args.push(...atomCompileCommandsArgs(filePath, language, options));
738
+ args.push(...buildToolArgs);
709
739
  args.push(resolve(filePath));
710
740
  const atomExcludeEnv = buildAtomCommandEnv(options, language);
711
741
  // Execute atom
@@ -1143,6 +1173,49 @@ export function mergeServiceDefinitions(target, source) {
1143
1173
  return target;
1144
1174
  }
1145
1175
 
1176
+ /**
1177
+ * Collect the services of a Go project for the golem evidence path (#4498).
1178
+ *
1179
+ * Two sources are kept and merged by service name, the way the Scala path
1180
+ * merges a user-provided spec into its report's services. They roll services
1181
+ * up differently — golem per server, OpenAPI per path and method — so a
1182
+ * route both name appears under both entries:
1183
+ *
1184
+ * - the golem report itself, whose `services[]` section rolls the registered
1185
+ * routes up per server (a gin engine, a net/http listener) with methods and
1186
+ * frameworks, and the outbound hosts it calls into `kind: "external"`
1187
+ * services flagged as trust-boundary crossings;
1188
+ * - an OpenAPI document, either the one `--openapi-spec-file` names or the
1189
+ * `openapi.json` an earlier `atom-tools convert -t go` left in the repo,
1190
+ * which is reused when present so `golem`/`atom-tools` and `cdxgen` can be
1191
+ * run as separate steps.
1192
+ *
1193
+ * @param {object|null|undefined} golemReport Parsed golem analysis report,
1194
+ * when one was produced; a missing or `null` report adds no services.
1195
+ * @param {string} dirPath Project directory the report was produced from.
1196
+ * @param {Object} options Command line options.
1197
+ * @param {Object} [servicesMap={}] Service map to populate.
1198
+ * @returns {Object} The service map
1199
+ */
1200
+ export function collectGoServices(
1201
+ golemReport,
1202
+ dirPath,
1203
+ options = {},
1204
+ servicesMap = {},
1205
+ ) {
1206
+ servicesMap = collectGolemServices(golemReport, servicesMap);
1207
+ const openapiSpecFile = resolve(
1208
+ dirPath,
1209
+ options.openapiSpecFile || "openapi.json",
1210
+ );
1211
+ if (safeExistsSync(openapiSpecFile)) {
1212
+ const openapiServices = {};
1213
+ detectServicesFromOpenAPI("go", openapiSpecFile, openapiServices);
1214
+ mergeServiceDefinitions(servicesMap, openapiServices);
1215
+ }
1216
+ return servicesMap;
1217
+ }
1218
+
1146
1219
  /**
1147
1220
  * Function to analyze the project
1148
1221
  *
@@ -1183,6 +1256,7 @@ export async function analyzeProject(dbObjMap, options) {
1183
1256
 
1184
1257
  if (isGolemGoLanguage(language)) {
1185
1258
  const golemReport = analyzeGolemProject(dirPath, options);
1259
+ servicesMap = collectGoServices(golemReport, dirPath, options, servicesMap);
1186
1260
  if (golemReport && Object.keys(golemReport).length) {
1187
1261
  const golemEvidence = collectGolemEvidence(golemReport, [
1188
1262
  bomJson.metadata?.component,
@@ -1329,24 +1403,58 @@ export async function analyzeProject(dbObjMap, options) {
1329
1403
  options.withDataFlow = true;
1330
1404
  options.includeCrypto = true;
1331
1405
  }
1406
+ let needsUsagesRun = true;
1332
1407
  if (
1333
1408
  options.usagesSlicesFile &&
1334
1409
  usableSlicesFile(options.usagesSlicesFile)
1335
1410
  ) {
1336
1411
  usageSlice = readDosaiMethodsReport(options.usagesSlicesFile);
1337
1412
  usagesSlicesFile = options.usagesSlicesFile;
1413
+ needsUsagesRun = false;
1414
+ // A user-supplied usages slice is used as given. When it was written
1415
+ // with --sections and left out a section cdxgen reads, the evidence
1416
+ // lacks that input; persistDosaiSemanticsReport reports separately
1417
+ // that the persisted report is partial.
1418
+ const missingInputs = dosaiSectionsCdxgenReads(
1419
+ dosaiOmittedSections(usageSlice),
1420
+ );
1421
+ if (missingInputs.length) {
1422
+ recordDegradation("dosai.usages.trimmed-report", {
1423
+ ecosystem: "csharp",
1424
+ tool: "dosai",
1425
+ impact: "components",
1426
+ command: `usages ${usagesSlicesFile}`,
1427
+ detail: `The supplied dosai methods report was written without sections cdxgen reads: ${missingInputs.join(", ")}. The evidence from those sections is missing. Run dosai methods without --sections, or with every section cdxgen reads.`,
1428
+ });
1429
+ }
1338
1430
  } else if (usableSlicesFile(options.depsSlicesFile)) {
1339
1431
  // createCsharpBom already ran dosai methods on this tree for --deep runs
1340
1432
  // and wrote the deps slice. For dosai, that output IS the usages slice,
1341
- // so reuse it instead of invoking dosai a second time.
1342
- if (DEBUG_MODE) {
1343
- console.log(
1344
- `Reusing the dosai deps slices file "${options.depsSlicesFile}" for the usages analysis.`,
1345
- );
1433
+ // so reuse it instead of invoking dosai a second time - unless it was
1434
+ // written with --sections (a --deep run that performs no evidence
1435
+ // analysis trims it): evinse persists this file as the full native
1436
+ // report, so it must not build on a trimmed one and dosai runs again.
1437
+ const omittedSections = dosaiReportFileOmittedSections(
1438
+ options.depsSlicesFile,
1439
+ );
1440
+ if (omittedSections.length) {
1441
+ if (DEBUG_MODE) {
1442
+ console.log(
1443
+ `The dosai deps slices file "${options.depsSlicesFile}" omits sections (${omittedSections.join(", ")}); running dosai for the full usages report.`,
1444
+ );
1445
+ }
1446
+ } else {
1447
+ if (DEBUG_MODE) {
1448
+ console.log(
1449
+ `Reusing the dosai deps slices file "${options.depsSlicesFile}" for the usages analysis.`,
1450
+ );
1451
+ }
1452
+ usageSlice = readDosaiMethodsReport(options.depsSlicesFile);
1453
+ usagesSlicesFile = options.depsSlicesFile;
1454
+ needsUsagesRun = false;
1346
1455
  }
1347
- usageSlice = readDosaiMethodsReport(options.depsSlicesFile);
1348
- usagesSlicesFile = options.depsSlicesFile;
1349
- } else {
1456
+ }
1457
+ if (needsUsagesRun) {
1350
1458
  retMap = await createSlice(language, dirPath, "usages", options);
1351
1459
  if (retMap?.slicesFile && safeExistsSync(retMap.slicesFile)) {
1352
1460
  usageSlice = readDosaiMethodsReport(retMap.slicesFile);
@@ -20,6 +20,7 @@ import {
20
20
  safeRmSync,
21
21
  safeSpawnSync,
22
22
  } from "../core/fs.js";
23
+ import { isProjectToolingAllowed } from "../ecosystems/gradleutils.js";
23
24
  import { splitCommandArgs } from "../ecosystems/parsers-js.js";
24
25
  import { collectSwiftProductionTargets } from "../ecosystems/parsers-misc.js";
25
26
  import { runSwiftCommand } from "../inventory/envcontext.js";
@@ -585,6 +586,45 @@ export function sanitizeSwiftModuleArgs(tokens) {
585
586
  return args;
586
587
  }
587
588
 
589
+ // Compiler arguments that load code into the compiler or the SourceKit
590
+ // service: macro plugins and their search paths, and the clang arguments
591
+ // (`-Xcc -fplugin=...`) behind -Xcc.
592
+ const SWIFT_CODE_LOADING_ARGS = new Set([
593
+ "-plugin-path",
594
+ "-external-plugin-path",
595
+ "-load-plugin-executable",
596
+ "-load-plugin-library",
597
+ "-in-process-plugin-server-path",
598
+ "-Xcc",
599
+ ]);
600
+
601
+ /**
602
+ * Remove the arguments that would make SourceKit load code named by the
603
+ * project. A `.build` directory that cdxgen did not create is project content,
604
+ * so the compiler arguments read from it are cleaned this way whenever the
605
+ * project's own tooling may not run.
606
+ *
607
+ * @param {Array<String>} args Sanitized compiler arguments
608
+ * @returns {Array<String>} Arguments without plugin and clang pass-through flags
609
+ */
610
+ export function withoutSwiftPluginArgs(args) {
611
+ const kept = [];
612
+ for (let i = 0; i < (args || []).length; i++) {
613
+ const token = args[i];
614
+ if (SWIFT_CODE_LOADING_ARGS.has(token)) {
615
+ i++;
616
+ continue;
617
+ }
618
+ // -Xfrontend <plugin flag> -Xfrontend <value>
619
+ if (token === "-Xfrontend" && SWIFT_CODE_LOADING_ARGS.has(args[i + 1])) {
620
+ i += 3;
621
+ continue;
622
+ }
623
+ kept.push(token);
624
+ }
625
+ return kept;
626
+ }
627
+
588
628
  /**
589
629
  * Read the sources listed in a response or file-list file (`@<path>`), one
590
630
  * path per line.
@@ -2198,7 +2238,16 @@ export function createSemanticsSlices(basePath, options) {
2198
2238
  if (!pkgSwiftFiles.length) {
2199
2239
  return undefined;
2200
2240
  }
2201
- const toolchain = resolveSwiftToolchain(basePath);
2241
+ // A SwiftPM manifest is a Swift program and the verbose build compiles the
2242
+ // project, so both belong to the dependency-tooling surface disabled by
2243
+ // --no-install-deps and the pre-build lifecycle. Evidence then comes from
2244
+ // the .build artifacts a previous build left and import declarations.
2245
+ const toolingAllowed = isProjectToolingAllowed(options);
2246
+ // swiftly picks a toolchain from the project's .swift-version, so the
2247
+ // toolchain is resolved outside the project when its tooling may not run
2248
+ const toolchain = resolveSwiftToolchain(
2249
+ toolingAllowed ? basePath : getTmpDir(),
2250
+ );
2202
2251
  const sourcekittenEnv = sourcekittenEnvForToolchain(toolchain);
2203
2252
  // Without a working sourcekitten, the evidence is limited to the import
2204
2253
  // declarations of the modules the build describes
@@ -2211,13 +2260,22 @@ export function createSemanticsSlices(basePath, options) {
2211
2260
  : "",
2212
2261
  );
2213
2262
  }
2263
+ if (!toolingAllowed) {
2264
+ console.log(
2265
+ "Skipping 'swift package dump-package' and the swift build for evidence since dependency installation is disabled. Evidence is limited to the build artifacts already in the project, without their compiler plugins.",
2266
+ );
2267
+ }
2214
2268
  // Package.swift contains the root targets, including which ones are tests
2215
- const packageMetadata = parseDumpPackage(dumpPackage(basePath));
2269
+ const packageMetadata = toolingAllowed
2270
+ ? parseDumpPackage(dumpPackage(basePath))
2271
+ : undefined;
2216
2272
  const userCompilerArgs = process?.env?.SWIFT_COMPILER_ARGS;
2217
2273
  const userSdkArgs = process?.env?.SWIFT_SDK_ARGS;
2218
2274
  // A clean verbose debug build reveals the compiler arguments of every module
2219
2275
  const buildOutput =
2220
- userCompilerArgs && userSdkArgs ? undefined : verboseBuild(basePath);
2276
+ !toolingAllowed || (userCompilerArgs && userSdkArgs)
2277
+ ? undefined
2278
+ : verboseBuild(basePath);
2221
2279
  const buildPlan = collectSwiftBuildPlan(
2222
2280
  basePath,
2223
2281
  buildOutput,
@@ -2267,7 +2325,9 @@ export function createSemanticsSlices(basePath, options) {
2267
2325
  moduleArgs[moduleName] = [
2268
2326
  ...(userCompilerArgs
2269
2327
  ? splitSwiftCommandArgs(`${userCompilerArgs} ${userSdkArgs || ""}`)
2270
- : amodule.args),
2328
+ : toolingAllowed
2329
+ ? amodule.args
2330
+ : withoutSwiftPluginArgs(amodule.args)),
2271
2331
  "-module-cache-path",
2272
2332
  moduleCachePath,
2273
2333
  ];
@@ -2284,12 +2344,15 @@ export function createSemanticsSlices(basePath, options) {
2284
2344
  }
2285
2345
  // Sources that belong to no known module, for example when the build
2286
2346
  // failed, are indexed with the merged arguments of the root modules
2287
- const fallbackArgs = userCompilerArgs
2347
+ let fallbackArgs = userCompilerArgs
2288
2348
  ? splitSwiftCommandArgs(`${userCompilerArgs} ${userSdkArgs || ""}`)
2289
2349
  : extractCompilerParamsFromBuild(
2290
2350
  buildOutput,
2291
2351
  packageMetadata?.rootModules,
2292
2352
  ).compilerArgs;
2353
+ if (!userCompilerArgs && !toolingAllowed) {
2354
+ fallbackArgs = withoutSwiftPluginArgs(fallbackArgs);
2355
+ }
2293
2356
  // Sources of test and dependency modules are never indexed
2294
2357
  const plannedSources = new Set();
2295
2358
  for (const amodule of Object.values(buildPlan.modules)) {
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Whether the frontmatter of a SKILL.md satisfies the open Agent Skills
3
+ * constraints (agentskills.io): a name of 1-64 lowercase letters, digits and
4
+ * hyphens, and a description of 1-1024 characters.
5
+ *
6
+ * @param {{name?: unknown, description?: unknown}|undefined} metadata Frontmatter values
7
+ * @returns {boolean} true when both constraints hold
8
+ */
9
+ export function isAgentSkillSpecValid(metadata) {
10
+ return (
11
+ typeof metadata?.name === "string" &&
12
+ /^[a-z0-9-]{1,64}$/u.test(metadata.name) &&
13
+ typeof metadata?.description === "string" &&
14
+ metadata.description.length >= 1 &&
15
+ metadata.description.length <= 1024
16
+ );
17
+ }
@@ -0,0 +1,243 @@
1
+ // Bounded, read-only reader of the apk installed database.
2
+ //
3
+ // osquery has no apk table, so an Alpine, Wolfi or Chainguard host would have no
4
+ // OS package inventory in an OBOM, and the PHP host probe needs the same
5
+ // database to tell which PHP files a package owns. Both read it through here, so
6
+ // what the OBOM lists and what the probe treats as owned follow one parser: the
7
+ // same file, location order, caps and record rules.
8
+ //
9
+ // The database is `/lib/apk/db/installed`. A usr-merged system keeps it at
10
+ // `/usr/lib/apk/db/installed` (and `/lib` is a link to `/usr/lib`, so the two
11
+ // names are one file), and Alpaquita keeps it under `/var/lib/apk/db`. The first
12
+ // of those that exists is the database. One that exists but cannot be read
13
+ // leaves the packages unknown: it is reported as unreadable, never read in part
14
+ // and never skipped for the next name.
15
+ //
16
+ // A record is a run of `<letter>:<value>` lines ended by a blank line. P: names
17
+ // the package, V: its version, A: its architecture, o: its origin (the source
18
+ // package), m: its maintainer, L: its licence, U: its home page and T: its
19
+ // description. F: names a directory and each R: after it a file in that
20
+ // directory. A record may start with C: (Alpine) or P: (Wolfi).
21
+
22
+ import { statSync } from "node:fs";
23
+ import { join } from "node:path";
24
+ import process from "node:process";
25
+
26
+ import { isDryRun, recordActivity } from "../core/activity.js";
27
+ import { safeExistsSync, safeReadFileSync } from "../core/fs.js";
28
+ import { thoughtLog } from "../core/logger.js";
29
+
30
+ /** The database, or the packages it lists, past these is unreadable rather than read in part. */
31
+ export const MAX_APK_DATABASE_BYTES = 64 * 1024 * 1024;
32
+ export const MAX_APK_PACKAGES = 100000;
33
+
34
+ /** Where apk keeps the installed database, relative to the root, in the order they are tried. */
35
+ export const APK_INSTALLED_DB_PATHS = [
36
+ "lib/apk/db/installed",
37
+ "usr/lib/apk/db/installed",
38
+ "var/lib/apk/db/installed",
39
+ ];
40
+
41
+ // No field of a package record is longer than this once read.
42
+ const MAX_FIELD_LENGTH = 4096;
43
+
44
+ const FIELD_NAMES = {
45
+ P: "name",
46
+ V: "version",
47
+ A: "arch",
48
+ o: "origin",
49
+ m: "maintainer",
50
+ L: "license",
51
+ U: "url",
52
+ T: "description",
53
+ };
54
+
55
+ function readBounded(file, maxBytes) {
56
+ try {
57
+ const stats = statSync(file);
58
+ if (!stats.isFile() || stats.size > maxBytes) {
59
+ return undefined;
60
+ }
61
+ } catch {
62
+ return undefined;
63
+ }
64
+ const text = safeReadFileSync(file, "utf-8");
65
+ // A file that grew between the stat and the read is over the cap too.
66
+ return typeof text === "string" && text.length <= maxBytes ? text : undefined;
67
+ }
68
+
69
+ function newRecord() {
70
+ return { fields: {}, fileCount: 0, files: [], touched: false };
71
+ }
72
+
73
+ /**
74
+ * Parse the text of an installed database.
75
+ *
76
+ * @param {string} text Database text
77
+ * @param {Set<string>|undefined} wantedFiles Absolute paths whose owner is wanted
78
+ * @param {number} maxPackages Most packages to accept
79
+ * @returns {{packages: Object[], malformed: number, overflow: boolean}}
80
+ */
81
+ function parseDatabase(text, wantedFiles, maxPackages) {
82
+ const packages = [];
83
+ let malformed = 0;
84
+ let overflow = false;
85
+ let record = newRecord();
86
+ let directory = "";
87
+ const finishRecord = () => {
88
+ const { fields, fileCount, files, touched } = record;
89
+ record = newRecord();
90
+ directory = "";
91
+ if (!touched) {
92
+ return;
93
+ }
94
+ // A package with no name or no version cannot be named by a purl.
95
+ if (!fields.name || !fields.version) {
96
+ malformed++;
97
+ return;
98
+ }
99
+ packages.push({ ...fields, fileCount, files });
100
+ if (packages.length > maxPackages) {
101
+ overflow = true;
102
+ }
103
+ };
104
+ let position = 0;
105
+ while (position <= text.length && !overflow) {
106
+ let end = text.indexOf("\n", position);
107
+ if (end === -1) {
108
+ end = text.length;
109
+ }
110
+ let line = text.slice(position, end);
111
+ position = end + 1;
112
+ if (line.endsWith("\r")) {
113
+ line = line.slice(0, -1);
114
+ }
115
+ if (line === "") {
116
+ finishRecord();
117
+ continue;
118
+ }
119
+ if (line.length < 2 || line[1] !== ":") {
120
+ continue;
121
+ }
122
+ const letter = line[0];
123
+ const value = line.slice(2);
124
+ if (Object.hasOwn(FIELD_NAMES, letter)) {
125
+ record.touched = true;
126
+ if (value) {
127
+ record.fields[FIELD_NAMES[letter]] =
128
+ value.length > MAX_FIELD_LENGTH
129
+ ? value.slice(0, MAX_FIELD_LENGTH)
130
+ : value;
131
+ }
132
+ } else if (letter === "F") {
133
+ record.touched = true;
134
+ directory = value;
135
+ } else if (letter === "R") {
136
+ record.touched = true;
137
+ record.fileCount++;
138
+ if (wantedFiles) {
139
+ const file = `/${directory ? `${directory}/` : ""}${value}`;
140
+ if (wantedFiles.has(file)) {
141
+ record.files.push(file);
142
+ }
143
+ }
144
+ }
145
+ }
146
+ if (!overflow) {
147
+ finishRecord();
148
+ }
149
+ return { packages, malformed, overflow };
150
+ }
151
+
152
+ /**
153
+ * Read the packages of the apk installed database.
154
+ *
155
+ * @param {Object} [options] Read options
156
+ * @param {string} [options.rootPath="/"] Directory the database paths hang off;
157
+ * tests point it at a built tree
158
+ * @param {string} [options.databaseFile] Read this file instead of looking
159
+ * under `rootPath`
160
+ * @param {Set<string>} [options.wantedFiles] Absolute paths whose owner is
161
+ * wanted. A package comes back with the ones it owns in `files`; the other
162
+ * files of a package are only counted, so a large database stays small.
163
+ * @param {number} [options.maxBytes] Largest database to read, 64 MiB by default
164
+ * @param {number} [options.maxPackages] Most packages to accept, 100000 by default
165
+ * @param {string} [options.platform] `linux` by default for the process; an apk
166
+ * database exists on Linux only
167
+ * @returns {{state: string, path?: string, packages: Object[], malformed: number}}
168
+ * `state` is `ok`, `absent` (no database, or not Linux), `unreadable` (a
169
+ * database that could not be read in full) or `skipped` (a dry run).
170
+ * `malformed` counts the records left out for having no name or version.
171
+ * Each package has `name`, `version` and `fileCount`, `files`, and, when the
172
+ * record has them, `arch`, `origin`, `maintainer`, `license`, `url` and
173
+ * `description`.
174
+ */
175
+ export function readApkInstalledDb(options = {}) {
176
+ const result = (state, extra = {}) => ({
177
+ state,
178
+ packages: [],
179
+ malformed: 0,
180
+ ...extra,
181
+ });
182
+ const platform = options.platform || process.platform;
183
+ if (platform !== "linux") {
184
+ return result("absent");
185
+ }
186
+ const rootPath = options.rootPath || "/";
187
+ if (isDryRun) {
188
+ recordActivity({
189
+ capability: "apk-installed-db",
190
+ kind: "read",
191
+ reason:
192
+ "Dry run mode blocks reading the apk installed database, which lives outside the project.",
193
+ status: "blocked",
194
+ target: options.databaseFile || rootPath,
195
+ });
196
+ return result("skipped");
197
+ }
198
+ const candidates = options.databaseFile
199
+ ? [options.databaseFile]
200
+ : APK_INSTALLED_DB_PATHS.map((location) => join(rootPath, location));
201
+ const file = candidates.find((candidate) => safeExistsSync(candidate));
202
+ if (!file) {
203
+ return result("absent");
204
+ }
205
+ const unreadable = (reason) => {
206
+ thoughtLog(`The apk installed database ${file} was not read: ${reason}.`);
207
+ recordActivity({
208
+ capability: "apk-installed-db",
209
+ kind: "read",
210
+ reason: `The apk installed database ${file} could not be read: ${reason}.`,
211
+ status: "failed",
212
+ target: file,
213
+ });
214
+ return result("unreadable", { path: file });
215
+ };
216
+ const maxBytes = options.maxBytes ?? MAX_APK_DATABASE_BYTES;
217
+ const text = readBounded(file, maxBytes);
218
+ if (text === undefined) {
219
+ return unreadable(`it is not a file of at most ${maxBytes} bytes`);
220
+ }
221
+ const maxPackages = options.maxPackages ?? MAX_APK_PACKAGES;
222
+ const { packages, malformed, overflow } = parseDatabase(
223
+ text,
224
+ options.wantedFiles,
225
+ maxPackages,
226
+ );
227
+ if (overflow) {
228
+ return unreadable(`it lists more than ${maxPackages} packages`);
229
+ }
230
+ if (malformed) {
231
+ thoughtLog(
232
+ `${malformed} record(s) of the apk installed database ${file} have no name or no version and were left out.`,
233
+ );
234
+ }
235
+ recordActivity({
236
+ capability: "apk-installed-db",
237
+ kind: "read",
238
+ reason: `Read ${packages.length} package(s) from the apk installed database ${file}.`,
239
+ status: "completed",
240
+ target: file,
241
+ });
242
+ return result("ok", { path: file, packages, malformed });
243
+ }
@@ -30,6 +30,7 @@ import {
30
30
  import { TRACE_MODE } from "../core/logger.js";
31
31
  import { dirNameStr, isWin } from "../core/paths.js";
32
32
  import { formatDuration, reapProcessTree } from "../core/processTree.js";
33
+ import { isProjectToolingAllowed } from "../core/projectTooling.js";
33
34
  import { readJsonFile } from "../parsers/largeJson.js";
34
35
  import { cmakeBuildDirCandidates } from "./cmakeBuildDirs.js";
35
36
 
@@ -314,6 +315,38 @@ export function atomCompileCommandsArgs(src, language, options = {}) {
314
315
  return ["--frontend-args", `compile-commands=${database}`];
315
316
  }
316
317
 
318
+ /**
319
+ * The arguments that stop atom's Java frontend from running the scanned
320
+ * project's build. In its default delombok mode, atom runs
321
+ * `mvn dependency:build-classpath` inside a Maven project, which loads the
322
+ * project's `.mvn` configuration and build extensions, so delombok and
323
+ * dependency fetching are turned off whenever the project's own tooling may
324
+ * not run.
325
+ *
326
+ * @param {string} language atom `-l` value
327
+ * @param {Object} options CLI options
328
+ * @returns {string[]|undefined} Arguments to add to the atom command, or
329
+ * undefined when the installed atom cannot be told to leave the build alone
330
+ */
331
+ export function atomBuildToolArgs(language, options = {}) {
332
+ if (
333
+ `${language}`.toLowerCase() !== "java" ||
334
+ isProjectToolingAllowed(options)
335
+ ) {
336
+ return [];
337
+ }
338
+ const keys = atomFrontendArgKeys(language);
339
+ if (!keys.has("delombok-mode")) {
340
+ return undefined;
341
+ }
342
+ return [
343
+ "--frontend-args",
344
+ keys.has("fetch-dependencies")
345
+ ? "delombok-mode=no-delombok,fetch-dependencies=false"
346
+ : "delombok-mode=no-delombok",
347
+ ];
348
+ }
349
+
317
350
  // Absolute ceiling on atom's heap. Past this point a larger heap buys a
318
351
  // collector that runs less often rather than an analysis that succeeds where it
319
352
  // otherwise would not, while the reservation itself is what pushes a build