@socketsecurity/cli 1.1.101 → 1.1.151

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 (201) hide show
  1. package/CHANGELOG.md +277 -1
  2. package/README.md +9 -0
  3. package/bin/cli.js +33 -1
  4. package/dist/cli.js +9914 -5001
  5. package/dist/cli.js.map +1 -1
  6. package/dist/constants.js +8 -6
  7. package/dist/constants.js.map +1 -1
  8. package/dist/init.gradle +73 -0
  9. package/dist/manifest-scripts/maven-extension/coana-maven-extension.jar +0 -0
  10. package/dist/manifest-scripts/socket-facts.init.gradle +581 -0
  11. package/dist/manifest-scripts/socket-facts.plugin.scala +544 -0
  12. package/dist/shadow-npm-bin2.js +6 -6
  13. package/dist/shadow-npm-bin2.js.map +1 -1
  14. package/dist/shadow-npm-inject.js +10 -10
  15. package/dist/shadow-npm-inject.js.map +1 -1
  16. package/dist/shadow-pnpm-bin2.js +13 -13
  17. package/dist/shadow-pnpm-bin2.js.map +1 -1
  18. package/dist/shadow-yarn-bin.js +3 -3
  19. package/dist/shadow-yarn-bin.js.map +1 -1
  20. package/dist/socket-completion.bash +0 -0
  21. package/dist/tsconfig.dts.tsbuildinfo +1 -1
  22. package/dist/types/commands/audit-log/output-audit-log.d.mts.map +1 -1
  23. package/dist/types/commands/ci/fetch-default-org-slug.d.mts.map +1 -1
  24. package/dist/types/commands/ci/handle-ci.d.mts.map +1 -1
  25. package/dist/types/commands/config/handle-config-get.d.mts.map +1 -1
  26. package/dist/types/commands/config/handle-config-set.d.mts.map +1 -1
  27. package/dist/types/commands/config/output-config-set.d.mts.map +1 -1
  28. package/dist/types/commands/fix/cmd-fix.d.mts.map +1 -1
  29. package/dist/types/commands/fix/coana-fix.d.mts.map +1 -1
  30. package/dist/types/commands/fix/handle-fix.d.mts +1 -1
  31. package/dist/types/commands/fix/handle-fix.d.mts.map +1 -1
  32. package/dist/types/commands/fix/types.d.mts +1 -0
  33. package/dist/types/commands/fix/types.d.mts.map +1 -1
  34. package/dist/types/commands/manifest/bazel/bazel-cquery.d.mts +70 -0
  35. package/dist/types/commands/manifest/bazel/bazel-cquery.d.mts.map +1 -0
  36. package/dist/types/commands/manifest/bazel/bazel-pypi-discovery.d.mts +44 -0
  37. package/dist/types/commands/manifest/bazel/bazel-pypi-discovery.d.mts.map +1 -0
  38. package/dist/types/commands/manifest/bazel/bazel-pypi-parser.d.mts +46 -0
  39. package/dist/types/commands/manifest/bazel/bazel-pypi-parser.d.mts.map +1 -0
  40. package/dist/types/commands/manifest/bazel/bazel-query-runner.d.mts +66 -8
  41. package/dist/types/commands/manifest/bazel/bazel-query-runner.d.mts.map +1 -1
  42. package/dist/types/commands/manifest/bazel/bazel-repo-discovery.d.mts +69 -29
  43. package/dist/types/commands/manifest/bazel/bazel-repo-discovery.d.mts.map +1 -1
  44. package/dist/types/commands/manifest/bazel/bazel-workspace-walk.d.mts +18 -0
  45. package/dist/types/commands/manifest/bazel/bazel-workspace-walk.d.mts.map +1 -0
  46. package/dist/types/commands/manifest/bazel/cmd-manifest-bazel.d.mts +26 -0
  47. package/dist/types/commands/manifest/bazel/cmd-manifest-bazel.d.mts.map +1 -1
  48. package/dist/types/commands/manifest/bazel/extract_bazel_to_maven.d.mts +110 -7
  49. package/dist/types/commands/manifest/bazel/extract_bazel_to_maven.d.mts.map +1 -1
  50. package/dist/types/commands/manifest/bazel/extract_bazel_to_pypi.d.mts +20 -0
  51. package/dist/types/commands/manifest/bazel/extract_bazel_to_pypi.d.mts.map +1 -0
  52. package/dist/types/commands/manifest/cmd-manifest-auto.d.mts.map +1 -1
  53. package/dist/types/commands/manifest/cmd-manifest-gradle.d.mts.map +1 -1
  54. package/dist/types/commands/manifest/cmd-manifest-kotlin.d.mts.map +1 -1
  55. package/dist/types/commands/manifest/cmd-manifest-maven.d.mts +9 -0
  56. package/dist/types/commands/manifest/cmd-manifest-maven.d.mts.map +1 -0
  57. package/dist/types/commands/manifest/cmd-manifest-scala.d.mts.map +1 -1
  58. package/dist/types/commands/manifest/cmd-manifest.d.mts.map +1 -1
  59. package/dist/types/commands/manifest/convert-gradle-to-facts.d.mts +15 -0
  60. package/dist/types/commands/manifest/convert-gradle-to-facts.d.mts.map +1 -0
  61. package/dist/types/commands/manifest/convert-maven-to-facts.d.mts +15 -0
  62. package/dist/types/commands/manifest/convert-maven-to-facts.d.mts.map +1 -0
  63. package/dist/types/commands/manifest/convert-sbt-to-facts.d.mts +19 -0
  64. package/dist/types/commands/manifest/convert-sbt-to-facts.d.mts.map +1 -0
  65. package/dist/types/commands/manifest/convert_gradle_to_maven.d.mts +2 -1
  66. package/dist/types/commands/manifest/convert_gradle_to_maven.d.mts.map +1 -1
  67. package/dist/types/commands/manifest/detect-manifest-actions.d.mts +1 -0
  68. package/dist/types/commands/manifest/detect-manifest-actions.d.mts.map +1 -1
  69. package/dist/types/commands/manifest/generate_auto_manifest.d.mts +12 -1
  70. package/dist/types/commands/manifest/generate_auto_manifest.d.mts.map +1 -1
  71. package/dist/types/commands/manifest/parse-build-tool-opts.d.mts +9 -0
  72. package/dist/types/commands/manifest/parse-build-tool-opts.d.mts.map +1 -0
  73. package/dist/types/commands/manifest/run-manifest-facts.d.mts +24 -0
  74. package/dist/types/commands/manifest/run-manifest-facts.d.mts.map +1 -0
  75. package/dist/types/commands/manifest/scripts/assemble.d.mts +16 -0
  76. package/dist/types/commands/manifest/scripts/assemble.d.mts.map +1 -0
  77. package/dist/types/commands/manifest/scripts/build-tool.d.mts +3 -0
  78. package/dist/types/commands/manifest/scripts/build-tool.d.mts.map +1 -0
  79. package/dist/types/commands/manifest/scripts/facts.d.mts +46 -0
  80. package/dist/types/commands/manifest/scripts/facts.d.mts.map +1 -0
  81. package/dist/types/commands/manifest/scripts/records.d.mts +66 -0
  82. package/dist/types/commands/manifest/scripts/records.d.mts.map +1 -0
  83. package/dist/types/commands/manifest/scripts/resolution-report-gradle.d.mts +10 -0
  84. package/dist/types/commands/manifest/scripts/resolution-report-gradle.d.mts.map +1 -0
  85. package/dist/types/commands/manifest/scripts/resolution-report-ivy.d.mts +7 -0
  86. package/dist/types/commands/manifest/scripts/resolution-report-ivy.d.mts.map +1 -0
  87. package/dist/types/commands/manifest/scripts/resolution-report-maven.d.mts +8 -0
  88. package/dist/types/commands/manifest/scripts/resolution-report-maven.d.mts.map +1 -0
  89. package/dist/types/commands/manifest/scripts/resolution-report-render.d.mts +43 -0
  90. package/dist/types/commands/manifest/scripts/resolution-report-render.d.mts.map +1 -0
  91. package/dist/types/commands/manifest/scripts/resolution-report.d.mts +18 -0
  92. package/dist/types/commands/manifest/scripts/resolution-report.d.mts.map +1 -0
  93. package/dist/types/commands/manifest/scripts/run.d.mts +43 -0
  94. package/dist/types/commands/manifest/scripts/run.d.mts.map +1 -0
  95. package/dist/types/commands/manifest/scripts/sidecar.d.mts +27 -0
  96. package/dist/types/commands/manifest/scripts/sidecar.d.mts.map +1 -0
  97. package/dist/types/commands/manifest/setup-manifest-config.d.mts.map +1 -1
  98. package/dist/types/commands/organization/fetch-organization-list.d.mts +2 -2
  99. package/dist/types/commands/organization/fetch-organization-list.d.mts.map +1 -1
  100. package/dist/types/commands/package/output-purls-shallow-score.d.mts.map +1 -1
  101. package/dist/types/commands/repository/fetch-create-repo.d.mts +2 -2
  102. package/dist/types/commands/repository/fetch-create-repo.d.mts.map +1 -1
  103. package/dist/types/commands/repository/fetch-delete-repo.d.mts +2 -2
  104. package/dist/types/commands/repository/fetch-delete-repo.d.mts.map +1 -1
  105. package/dist/types/commands/repository/fetch-list-all-repos.d.mts +2 -2
  106. package/dist/types/commands/repository/fetch-list-all-repos.d.mts.map +1 -1
  107. package/dist/types/commands/repository/fetch-list-repos.d.mts +2 -2
  108. package/dist/types/commands/repository/fetch-list-repos.d.mts.map +1 -1
  109. package/dist/types/commands/repository/fetch-update-repo.d.mts +2 -2
  110. package/dist/types/commands/repository/fetch-update-repo.d.mts.map +1 -1
  111. package/dist/types/commands/repository/fetch-view-repo.d.mts +2 -2
  112. package/dist/types/commands/repository/fetch-view-repo.d.mts.map +1 -1
  113. package/dist/types/commands/repository/handle-list-repos.d.mts.map +1 -1
  114. package/dist/types/commands/repository/output-create-repo.d.mts +2 -2
  115. package/dist/types/commands/repository/output-create-repo.d.mts.map +1 -1
  116. package/dist/types/commands/repository/output-delete-repo.d.mts +2 -2
  117. package/dist/types/commands/repository/output-delete-repo.d.mts.map +1 -1
  118. package/dist/types/commands/repository/output-list-repos.d.mts +2 -2
  119. package/dist/types/commands/repository/output-list-repos.d.mts.map +1 -1
  120. package/dist/types/commands/repository/output-update-repo.d.mts +2 -2
  121. package/dist/types/commands/repository/output-update-repo.d.mts.map +1 -1
  122. package/dist/types/commands/repository/output-view-repo.d.mts +2 -2
  123. package/dist/types/commands/repository/output-view-repo.d.mts.map +1 -1
  124. package/dist/types/commands/scan/cmd-scan-create.d.mts.map +1 -1
  125. package/dist/types/commands/scan/cmd-scan-reach.d.mts.map +1 -1
  126. package/dist/types/commands/scan/create-scan-from-github.d.mts.map +1 -1
  127. package/dist/types/commands/scan/exclude-paths.d.mts +2 -2
  128. package/dist/types/commands/scan/exclude-paths.d.mts.map +1 -1
  129. package/dist/types/commands/scan/fetch-create-org-full-scan.d.mts +2 -2
  130. package/dist/types/commands/scan/fetch-create-org-full-scan.d.mts.map +1 -1
  131. package/dist/types/commands/scan/fetch-delete-org-full-scan.d.mts +2 -2
  132. package/dist/types/commands/scan/fetch-delete-org-full-scan.d.mts.map +1 -1
  133. package/dist/types/commands/scan/fetch-list-scans.d.mts +2 -2
  134. package/dist/types/commands/scan/fetch-list-scans.d.mts.map +1 -1
  135. package/dist/types/commands/scan/fetch-scan-metadata.d.mts +2 -2
  136. package/dist/types/commands/scan/fetch-scan-metadata.d.mts.map +1 -1
  137. package/dist/types/commands/scan/fetch-scan.d.mts +4 -0
  138. package/dist/types/commands/scan/fetch-scan.d.mts.map +1 -1
  139. package/dist/types/commands/scan/fetch-supported-scan-file-names.d.mts +2 -1
  140. package/dist/types/commands/scan/fetch-supported-scan-file-names.d.mts.map +1 -1
  141. package/dist/types/commands/scan/finalize-tier1-scan.d.mts +9 -5
  142. package/dist/types/commands/scan/finalize-tier1-scan.d.mts.map +1 -1
  143. package/dist/types/commands/scan/handle-create-new-scan.d.mts.map +1 -1
  144. package/dist/types/commands/scan/handle-scan-reach.d.mts.map +1 -1
  145. package/dist/types/commands/scan/output-create-new-scan.d.mts +2 -2
  146. package/dist/types/commands/scan/output-create-new-scan.d.mts.map +1 -1
  147. package/dist/types/commands/scan/output-delete-scan.d.mts +2 -2
  148. package/dist/types/commands/scan/output-delete-scan.d.mts.map +1 -1
  149. package/dist/types/commands/scan/output-list-scans.d.mts +2 -2
  150. package/dist/types/commands/scan/output-list-scans.d.mts.map +1 -1
  151. package/dist/types/commands/scan/output-scan-metadata.d.mts +2 -2
  152. package/dist/types/commands/scan/output-scan-metadata.d.mts.map +1 -1
  153. package/dist/types/commands/scan/output-scan-reach.d.mts +2 -1
  154. package/dist/types/commands/scan/output-scan-reach.d.mts.map +1 -1
  155. package/dist/types/commands/scan/perform-reachability-analysis.d.mts +10 -3
  156. package/dist/types/commands/scan/perform-reachability-analysis.d.mts.map +1 -1
  157. package/dist/types/commands/scan/reachability-flags.d.mts.map +1 -1
  158. package/dist/types/commands/scan/reachability-units.d.mts +18 -0
  159. package/dist/types/commands/scan/reachability-units.d.mts.map +1 -0
  160. package/dist/types/commands/scan/stream-scan.d.mts +3 -1
  161. package/dist/types/commands/scan/stream-scan.d.mts.map +1 -1
  162. package/dist/types/constants.d.mts +2 -0
  163. package/dist/types/constants.d.mts.map +1 -1
  164. package/dist/types/utils/api.d.mts +53 -7
  165. package/dist/types/utils/api.d.mts.map +1 -1
  166. package/dist/types/utils/config.d.mts.map +1 -1
  167. package/dist/types/utils/dlx.d.mts +4 -3
  168. package/dist/types/utils/dlx.d.mts.map +1 -1
  169. package/dist/types/utils/ecosystem.d.mts +8 -1
  170. package/dist/types/utils/ecosystem.d.mts.map +1 -1
  171. package/dist/types/utils/fs.d.mts +1 -0
  172. package/dist/types/utils/fs.d.mts.map +1 -1
  173. package/dist/types/utils/glob.d.mts +5 -4
  174. package/dist/types/utils/glob.d.mts.map +1 -1
  175. package/dist/types/utils/meow-with-subcommands.d.mts.map +1 -1
  176. package/dist/types/utils/path-resolve.d.mts +1 -1
  177. package/dist/types/utils/path-resolve.d.mts.map +1 -1
  178. package/dist/types/utils/sdk.d.mts +6 -0
  179. package/dist/types/utils/sdk.d.mts.map +1 -1
  180. package/dist/types/utils/self-update.d.mts +3 -0
  181. package/dist/types/utils/self-update.d.mts.map +1 -0
  182. package/dist/types/utils/socket-json.d.mts +18 -0
  183. package/dist/types/utils/socket-json.d.mts.map +1 -1
  184. package/dist/types/utils/socket-package-alert.d.mts.map +1 -1
  185. package/dist/utils.js +614 -283
  186. package/dist/utils.js.map +1 -1
  187. package/dist/vendor.js +50578 -27781
  188. package/external/blessed/lib/tput.js +5 -5
  189. package/package.json +60 -132
  190. package/requirements.json +1 -1
  191. package/shadow-bin/npm +0 -0
  192. package/shadow-bin/npx +0 -0
  193. package/shadow-bin/pnpm +0 -0
  194. package/shadow-bin/yarn +0 -0
  195. package/dist/types/commands/manifest/bazel/bazel-build-parser.d.mts +0 -34
  196. package/dist/types/commands/manifest/bazel/bazel-build-parser.d.mts.map +0 -1
  197. package/external/blessed/usr/fonts/AUTHORS +0 -1
  198. package/external/blessed/usr/fonts/LICENSE +0 -94
  199. package/external/blessed/usr/fonts/README +0 -340
  200. package/external/blessed/usr/fonts/ter-u14b.json +0 -17826
  201. package/external/blessed/usr/fonts/ter-u14n.json +0 -17826
@@ -0,0 +1,581 @@
1
+ // Invoke via:
2
+ // ./gradlew --init-script socket-facts.init.gradle socketFacts
3
+
4
+ import java.util.Collections
5
+
6
+ // Emits a flat line-protocol RECORDS file (NOT the final .socket.facts.json, and NOT to stdout). The
7
+ // TS assembler (utils/src/manifest-scripts/assemble.ts) reads the records and owns all SBOM
8
+ // construction — graph merge, content-addressed ids, contentHash. This script only RESOLVES and emits
9
+ // raw facts. See records.ts for the record grammar.
10
+
11
+ // `Project.findProperty` only exists since Gradle 2.13; fall back to hasProperty/property for older Gradle.
12
+ gradle.ext.socketProp = { proj, name -> proj.hasProperty(name) ? proj.property(name) : null }
13
+
14
+ // `-Psocket.excludePaths` → glob PathMatchers, used only to skip whole excluded subprojects
15
+ // (source-file-level exclusion is left to the reachability analysis). Each entry variant yields the entry
16
+ // itself and `entry/**` so it matches the dir and its subtree (same expansion as the SCA ignore path). A
17
+ // trailing `/**` is stripped first, so a user-written `dir/**` still excludes the `dir` directory itself,
18
+ // not only its contents. Standard glob semantics (anchored to the scan root, matching the CLI flag): `x`
19
+ // is root-level, `**/x` matches at any depth. Mirrors the sbt/maven producers.
20
+ // NIO glob requires a slash-adjacent `**` to consume at least one path segment, but the CLI's
21
+ // micromatch lets it match zero (`**/x` matches root-level `x`). Emit every variant with `**/`
22
+ // occurrences dropped so both semantics hold.
23
+ gradle.ext.socketZeroDepthVariants = { String glob ->
24
+ def out = new LinkedHashSet()
25
+ def work = new ArrayDeque()
26
+ work.add(glob)
27
+ while (!work.isEmpty()) {
28
+ def cur = work.poll()
29
+ if (!out.add(cur)) { continue }
30
+ int idx = cur.indexOf('**/')
31
+ while (idx >= 0) {
32
+ if (idx == 0 || cur[idx - 1] == '/') {
33
+ def collapsed = cur.substring(0, idx) + cur.substring(idx + 3)
34
+ if (!collapsed.isEmpty()) { work.add(collapsed) }
35
+ }
36
+ idx = cur.indexOf('**/', idx + 1)
37
+ }
38
+ }
39
+ out
40
+ }
41
+ gradle.ext.socketExcludeMatchersCache = null
42
+ gradle.ext.socketExcludeMatchers = {
43
+ if (gradle.ext.socketExcludeMatchersCache != null) return gradle.ext.socketExcludeMatchersCache
44
+ def raw = gradle.socketProp.call(gradle.rootProject, 'socket.excludePaths')?.toString()
45
+ def matchers = []
46
+ if (raw != null && !raw.trim().isEmpty()) {
47
+ def fs = java.nio.file.FileSystems.getDefault()
48
+ raw.split(',').each { r ->
49
+ def g = r.trim().replace('\\', '/')
50
+ while (g.startsWith('/')) { g = g.substring(1) }
51
+ while (g.endsWith('/')) { g = g.substring(0, g.length() - 1) }
52
+ while (g.endsWith('/**')) {
53
+ g = g.substring(0, g.length() - 3)
54
+ while (g.endsWith('/')) { g = g.substring(0, g.length() - 1) }
55
+ }
56
+ if (!g.isEmpty()) {
57
+ gradle.ext.socketZeroDepthVariants.call(g).each { v ->
58
+ matchers << fs.getPathMatcher('glob:' + v)
59
+ matchers << fs.getPathMatcher('glob:' + v + '/**')
60
+ }
61
+ }
62
+ }
63
+ }
64
+ gradle.ext.socketExcludeMatchersCache = matchers
65
+ matchers
66
+ }
67
+ gradle.ext.socketIsExcluded = { String rel ->
68
+ def c = (rel == null ? '' : rel).replace('\\', '/')
69
+ while (c.startsWith('./')) { c = c.substring(2) }
70
+ while (c.startsWith('/')) { c = c.substring(1) }
71
+ while (c.endsWith('/')) { c = c.substring(0, c.length() - 1) }
72
+ if (c.isEmpty()) { return false }
73
+ def p = java.nio.file.Paths.get(c)
74
+ gradle.ext.socketExcludeMatchers.call().any { m -> m.matches(p) }
75
+ }
76
+
77
+ // Synchronized collections so --parallel-enabled builds don't race; lives on gradle.ext so every
78
+ // collector and the root aggregator share one instance.
79
+ gradle.ext.socketFactsState = [
80
+ nodes : Collections.synchronizedMap([:]),
81
+ directIds : Collections.synchronizedSet([] as Set),
82
+ // `detail` is the build tool's failure message verbatim; Coana classifies the failure KIND from it.
83
+ failures : Collections.synchronizedList([]),
84
+ // Whole configs whose resolution threw (vs a single unresolved dep in `failures`).
85
+ unscannable : Collections.synchronizedList([]),
86
+ scannedConfigs : Collections.synchronizedSet([] as Set),
87
+ projectKeys : Collections.synchronizedSet([] as Set),
88
+ // Only populated with `-Psocket.withFiles=true`: reading `artifact.file` downloads the artifact.
89
+ paths : Collections.synchronizedMap([:]),
90
+ perSub : Collections.synchronizedMap([:]),
91
+ projectsInfo : Collections.synchronizedList([]),
92
+ // "group:name" -> the module's real artifact extension (e.g. jar), so an intra-project dep that
93
+ // resolves without selecting a published artifact still gets its true coordinate, not ext-less.
94
+ projectArtifactExt : Collections.synchronizedMap([:]),
95
+ ]
96
+
97
+ // Capture every project's (group:name) before collectors run so they can filter intra-project
98
+ // deps without an ordering dependency on other subprojects.
99
+ gradle.projectsEvaluated { g ->
100
+ def rootPath = g.rootProject.projectDir.toPath()
101
+ // '/'-normalized so values are stable across OSes and portable to another machine.
102
+ def rel = { java.io.File f ->
103
+ def r = rootPath.relativize(f.toPath()).toString().replace(File.separator, '/')
104
+ r.isEmpty() ? '.' : r
105
+ }
106
+ g.rootProject.allprojects.each { p ->
107
+ g.socketFactsState.projectKeys.add("${p.group ?: ''}:${p.name}")
108
+ // The module's real artifact extension (jar for java; '' for a project that builds no archive,
109
+ // e.g. a pure aggregator). Used to stamp intra-project deps that resolve without an artifact.
110
+ def artExt = ''
111
+ try {
112
+ def jt = p.tasks.findByName('jar')
113
+ if (jt != null) {
114
+ artExt = 'jar'
115
+ try { def e = jt.archiveExtension.getOrNull(); if (e) artExt = e } catch (Exception ig1) {
116
+ try { def e = jt.extension; if (e) artExt = e } catch (Exception ig2) {}
117
+ }
118
+ }
119
+ } catch (Exception ignore) {}
120
+ g.socketFactsState.projectArtifactExt["${p.group ?: ''}:${p.name}".toString()] = artExt
121
+ // A wholly excluded subproject emits no project record and its configs are never resolved (see the
122
+ // collector). projectKeys/projectArtifactExt above stay populated so a KEPT project depending on it
123
+ // still recognizes it as a first-party module.
124
+ if (g.ext.socketIsExcluded.call(rel(p.projectDir))) {
125
+ return
126
+ }
127
+ // Guarded: projects without the java `sourceSets` convention (pure aggregators, AGP-only
128
+ // Android modules) contribute empty lists rather than failing.
129
+ // ALL source sets are emitted (not just main/test) so custom ones (integrationTest,
130
+ // functionalTest, ...) reach the analysis; source-file-level exclusion is coana's job.
131
+ // `classesDirs` (Gradle 4+) falls back to the legacy single `classesDir` on older Gradle.
132
+ def sources = [] as Set
133
+ def targets = [] as Set
134
+ try {
135
+ if (p.hasProperty('sourceSets')) {
136
+ p.sourceSets.each { ss ->
137
+ if (ss != null) {
138
+ ss.allSource.srcDirs.each { d -> sources << d.absolutePath }
139
+ try {
140
+ ss.output.classesDirs.each { d -> targets << d.absolutePath }
141
+ } catch (Exception e1) {
142
+ try { targets << ss.output.classesDir.absolutePath } catch (Exception ignore) {}
143
+ }
144
+ }
145
+ }
146
+ }
147
+ } catch (Exception ignore) {}
148
+ g.socketFactsState.projectsInfo.add([
149
+ path : p.path,
150
+ group : (p.group ?: '').toString(),
151
+ name : p.name,
152
+ version: (p.version ?: '').toString(),
153
+ dir : rel(p.projectDir),
154
+ sources: (sources as List).sort(),
155
+ targets: (targets as List).sort(),
156
+ ])
157
+ }
158
+ }
159
+
160
+ allprojects { project ->
161
+ def collectTask = project.tasks.create('socketFactsCollect') {
162
+ description = "Resolves ${project.path}'s configurations into the build-wide Socket facts accumulator"
163
+ // Dependency resolution depends on state Gradle's up-to-date tracking can't represent reliably.
164
+ outputs.upToDateWhen { false }
165
+
166
+ doLast {
167
+ def state = gradle.socketFactsState
168
+ // Skip resolving a wholly excluded subproject: `--exclude-paths` covering its dir means we don't
169
+ // resolve for it at all (matches the project-record skip in projectsEvaluated).
170
+ def excludeRootPath = project.rootProject.projectDir.toPath()
171
+ def excludeRelDir = excludeRootPath.relativize(project.projectDir.toPath()).toString().replace(File.separator, '/')
172
+ if (gradle.ext.socketIsExcluded.call(excludeRelDir.isEmpty() ? '.' : excludeRelDir)) {
173
+ return
174
+ }
175
+ // Per-RESOLUTION-ROOT accumulators, LOCAL to this collector: one (subproject, configuration)
176
+ // classpath resolved into its own coordinate-keyed tree. Reassigned per config below (the
177
+ // visit/upsertNode closures capture them); `failures`/`scannedConfigs` stay shared.
178
+ def nodes = [:]
179
+ def directIds = [] as Set
180
+ def failures = state.failures
181
+ def scannedConfigs = state.scannedConfigs
182
+ def projectKeys = state.projectKeys
183
+ def projectArtifactExt = state.projectArtifactExt
184
+ def paths = [:]
185
+ def withFiles = gradle.socketProp.call(project, 'socket.withFiles')?.toString()?.toLowerCase() == 'true'
186
+ // Optional `-Psocket.populateFilesFor=<file>` (newline-delimited GAVs): scope `--with-files`
187
+ // materialization to those GAVs. This run walks EVERY resolvable config (not told the SBOM's
188
+ // include/exclude), so without a scope it would force-download the build's entire dep universe.
189
+ // Matched at GAV level to tolerate untrustworthy-ext SBOMs (lockfile / version catalog hardcode
190
+ // ext=jar). Absent/empty/missing file -> materialize all. Parsed once under the state monitor.
191
+ def populateFilesFor = gradle.socketProp.call(project, 'socket.populateFilesFor')?.toString()
192
+ def populateGavs = null
193
+ if (withFiles && populateFilesFor != null && !populateFilesFor.trim().isEmpty()) {
194
+ synchronized (state) {
195
+ if (!state.containsKey('populateGavs')) {
196
+ def f = new File(populateFilesFor.trim())
197
+ if (f.exists()) {
198
+ def set = [] as Set
199
+ f.eachLine { line -> def t = line.trim(); if (!t.isEmpty()) { set << t } }
200
+ if (set.isEmpty()) {
201
+ // Empty file is more likely a wiring slip than a deliberate "fetch nothing"; materialize all.
202
+ state.populateGavs = null
203
+ println "[socket-facts] WARN populateFilesFor file empty; materializing files for all resolved artifacts"
204
+ } else {
205
+ state.populateGavs = set
206
+ println "[socket-facts] --with-files scoped to ${set.size()} SBOM artifact(s)"
207
+ }
208
+ } else {
209
+ // Missing file is more likely a wiring bug than an intentional empty scope; materialize all.
210
+ state.populateGavs = null
211
+ println "[socket-facts] WARN populateFilesFor file not found; materializing files for all resolved artifacts"
212
+ }
213
+ }
214
+ }
215
+ populateGavs = state.populateGavs
216
+ }
217
+
218
+ // Full Maven coordinate (absent segments dropped) — each ext/classifier variant is its own
219
+ // component; the id is the coordinate key reachability joins on (see mavenCoordinateKey).
220
+ def coordId = { coord ->
221
+ [coord.groupId, coord.artifactId, coord.ext, coord.classifier, coord.version].findAll { it }.join(':')
222
+ }
223
+
224
+ def isIntraProject = { String group, String name ->
225
+ projectKeys.contains("${group ?: ''}:${name}")
226
+ }
227
+
228
+ // A first-party module dep can resolve with no published artifact (classes-dir variant, or
229
+ // variant-ambiguous moduleArtifacts), leaving it ext-less. Stamp the module's real artifact
230
+ // extension so it keeps a full, stable coordinate instead of an ext-less one.
231
+ def effExt = { String group, String name, String ext ->
232
+ if ((ext == null || ext.isEmpty()) && isIntraProject(group, name)) {
233
+ projectArtifactExt["${group ?: ''}:${name}".toString()] ?: ''
234
+ } else {
235
+ ext ?: ''
236
+ }
237
+ }
238
+
239
+ // A node is created once; its prod flag accumulates (OR) across the configs that reach it.
240
+ def upsertNode = { Map coord, boolean isProd ->
241
+ def id = coordId(coord)
242
+ synchronized (nodes) {
243
+ def node = nodes[id]
244
+ if (node == null) {
245
+ node = [coord: coord, children: [] as Set, prod: false]
246
+ nodes[id] = node
247
+ }
248
+ if (isProd) {
249
+ node.prod = true
250
+ }
251
+ }
252
+ id
253
+ }
254
+
255
+ // Walk a resolved dependency and its transitive closure. We never touch `artifact.file` here —
256
+ // that forces Gradle to download the file (catastrophic on builds declaring distribution
257
+ // archives as deps); `artifact.extension`/`artifact.classifier` read already-resolved metadata.
258
+ // Intra-project deps (project(':lib')) ARE emitted and recursed, so an external the consumer
259
+ // pins/forces to a different version THROUGH the edge is captured (firstLevelModuleDependencies
260
+ // lists only declared deps, so a project dep's transitives are reachable only via the edge).
261
+ def visit
262
+ visit = { dep, boolean isProd, Map cache ->
263
+ if (cache.containsKey(dep)) {
264
+ return cache[dep]
265
+ }
266
+ // Pre-populate the cache to break cycles before we recurse.
267
+ def producedIds = [] as Set
268
+ cache[dep] = producedIds
269
+
270
+ // `moduleArtifacts` forces ARTIFACT-level variant selection, which can throw on a
271
+ // variant-ambiguous dep even when the dependency GRAPH resolves fine. Guard per-dep: on
272
+ // failure emit at GAV level and keep walking so one ambiguous artifact can't sink the config.
273
+ def artifacts
274
+ try {
275
+ artifacts = dep.moduleArtifacts
276
+ } catch (Exception e) {
277
+ artifacts = [] as Set
278
+ }
279
+ if (artifacts.isEmpty()) {
280
+ def ext = effExt(dep.moduleGroup, dep.moduleName, '')
281
+ // Skip a no-artifact first-party module (aggregator / build root): it builds no archive, so
282
+ // it's fully described by `projects` and is never a real artifact dependency.
283
+ if (!(ext.isEmpty() && isIntraProject(dep.moduleGroup, dep.moduleName))) {
284
+ producedIds << upsertNode([
285
+ groupId : dep.moduleGroup ?: '',
286
+ artifactId: dep.moduleName,
287
+ version : dep.moduleVersion ?: '',
288
+ classifier: '',
289
+ ext : ext,
290
+ ], isProd)
291
+ }
292
+ } else {
293
+ // Build the GAV scope key only when a scope is set: the no-scope path never reads it and
294
+ // this is the hottest line under --parallel.
295
+ def shouldMaterialize
296
+ if (!withFiles) {
297
+ shouldMaterialize = false
298
+ } else if (populateGavs == null) {
299
+ shouldMaterialize = true
300
+ } else {
301
+ def gav = "${dep.moduleGroup ?: ''}:${dep.moduleName}:${dep.moduleVersion ?: ''}".toString()
302
+ shouldMaterialize = populateGavs.contains(gav)
303
+ }
304
+ artifacts.each { a ->
305
+ // Directory variants (java-classes-directory etc.) carry no extension; for a first-party
306
+ // module fall back to its real artifact ext (never artifact.type, a Gradle variant attr).
307
+ def ext = effExt(dep.moduleGroup, dep.moduleName, a.extension)
308
+ // Skip a no-artifact first-party module (see the empty-artifacts branch above).
309
+ if (ext.isEmpty() && isIntraProject(dep.moduleGroup, dep.moduleName)) return
310
+ def aid = upsertNode([
311
+ groupId : dep.moduleGroup ?: '',
312
+ artifactId: dep.moduleName,
313
+ version : dep.moduleVersion ?: '',
314
+ classifier: a.classifier ?: '',
315
+ ext : ext,
316
+ ], isProd)
317
+ producedIds << aid
318
+ // `a.file` downloads the artifact if not already cached, so scoping avoids fetching
319
+ // artifacts the SBOM doesn't reference. Per-artifact try/catch: a single download can
320
+ // fail without the whole config being unresolvable.
321
+ if (shouldMaterialize) {
322
+ try {
323
+ def path = a.file.absolutePath
324
+ synchronized (paths) {
325
+ def set = paths[aid]
326
+ if (set == null) { set = [] as Set; paths[aid] = set }
327
+ set << path
328
+ }
329
+ } catch (Exception e) {
330
+ println "[socket-facts] could not materialize ${aid}: ${e.message?.readLines()?.first()}"
331
+ }
332
+ }
333
+ }
334
+ }
335
+
336
+ def childIds = [] as Set
337
+ dep.children.each { child ->
338
+ childIds.addAll(visit(child, isProd, cache))
339
+ }
340
+ // A skipped no-artifact first-party module produced no node, so bubble its resolved children
341
+ // up to the parent (return childIds) — otherwise the module's transitives orphan (attached to
342
+ // nothing, not bubbled). The module itself is described by `projects`.
343
+ if (producedIds.isEmpty()) {
344
+ cache[dep] = childIds
345
+ return childIds
346
+ }
347
+ synchronized (nodes) {
348
+ producedIds.each { pid ->
349
+ // Drop self-edges: a project that depends on its own output (test → main) can resolve
350
+ // to the same coordinate as its root, which would otherwise emit a node listing itself.
351
+ nodes[pid].children.addAll(childIds.findAll { it != pid })
352
+ }
353
+ }
354
+ producedIds
355
+ }
356
+
357
+ def isTestConfig = { String name -> name.toLowerCase().contains('test') }
358
+ // Matches real compile/runtime classpaths (modern `*Classpath`, legacy `compile`/`runtime`),
359
+ // NOT a loose substring that would wrongly promote war-plugin `providedCompile`/`providedRuntime`
360
+ // (provided deps are dev). Sets the informational `dev` flag only — never gates analysis.
361
+ def isClasspathConfig = { String name ->
362
+ def n = name.toLowerCase()
363
+ n.contains('classpath') || n == 'compile' || n == 'runtime'
364
+ }
365
+
366
+ // Config-name glob to a case-SENSITIVE regex (matching is documented as case-sensitive).
367
+ // Supports `*`, `?`, and `[...]` character classes: enumerations (`[cC]`), ranges (`[a-z]`),
368
+ // and `[!..]`/`[^..]` negation. A malformed glob falls back to a literal match, never throws.
369
+ def globToRegex = { String glob ->
370
+ def sb = new StringBuilder()
371
+ int i = 0
372
+ int n = glob.length()
373
+ while (i < n) {
374
+ def ch = glob[i]
375
+ if (ch == '*') { sb << '.*'; i++ }
376
+ else if (ch == '?') { sb << '.'; i++ }
377
+ else if (ch == '[') {
378
+ int j = glob.indexOf(']', i + 1)
379
+ // Treat as a class only with a non-empty body; else a literal `[`.
380
+ if (j <= i + 1) { sb << '\\['; i++ }
381
+ else {
382
+ def body = glob.substring(i + 1, j)
383
+ boolean neg = body.startsWith('!')
384
+ if (neg) { body = body.substring(1) }
385
+ // Only literal chars and `-` ranges are meaningful; neutralize regex-class tricks.
386
+ body = body.replace('\\', '\\\\').replace('[', '\\[').replace('&', '\\&')
387
+ sb << '[' << (neg ? '^' : '') << body << ']'
388
+ i = j + 1
389
+ }
390
+ }
391
+ else if ('.\\^$|+(){}]'.contains(ch)) { sb << '\\' << ch; i++ }
392
+ else { sb << ch; i++ }
393
+ }
394
+ try {
395
+ java.util.regex.Pattern.compile(sb.toString())
396
+ } catch (java.util.regex.PatternSyntaxException e) {
397
+ java.util.regex.Pattern.compile(java.util.regex.Pattern.quote(glob))
398
+ }
399
+ }
400
+
401
+ // `-Psocket.includeConfigs`/`-Psocket.excludeConfigs`: comma-separated config-name globs. A
402
+ // config is walked when it matches some include (or there are none) AND matches no exclude.
403
+ def parsePatterns = { String s ->
404
+ def out = []
405
+ if (s != null && !s.trim().isEmpty()) {
406
+ s.split(',').each { raw ->
407
+ def p = raw.trim()
408
+ if (!p.isEmpty()) out << globToRegex(p)
409
+ }
410
+ }
411
+ out
412
+ }
413
+ def includePatterns = parsePatterns(gradle.socketProp.call(project, 'socket.includeConfigs')?.toString())
414
+ def excludePatterns = parsePatterns(gradle.socketProp.call(project, 'socket.excludeConfigs')?.toString())
415
+
416
+ // Don't filter by canBeConsumed: Gradle defaults both role flags to true, so a plain resolvable
417
+ // config is also consumable, and excluding consumable configs would drop legitimate custom ones.
418
+ // `isCanBeResolved` only exists since Gradle 3.3; older Gradle has no split (all resolvable).
419
+ def isResolvable = { c -> c.metaClass.respondsTo(c, 'isCanBeResolved') ? c.canBeResolved : true }
420
+ def targetConfigs = project.configurations.findAll {
421
+ if (!isResolvable(it)) return false
422
+ def name = it.name
423
+ if (excludePatterns.any { p -> p.matcher(name).matches() }) return false
424
+ if (!includePatterns.isEmpty() && !includePatterns.any { p -> p.matcher(name).matches() }) return false
425
+ return true
426
+ }
427
+
428
+ // Deepest cause's message, in FULL — Coana classifies/templates it, so we don't categorize here.
429
+ def rawDetail = { problem ->
430
+ def deepest = null
431
+ def t = problem
432
+ int guard = 0
433
+ while (t != null && guard++ < 12) { if (t.message) { deepest = t.message }; t = t.cause }
434
+ return (deepest ?: 'unknown resolution failure').toString().trim()
435
+ }
436
+
437
+ targetConfigs.each { cfg ->
438
+ // Recorded even if it resolves nothing or throws — Coana surfaces the full scanned set.
439
+ scannedConfigs.add(cfg.name)
440
+ // Informational only (OR-accumulated across configs); never gates analysis, so this name
441
+ // heuristic can only mis-bucket a dep in prod/dev views, never drop it.
442
+ def isProd = isClasspathConfig(cfg.name) && !isTestConfig(cfg.name)
443
+ // Fresh tree for THIS resolution root — captured by visit/upsertNode, so no union with siblings.
444
+ nodes = [:]
445
+ directIds = [] as Set
446
+ paths = [:]
447
+ // Per-config try/catch: AGP-style configs can fail variant ambiguity from an init-script
448
+ // context lacking the consumer attributes AGP sets internally.
449
+ try {
450
+ def lenient = cfg.resolvedConfiguration.lenientConfiguration
451
+ def cache = [:]
452
+ // No-arg getter only since Gradle 3.3; older Gradle has just the Spec-taking overload.
453
+ def firstLevel
454
+ try {
455
+ firstLevel = lenient.firstLevelModuleDependencies
456
+ } catch (MissingPropertyException e) {
457
+ firstLevel = lenient.getFirstLevelModuleDependencies({ true } as org.gradle.api.specs.Spec)
458
+ }
459
+ firstLevel.each { dep ->
460
+ directIds.addAll(visit(dep, isProd, cache))
461
+ }
462
+ // Unresolved deps drive the root aggregator's abort/warn decision but are NOT emitted as
463
+ // nodes — their selector-only coordinates would surface as half-formed phantom artifacts.
464
+ lenient.unresolvedModuleDependencies.each { dep ->
465
+ def sel = dep.selector
466
+ if (isIntraProject(sel.group, sel.name)) {
467
+ return
468
+ }
469
+ failures << [
470
+ coord : "${sel.group}:${sel.name}${sel.version ? ':' + sel.version : ''}".toString(),
471
+ detail: rawDetail(dep.problem),
472
+ config: cfg.name,
473
+ ]
474
+ }
475
+ } catch (Exception e) {
476
+ // A config-LEVEL resolution failure (a resolutionStrategy/beforeResolve rule that throws,
477
+ // an artifact-transform failure, a broken repository, ...) rather than a per-dep failure.
478
+ // Record it instead of swallowing; the build still exits cleanly and the TS layer surfaces it.
479
+ state.unscannable << [config: cfg.name, detail: rawDetail(e)]
480
+ }
481
+ // Stash this resolution root's tree; the key only has to be unique per root (aggregator
482
+ // groups by subtree hash). Skip empty configs — they'd contribute nothing.
483
+ if (!nodes.isEmpty()) {
484
+ synchronized (state.perSub) {
485
+ state.perSub["${project.path}::${cfg.name}".toString()] =
486
+ [nodes: nodes, direct: directIds, paths: paths, projectPath: project.path, config: cfg.name, prod: isProd]
487
+ }
488
+ }
489
+ }
490
+ }
491
+ }
492
+ }
493
+
494
+ rootProject { rp ->
495
+ // Hoist property reads to configuration time: the configuration cache forbids `Task.project` from
496
+ // task actions. The Socket CLI disables the cache for this run, but hoisting is cheap insurance.
497
+ def recordsFileOverride = gradle.socketProp.call(rp, 'socket.recordsFile')?.toString()
498
+ def defaultRecordsFile = new File(rp.projectDir, '.socket.facts.records.tsv').absolutePath
499
+ // `sources`/`targets` are --with-files-only; a plain run emits only the graph fields.
500
+ def withFilesProjects = gradle.socketProp.call(rp, 'socket.withFiles')?.toString()?.toLowerCase() == 'true'
501
+
502
+ rp.tasks.create('socketFacts') {
503
+ group = 'socket'
504
+ description = 'Emits Socket facts records for the entire build'
505
+ outputs.upToDateWhen { false }
506
+
507
+ doLast {
508
+ def state = gradle.socketFactsState
509
+
510
+ // Backslash-escape so a value can never break line/field framing (see records.ts unescape).
511
+ def esc = { v ->
512
+ (v == null ? '' : v.toString())
513
+ .replace('\\', '\\\\').replace('\t', '\\t').replace('\n', '\\n').replace('\r', '\\r')
514
+ }
515
+ def lines = []
516
+ def rec = { List fields -> lines << fields.collect { esc(it) }.join('\t') }
517
+
518
+ rec(['meta', 'gradle', gradle.gradleVersion, System.getProperty('java.version')])
519
+
520
+ // One `project` record per build module (sources/targets only with --with-files).
521
+ def projectsInfo
522
+ synchronized (state.projectsInfo) { projectsInfo = new ArrayList(state.projectsInfo) }
523
+ projectsInfo.each { pi ->
524
+ rec(['project', pi.path, pi.group ?: '', pi.name, pi.version ?: '', pi.dir])
525
+ if (withFilesProjects) {
526
+ pi.sources.each { s -> rec(['projectSrc', pi.path, s]) }
527
+ pi.targets.each { t -> rec(['projectTgt', pi.path, t]) }
528
+ }
529
+ }
530
+
531
+ // One resolution root per (subproject, configuration); the TS assembler merges them
532
+ // path-sensitively and content-addresses divergent subtrees.
533
+ def perSub
534
+ synchronized (state.perSub) { perSub = new LinkedHashMap(state.perSub) }
535
+ int rootIdx = 0
536
+ perSub.each { rootKey, tree ->
537
+ def rootId = (rootIdx++).toString()
538
+ rec(['root', rootId, tree.projectPath, tree.config, tree.prod ? '1' : '0'])
539
+ tree.nodes.each { coordId, node ->
540
+ def c = node.coord
541
+ rec(['node', rootId, coordId, c.groupId ?: '', c.artifactId ?: '', c.version ?: '', c.ext ?: '',
542
+ c.classifier ?: '', tree.direct.contains(coordId) ? '1' : '0'])
543
+ node.children.each { childId -> rec(['edge', rootId, coordId, childId]) }
544
+ def fs = tree.paths[coordId]
545
+ if (fs) { (fs as List).sort().each { p -> rec(['file', rootId, coordId, p]) } }
546
+ }
547
+ }
548
+
549
+ // Scanned configs + raw failures (Coana owns the abort/warn policy and report rendering).
550
+ def scanned
551
+ synchronized (state.scannedConfigs) { scanned = (state.scannedConfigs as List).sort() }
552
+ scanned.each { name -> rec(['scanned', name]) }
553
+ def failures
554
+ synchronized (state.failures) { failures = new ArrayList(state.failures) }
555
+ failures.each { f -> rec(['failure', f.coord, f.detail, f.config]) }
556
+ def unscannable
557
+ synchronized (state.unscannable) { unscannable = new ArrayList(state.unscannable) }
558
+ unscannable.each { u -> rec(['unscannable', u.config, u.detail]) }
559
+
560
+ def outFile = new File(recordsFileOverride ?: defaultRecordsFile)
561
+ outFile.parentFile?.mkdirs()
562
+ // Explicit UTF-8: the TS reader reads UTF-8, but `File.text =` would encode
563
+ // with the JVM default charset (Cp1252 / US-ASCII on Windows or a non-UTF-8
564
+ // locale on JDK <=17), corrupting non-ASCII paths and failure messages.
565
+ outFile.withWriter('UTF-8') { it.write(lines.join('\n') + '\n') }
566
+ println "Socket facts records written to: ${outFile.absolutePath}"
567
+ }
568
+ }
569
+ }
570
+
571
+ gradle.projectsEvaluated { g ->
572
+ def aggregator = g.rootProject.tasks.findByName('socketFacts')
573
+ if (aggregator) {
574
+ g.rootProject.allprojects.each { p ->
575
+ def collector = p.tasks.findByName('socketFactsCollect')
576
+ if (collector) {
577
+ aggregator.dependsOn(collector)
578
+ }
579
+ }
580
+ }
581
+ }