@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
@@ -15,7 +15,13 @@ import {
15
15
  reportPluginRunFailure,
16
16
  resolvePluginSourceDir,
17
17
  } from "../core/pluginRun.js";
18
+ import {
19
+ encodeUriReference,
20
+ hasInlineCredential,
21
+ sanitizeBomUrl,
22
+ } from "../core/propertySanitizer.js";
18
23
  import { resolvePluginBinary } from "../inventory/plugins.js";
24
+ import { canonicalPurlFromLooseString } from "../inventory/purl.js";
19
25
 
20
26
  const GO_LANGUAGES = new Set(["go", "golang"]);
21
27
  const GOLEM_CALLGRAPH_MODES = new Set(["none", "static", "cha", "rta", "vta"]);
@@ -108,7 +114,38 @@ function purlWithoutVersion(purl) {
108
114
  }
109
115
 
110
116
  function modulePurl(module) {
111
- return module?.purl || module?.PURL;
117
+ // cdxgen reports a module that a replace directive swapped out under the
118
+ // replacement's coordinates, which golem names as replace.purl. A directory
119
+ // replacement has none, and its versionless component matches module.purl.
120
+ return module?.replace?.purl || module?.purl || module?.PURL;
121
+ }
122
+
123
+ // The canonical form of every purl an alias map was asked about, so a report
124
+ // naming one module in many usages parses its purl once.
125
+ const purlMatchKeyCache = new WeakMap();
126
+
127
+ /**
128
+ * The form two purls of one module share. Golem prints a module path as Go
129
+ * does, keeping its upper-case letters (github.com/BurntSushi/toml), while
130
+ * cdxgen components carry the canonical golang purl, which lower-cases them.
131
+ * Canonicalising both sides also reads an unescaped `+incompatible` like the
132
+ * escaped one.
133
+ *
134
+ * @param {string} purl A purl from the report or a component
135
+ * @param {Map} purlAliasMap The alias map the key belongs to
136
+ * @returns {string} The canonical purl, or the purl itself when it cannot be
137
+ * parsed
138
+ */
139
+ function purlMatchKey(purl, purlAliasMap) {
140
+ let cache = purlMatchKeyCache.get(purlAliasMap);
141
+ if (!cache) {
142
+ cache = new Map();
143
+ purlMatchKeyCache.set(purlAliasMap, cache);
144
+ }
145
+ if (!cache.has(purl)) {
146
+ cache.set(purl, canonicalPurlFromLooseString(purl) || purl);
147
+ }
148
+ return cache.get(purl);
112
149
  }
113
150
 
114
151
  function createPurlAliasMap(components = []) {
@@ -117,8 +154,9 @@ function createPurlAliasMap(components = []) {
117
154
  if (!component?.purl) {
118
155
  continue;
119
156
  }
120
- purlAliasMap.set(component.purl, component.purl);
121
- const noVersionPurl = purlWithoutVersion(component.purl);
157
+ const key = purlMatchKey(component.purl, purlAliasMap);
158
+ purlAliasMap.set(key, component.purl);
159
+ const noVersionPurl = purlWithoutVersion(key);
122
160
  if (noVersionPurl && !purlAliasMap.has(noVersionPurl)) {
123
161
  purlAliasMap.set(noVersionPurl, component.purl);
124
162
  }
@@ -130,7 +168,13 @@ function resolveComponentPurl(purl, purlAliasMap) {
130
168
  if (!purl) {
131
169
  return undefined;
132
170
  }
133
- return purlAliasMap.get(purl) || purlAliasMap.get(purlWithoutVersion(purl));
171
+ const key = purlMatchKey(purl, purlAliasMap);
172
+ return (
173
+ purlAliasMap.get(key) ||
174
+ // A package subpath (`#internal`) names a part of the module.
175
+ purlAliasMap.get(key.split("#")[0]) ||
176
+ purlAliasMap.get(purlWithoutVersion(key))
177
+ );
134
178
  }
135
179
 
136
180
  function addResolvedPurl(purls, purl, purlAliasMap) {
@@ -1613,14 +1657,18 @@ export function isGolemGoLanguage(language) {
1613
1657
  *
1614
1658
  * @param {string} jsonFile Path to the golem JSON report.
1615
1659
  * @returns {object|undefined} Parsed report object, or undefined when the file
1616
- * is missing or cannot be parsed.
1660
+ * is missing, cannot be parsed, or holds something other than a JSON object
1661
+ * such as `null`.
1617
1662
  */
1618
1663
  export function readGolemJsonFile(jsonFile) {
1619
1664
  if (!jsonFile || !safeExistsSync(jsonFile)) {
1620
1665
  return undefined;
1621
1666
  }
1622
1667
  try {
1623
- return JSON.parse(readFileSync(jsonFile, "utf-8"));
1668
+ const report = JSON.parse(readFileSync(jsonFile, "utf-8"));
1669
+ return report && typeof report === "object" && !Array.isArray(report)
1670
+ ? report
1671
+ : undefined;
1624
1672
  } catch (_err) {
1625
1673
  return undefined;
1626
1674
  }
@@ -1831,3 +1879,499 @@ export function collectGolemEvidence(golemReport = {}, components = []) {
1831
1879
  purlLocationMap,
1832
1880
  };
1833
1881
  }
1882
+
1883
+ // The methods a route can be registered for: the RFC 9110 methods, PATCH,
1884
+ // and the catch-all that gin's and echo's Any register.
1885
+ const GOLEM_HTTP_METHODS = new Set([
1886
+ "get",
1887
+ "head",
1888
+ "post",
1889
+ "put",
1890
+ "delete",
1891
+ "connect",
1892
+ "options",
1893
+ "trace",
1894
+ "patch",
1895
+ "any",
1896
+ ]);
1897
+
1898
+ // An RFC 9110 token, which is what net/http accepts as a pattern's method.
1899
+ const HTTP_METHOD_TOKEN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
1900
+
1901
+ // The host of a net/http pattern: a name or address with an optional port.
1902
+ const GO_PATTERN_HOST = /^[A-Za-z0-9.:[\]-]+$/;
1903
+
1904
+ // Hosts whose URL path carries the credential itself — webhook and bot URLs
1905
+ // that authenticate by the path alone — with the route each keeps. Everything
1906
+ // below that route, the team, channel and hook ids and the secret, is dropped.
1907
+ const GOLEM_SECRET_PATH_ROUTES = [
1908
+ ["hooks.slack.com", ["/services/", "/workflows/", "/triggers/"]],
1909
+ ["discord.com", ["/api/webhooks/"]],
1910
+ ["discordapp.com", ["/api/webhooks/"]],
1911
+ ["hooks.zapier.com", ["/hooks/catch/", "/hooks/standard/"]],
1912
+ ["maker.ifttt.com", ["/trigger/"]],
1913
+ ["api.telegram.org", ["/bot", "/file/bot"]],
1914
+ ["outlook.office.com", ["/webhook/", "/webhookb2/"]],
1915
+ ["webhook.office.com", ["/webhookb2/"]],
1916
+ ];
1917
+
1918
+ /**
1919
+ * Append a property to a service definition unless the same name and value are
1920
+ * already there, so repeated endpoints of one service do not repeat their
1921
+ * framework or method.
1922
+ *
1923
+ * @param {Object[]} properties Property array, appended to in place
1924
+ * @param {string} name Property name
1925
+ * @param {string} value Property value, skipped unless a non-empty string
1926
+ */
1927
+ function appendGolemServiceProperty(properties, name, value) {
1928
+ if (typeof value !== "string" || !value) {
1929
+ return;
1930
+ }
1931
+ if (
1932
+ !properties.some((known) => known.name === name && known.value === value)
1933
+ ) {
1934
+ properties.push({ name, value });
1935
+ }
1936
+ }
1937
+
1938
+ /**
1939
+ * The method a golem endpoint names, lower-cased the way the OpenAPI flow
1940
+ * spells `cdx:service:httpMethod`, or undefined when it is not an HTTP
1941
+ * method. golem takes the selector of a chained gorilla/mux call for the
1942
+ * method, so `.Methods("GET", "POST")` arrives as `METHODS` and
1943
+ * `.PathPrefix("/products")` as `PATHPREFIX`; neither names one.
1944
+ *
1945
+ * @param {unknown} method Method a golem endpoint reports
1946
+ * @returns {string|undefined} The lower-cased method
1947
+ */
1948
+ function golemHttpMethod(method) {
1949
+ if (typeof method !== "string") {
1950
+ return undefined;
1951
+ }
1952
+ const lowered = method.trim().toLowerCase();
1953
+ return GOLEM_HTTP_METHODS.has(lowered) ? lowered : undefined;
1954
+ }
1955
+
1956
+ /**
1957
+ * The host a golem service names, without the `/` golem puts in front of a
1958
+ * listener address that has a host (`/127.0.0.1:9095`) and without userinfo.
1959
+ *
1960
+ * @param {unknown} host Host a golem service reports
1961
+ * @returns {string|undefined} The host
1962
+ */
1963
+ function golemServiceHost(host) {
1964
+ if (typeof host !== "string") {
1965
+ return undefined;
1966
+ }
1967
+ return host.slice(host.lastIndexOf("@") + 1).replace(/^\/+/, "") || undefined;
1968
+ }
1969
+
1970
+ /**
1971
+ * Index of the brace that closes the one at `open`, counting nesting the way
1972
+ * gorilla/mux's braceIndices does, so the `{3}` of `{code:[0-9]{3}}` stays
1973
+ * inside the variable. -1 when the braces are unbalanced.
1974
+ *
1975
+ * @param {string} text Route path
1976
+ * @param {number} open Index of an opening brace
1977
+ * @returns {number} Index of the closing brace, or -1
1978
+ */
1979
+ function closingBraceIndex(text, open) {
1980
+ let depth = 0;
1981
+ for (let index = open; index < text.length; index++) {
1982
+ if (text[index] === "{") {
1983
+ depth++;
1984
+ } else if (text[index] === "}" && --depth === 0) {
1985
+ return index;
1986
+ }
1987
+ }
1988
+ return -1;
1989
+ }
1990
+
1991
+ /**
1992
+ * The OpenAPI spelling of a braced variable. gorilla/mux, chi and iris write
1993
+ * `{name:pattern}`; Go 1.22 net/http writes `{name...}` for a trailing
1994
+ * wildcard and `{$}` for an exact match of a trailing slash, which names no
1995
+ * parameter and is dropped.
1996
+ *
1997
+ * @param {string} raw Text between the braces
1998
+ * @returns {string} The placeholder
1999
+ */
2000
+ function bracedPlaceholder(raw) {
2001
+ let name = raw.split(":", 1)[0].trim();
2002
+ if (name.endsWith("...")) {
2003
+ name = name.slice(0, -3);
2004
+ }
2005
+ if (name === "$") {
2006
+ return "";
2007
+ }
2008
+ return name ? `{${name}}` : `{${raw}}`;
2009
+ }
2010
+
2011
+ /**
2012
+ * The name of a `:name` parameter starting at `start` and the index after it.
2013
+ * gin and echo end the name at the next `/`, so `:user-id` names `user-id`.
2014
+ * fiber also ends it at `-` and `.`, which lets one segment hold two
2015
+ * parameters (`:from-:to`), and allows a constraint and an optional marker
2016
+ * after it (`:id<int>?`); both are dropped.
2017
+ *
2018
+ * @param {string} routePath Route path
2019
+ * @param {number} start Index after the colon
2020
+ * @param {boolean} fiber Whether the route is a fiber route
2021
+ * @returns {{name: string, end: number}|undefined} The parameter
2022
+ */
2023
+ function colonParameter(routePath, start, fiber) {
2024
+ const stops = fiber ? "/-.<?" : "/<?";
2025
+ let end = start;
2026
+ while (end < routePath.length && !stops.includes(routePath[end])) {
2027
+ end++;
2028
+ }
2029
+ const name = routePath.slice(start, end);
2030
+ if (!name) {
2031
+ return undefined;
2032
+ }
2033
+ if (routePath[end] === "<") {
2034
+ const close = routePath.indexOf(">", end);
2035
+ if (close >= 0) {
2036
+ end = close + 1;
2037
+ }
2038
+ }
2039
+ if (routePath[end] === "?") {
2040
+ end++;
2041
+ }
2042
+ return { name, end };
2043
+ }
2044
+
2045
+ /**
2046
+ * Normalise a Go route path to the placeholder spelling OpenAPI — and
2047
+ * therefore the rest of the services a BOM carries — uses. gin, echo and
2048
+ * fiber write `:id`, gin writes `*filepath`, chi and gorilla/mux write `{id}`
2049
+ * or `{id:[0-9]+}`, and Go 1.22's net/http patterns write `{id...}`; all
2050
+ * become `{id}`. Braces are matched by depth, so a regular expression with
2051
+ * its own braces or slashes (`{code:[0-9]{3}}`, `{rest:[a-z/]+}`) stays one
2052
+ * variable. A fiber constraint or optional marker (`:id<int>`, `:name?`) is
2053
+ * dropped, and net/http's `{$}` is removed. A `:` or `*` placeholder is only
2054
+ * recognised at the start of a segment — or, for fiber, after a `-` or `.` —
2055
+ * so a literal colon such as `/time/09:30` is left alone. Values that do not
2056
+ * start with `/` are returned as they are, and so is echo's catch-all, whose
2057
+ * bare `*` names no placeholder.
2058
+ *
2059
+ * @param {string} routePath Path a golem endpoint reports
2060
+ * @param {string} [framework] Framework the route was registered with
2061
+ * @returns {string} The normalised path
2062
+ */
2063
+ export function normalizeGolemRoutePath(routePath, framework) {
2064
+ if (typeof routePath !== "string" || !routePath.startsWith("/")) {
2065
+ return routePath;
2066
+ }
2067
+ const fiber = framework === "fiber";
2068
+ let normalized = "";
2069
+ let index = 0;
2070
+ while (index < routePath.length) {
2071
+ const character = routePath[index];
2072
+ const previous = routePath[index - 1];
2073
+ if (character === "{") {
2074
+ const close = closingBraceIndex(routePath, index);
2075
+ if (close < 0) {
2076
+ // gorilla/mux rejects unbalanced braces; keep the rest as written.
2077
+ normalized += routePath.slice(index);
2078
+ break;
2079
+ }
2080
+ normalized += bracedPlaceholder(routePath.slice(index + 1, close));
2081
+ index = close + 1;
2082
+ continue;
2083
+ }
2084
+ if (
2085
+ character === ":" &&
2086
+ (previous === "/" || (fiber && (previous === "-" || previous === ".")))
2087
+ ) {
2088
+ const parameter = colonParameter(routePath, index + 1, fiber);
2089
+ if (parameter) {
2090
+ normalized += `{${parameter.name}}`;
2091
+ index = parameter.end;
2092
+ continue;
2093
+ }
2094
+ }
2095
+ if (character === "*" && previous === "/") {
2096
+ let end = routePath.indexOf("/", index);
2097
+ if (end < 0) {
2098
+ end = routePath.length;
2099
+ }
2100
+ const name = routePath.slice(index + 1, end);
2101
+ if (/^[A-Za-z0-9_]+$/.test(name)) {
2102
+ normalized += `{${name}}`;
2103
+ index = end;
2104
+ continue;
2105
+ }
2106
+ }
2107
+ normalized += character;
2108
+ index++;
2109
+ }
2110
+ return normalized;
2111
+ }
2112
+
2113
+ /**
2114
+ * Split a route golem reports into the `[METHOD ][HOST]/PATH` parts of a Go
2115
+ * 1.22 net/http ServeMux pattern. golem puts a `/` in front of the pattern it
2116
+ * read, so `GET /items/{id}` arrives as `/GET /items/{id}` and
2117
+ * `GET example.com/items` as `/GET example.com/items`. A method followed by a
2118
+ * space or tab is split off, and so is a host between the method and the
2119
+ * path. Without a method, only a value that starts with `/` is a route: the
2120
+ * `:8080` a listener reports and the `GET` that a gorilla/mux
2121
+ * `.Methods("GET")` call leaves behind are not, and give undefined. A host
2122
+ * pattern without a method reaches golem's report as a plain path
2123
+ * (`/example.com/items`) and stays one.
2124
+ *
2125
+ * @param {string} routePath Path a golem endpoint reports
2126
+ * @returns {{method: (string|undefined), host: (string|undefined), path: string}|undefined}
2127
+ * The parts, or undefined when the value is not a route
2128
+ */
2129
+ export function parseGolemRoutePattern(routePath) {
2130
+ if (typeof routePath !== "string") {
2131
+ return undefined;
2132
+ }
2133
+ const body = routePath.startsWith("/") ? routePath.slice(1) : routePath;
2134
+ const gap = body.search(/[ \t]/);
2135
+ if (gap > 0) {
2136
+ const method = body.slice(0, gap);
2137
+ const rest = body.slice(gap + 1).replace(/^[ \t]+/, "");
2138
+ const slash = rest.indexOf("/");
2139
+ if (HTTP_METHOD_TOKEN.test(method) && slash >= 0) {
2140
+ const host = rest.slice(0, slash);
2141
+ if (host && !GO_PATTERN_HOST.test(host)) {
2142
+ return undefined;
2143
+ }
2144
+ return { method, host: host || undefined, path: rest.slice(slash) };
2145
+ }
2146
+ }
2147
+ if (!routePath.startsWith("/")) {
2148
+ return undefined;
2149
+ }
2150
+ return { method: undefined, host: undefined, path: routePath };
2151
+ }
2152
+
2153
+ /**
2154
+ * The route a golem endpoint of a server names: its CycloneDX endpoint, HTTP
2155
+ * method and, for a host-specific net/http pattern, host. Undefined when the
2156
+ * entry is not a route — the listener entry that repeats the address a server
2157
+ * binds, the leftovers of a chained gorilla/mux call, or the `/PATCH` golem
2158
+ * reports for gin's `Handle("PATCH", "/path")`, whose method it read as the
2159
+ * path.
2160
+ *
2161
+ * @param {object} endpoint Endpoint a golem server service reports
2162
+ * @param {object} service The service
2163
+ * @returns {{endpoint: string, method: (string|undefined), host: (string|undefined)}|undefined}
2164
+ * The route
2165
+ */
2166
+ function golemServerRoute(endpoint, service) {
2167
+ const routePath = endpoint.path;
2168
+ if (typeof routePath !== "string" || !routePath) {
2169
+ return undefined;
2170
+ }
2171
+ const listener = golemServiceHost(service.host);
2172
+ if (listener && routePath.replace(/^\//, "") === listener) {
2173
+ return undefined;
2174
+ }
2175
+ const pattern = parseGolemRoutePattern(routePath);
2176
+ if (!pattern) {
2177
+ return undefined;
2178
+ }
2179
+ const method =
2180
+ golemHttpMethod(endpoint.method) || golemHttpMethod(pattern.method);
2181
+ if (
2182
+ !method &&
2183
+ /^\/[A-Z]+$/.test(pattern.path) &&
2184
+ GOLEM_HTTP_METHODS.has(pattern.path.slice(1).toLowerCase())
2185
+ ) {
2186
+ return undefined;
2187
+ }
2188
+ const path = normalizeGolemRoutePath(
2189
+ pattern.path,
2190
+ endpoint.properties?.framework,
2191
+ ).split(/[?#]/, 1)[0];
2192
+ return {
2193
+ endpoint: encodeUriReference(path),
2194
+ method,
2195
+ host: pattern.host,
2196
+ };
2197
+ }
2198
+
2199
+ /**
2200
+ * Reduce an outbound URL golem reports to what is safe to keep in a BOM:
2201
+ * scheme, host and path, without userinfo, query, fragment or `;name=value`
2202
+ * path parameters such as `;jsessionid=` and `;token=`. Webhook and bot URLs
2203
+ * that authenticate by their path — Slack, Discord, Teams, Zapier, IFTTT,
2204
+ * Telegram — keep only their route (`https://hooks.slack.com/services`), and a
2205
+ * path segment holding a token shape the BOM sanitizer recognises ends the
2206
+ * path there. Undefined when the value is not a URL with a host.
2207
+ *
2208
+ * @param {string} value URL golem reports
2209
+ * @returns {string|undefined} The sanitised URL
2210
+ */
2211
+ export function sanitizeGolemEndpointUrl(value) {
2212
+ if (typeof value !== "string") {
2213
+ return undefined;
2214
+ }
2215
+ let parsed;
2216
+ try {
2217
+ // sanitizeBomUrl returns what it cannot parse as it is, which this parse
2218
+ // then rejects.
2219
+ parsed = new URL(sanitizeBomUrl(value));
2220
+ } catch (_err) {
2221
+ return undefined;
2222
+ }
2223
+ if (!parsed.host) {
2224
+ return undefined;
2225
+ }
2226
+ const segments = [];
2227
+ for (const segment of parsed.pathname.split("/").slice(1)) {
2228
+ const bare = segment.split(";", 1)[0];
2229
+ let decoded = bare;
2230
+ try {
2231
+ decoded = decodeURIComponent(bare);
2232
+ } catch (_err) {
2233
+ // A malformed escape is checked as written.
2234
+ }
2235
+ if (hasInlineCredential(decoded)) {
2236
+ break;
2237
+ }
2238
+ segments.push(bare);
2239
+ }
2240
+ let path = `/${segments.join("/")}`;
2241
+ for (const [host, routes] of GOLEM_SECRET_PATH_ROUTES) {
2242
+ if (parsed.hostname !== host && !parsed.hostname.endsWith(`.${host}`)) {
2243
+ continue;
2244
+ }
2245
+ const route = routes.find((prefix) => path.startsWith(prefix));
2246
+ if (route) {
2247
+ path = route.endsWith("/") ? route.slice(0, -1) : route;
2248
+ break;
2249
+ }
2250
+ }
2251
+ return encodeUriReference(`${parsed.protocol}//${parsed.host}${path}`);
2252
+ }
2253
+
2254
+ /**
2255
+ * The URL a golem endpoint of an external service names, sanitised, with its
2256
+ * HTTP method. golem reports the full URL; a report without one has the
2257
+ * service's scheme and host put in front of the path.
2258
+ *
2259
+ * @param {object} endpoint Endpoint a golem external service reports
2260
+ * @param {object} service The service
2261
+ * @returns {{endpoint: string, method: (string|undefined)}|undefined} The URL
2262
+ */
2263
+ function golemExternalEndpoint(endpoint, service) {
2264
+ let url = endpoint.url;
2265
+ if (typeof url !== "string" || !url) {
2266
+ const host = golemServiceHost(service.host);
2267
+ if (
2268
+ !host ||
2269
+ typeof service.scheme !== "string" ||
2270
+ typeof endpoint.path !== "string" ||
2271
+ !endpoint.path.startsWith("/")
2272
+ ) {
2273
+ return undefined;
2274
+ }
2275
+ url = `${service.scheme}://${host}${endpoint.path}`;
2276
+ }
2277
+ const sanitized = sanitizeGolemEndpointUrl(url);
2278
+ return sanitized
2279
+ ? { endpoint: sanitized, method: golemHttpMethod(endpoint.method) }
2280
+ : undefined;
2281
+ }
2282
+
2283
+ /**
2284
+ * Project the services and API endpoints of a golem report into evinse's
2285
+ * service map, so a Go `evinse`/`saasbom` run lists its services the way the
2286
+ * kosi (Kotlin) and dosai (.NET) collectors already do (#4498).
2287
+ *
2288
+ * golem rolls one service up per server it saw — a `gin` engine, a net/http
2289
+ * listener on `:8080` — and lists the routes registered on it, each with its
2290
+ * HTTP method and framework. Route paths are normalised to OpenAPI
2291
+ * placeholders and Go 1.22 ServeMux patterns are split into method, host and
2292
+ * path; entries that are not routes are skipped. It also rolls the outbound
2293
+ * hosts the source calls into services of `kind: "external"`; those carry
2294
+ * `x-trust-boundary: true`, since a host the application calls sits outside
2295
+ * its own trust boundary, and their URLs are sanitised before they become
2296
+ * endpoints. Every endpoint is percent-encoded to an RFC 3986 reference. The
2297
+ * map is keyed by the golem service id, which is unique per server, while
2298
+ * `name` carries the display name.
2299
+ *
2300
+ * HTTP methods are lower-cased to match the property values the OpenAPI flow
2301
+ * emits, so a consumer grouping by `cdx:service:httpMethod` sees one value
2302
+ * per method.
2303
+ *
2304
+ * @param {object|null} [golemReport={}] Parsed golem analysis report.
2305
+ * @param {Object} [servicesMap={}] Service map to populate.
2306
+ * @returns {Object} The service map
2307
+ */
2308
+ export function collectGolemServices(golemReport = {}, servicesMap = {}) {
2309
+ const services = Array.isArray(golemReport?.services)
2310
+ ? golemReport.services
2311
+ : [];
2312
+ for (const service of services) {
2313
+ const id = typeof service?.id === "string" ? service.id : undefined;
2314
+ const name = typeof service?.name === "string" ? service.name : undefined;
2315
+ if (!id && !name) {
2316
+ continue;
2317
+ }
2318
+ const definition = (servicesMap[id || name] ??= {
2319
+ name: name || id,
2320
+ bomRef: id || name,
2321
+ endpoints: new Set(),
2322
+ properties: [],
2323
+ });
2324
+ const external = service.kind === "external";
2325
+ if (external) {
2326
+ definition["x-trust-boundary"] = true;
2327
+ }
2328
+ const endpoints = Array.isArray(service.endpoints) ? service.endpoints : [];
2329
+ for (const endpoint of endpoints) {
2330
+ if (!endpoint || typeof endpoint !== "object") {
2331
+ continue;
2332
+ }
2333
+ const route = external
2334
+ ? golemExternalEndpoint(endpoint, service)
2335
+ : golemServerRoute(endpoint, service);
2336
+ if (route) {
2337
+ definition.endpoints.add(route.endpoint);
2338
+ appendGolemServiceProperty(
2339
+ definition.properties,
2340
+ "cdx:service:httpMethod",
2341
+ route.method,
2342
+ );
2343
+ }
2344
+ appendGolemServiceProperty(
2345
+ definition.properties,
2346
+ "cdx:golem:service:framework",
2347
+ endpoint.properties?.framework,
2348
+ );
2349
+ appendGolemServiceProperty(
2350
+ definition.properties,
2351
+ "cdx:golem:service:host",
2352
+ route?.host,
2353
+ );
2354
+ }
2355
+ appendGolemServiceProperty(
2356
+ definition.properties,
2357
+ "cdx:golem:service:kind",
2358
+ service.kind,
2359
+ );
2360
+ appendGolemServiceProperty(
2361
+ definition.properties,
2362
+ "cdx:golem:service:scheme",
2363
+ service.scheme,
2364
+ );
2365
+ appendGolemServiceProperty(
2366
+ definition.properties,
2367
+ "cdx:golem:service:host",
2368
+ golemServiceHost(service.host),
2369
+ );
2370
+ appendGolemServiceProperty(
2371
+ definition.properties,
2372
+ "cdx:golem:service:package",
2373
+ service.packagePath,
2374
+ );
2375
+ }
2376
+ return servicesMap;
2377
+ }