@cdxgen/cdxgen 13.4.0 → 13.6.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.
- package/README.md +9 -9
- package/bin/cdxgen-secure.js +15 -0
- package/bin/cdxgen.js +29 -5
- package/bin/evinse.js +8 -0
- package/bin/hbom.js +15 -7
- package/bin/repl.js +29 -0
- package/bin/validate.js +6 -0
- package/data/cbomosdb-queries.json +13 -1
- package/data/component-tags.json +8 -0
- package/data/license-aliases.json +24 -2
- package/data/queries-darwin.json +25 -1
- package/data/queries-win.json +19 -1
- package/data/queries.json +41 -0
- package/data/remediations.json +407 -1
- package/data/rules/ai-bom.yaml +1 -1
- package/data/rules/brotli-decoder.yaml +107 -0
- package/data/rules/cbom-security.yaml +235 -4
- package/data/rules/go-binaries.yaml +46 -0
- package/data/rules/golem-go.yaml +80 -0
- package/data/rules/hbom-compliance.yaml +40 -22
- package/data/rules/hbom-performance.yaml +7 -1
- package/data/rules/host-topology.yaml +1 -1
- package/data/rules/obom-runtime.yaml +31 -2
- package/data/rules/php-runtime.yaml +143 -0
- package/data/rules/vscode-extensions.yaml +75 -0
- package/data/spdx-license-list.json +121 -7
- package/data/spdx-licenses.json +69 -0
- package/data/spdx.schema.json +845 -2
- package/lib/audit/index.js +14 -0
- package/lib/cli/bomAssembly.js +120 -6
- package/lib/cli/cliOptions.js +110 -3
- package/lib/cli/index.js +182 -6
- package/lib/cli/jsBom.js +153 -64
- package/lib/cli/jvmBom.js +363 -66
- package/lib/cli/managedBom.js +853 -87
- package/lib/cli/nativeBom.js +267 -33
- package/lib/core/boundedFile.js +344 -0
- package/lib/core/brotliProbe.js +182 -0
- package/lib/core/env.js +38 -8
- package/lib/core/fs.js +40 -19
- package/lib/core/httpClient.js +172 -46
- package/lib/core/lockPaths.js +38 -0
- package/lib/core/paths.js +24 -1
- package/lib/core/processTree.js +5 -0
- package/lib/core/projectConfig.js +1 -0
- package/lib/core/projectTooling.js +97 -0
- package/lib/core/propertySanitizer.js +72 -0
- package/lib/core/toolRegistry.js +170 -0
- package/lib/ecosystems/cppEvidence.js +263 -106
- package/lib/ecosystems/cppIncludeResolver.js +16 -5
- package/lib/ecosystems/ecosystems.js +25 -10
- package/lib/ecosystems/gemHostProbe.js +301 -0
- package/lib/ecosystems/golem.js +550 -6
- package/lib/ecosystems/gradleutils.js +858 -244
- package/lib/ecosystems/parsers-bazel.js +107 -6
- package/lib/ecosystems/parsers-dotnet.js +117 -18
- package/lib/ecosystems/parsers-go.js +1090 -367
- package/lib/ecosystems/parsers-js.js +505 -22
- package/lib/ecosystems/parsers-jvm.js +259 -17
- package/lib/ecosystems/parsers-misc.js +438 -64
- package/lib/ecosystems/parsers-python.js +100 -12
- package/lib/ecosystems/pythonutils.js +128 -2
- package/lib/ecosystems/rubyutils.js +544 -34
- package/lib/ecosystems/rusi.js +10 -0
- package/lib/ecosystems/scalacliutils.js +4 -1
- package/lib/ecosystems/scalasem.js +798 -151
- package/lib/ecosystems/vendoredCode.js +237 -3
- package/lib/ecosystems/venvProbe.js +360 -0
- package/lib/evinser/evinser.js +472 -61
- package/lib/evinser/swiftsem.js +68 -5
- package/lib/helpers/agenticToolutils.js +413 -93
- package/lib/helpers/vsixutils.js +1574 -315
- package/lib/inventory/agentSkillSpec.js +17 -0
- package/lib/inventory/apkInstalledDb.js +243 -0
- package/lib/inventory/atomUtils.js +110 -0
- package/lib/inventory/auditCategories.js +3 -3
- package/lib/inventory/brotliDecoder.js +142 -0
- package/lib/inventory/buildToolRateLimit.js +1 -1
- package/lib/inventory/cacheDir.js +52 -9
- package/lib/inventory/cbomutils.js +56 -7
- package/lib/inventory/certificates.js +2371 -0
- package/lib/inventory/communityAiConfigParser.js +4 -9
- package/lib/inventory/deps.js +41 -4
- package/lib/inventory/display.js +200 -3
- package/lib/inventory/dosai.js +639 -63
- package/lib/inventory/dotnetRuntime.js +217 -0
- package/lib/inventory/dynamic.js +45 -3
- package/lib/inventory/envcontext.js +475 -18
- package/lib/inventory/evidenceUtils.js +447 -23
- package/lib/inventory/fetchBatch.js +1 -1
- package/lib/inventory/formulationParsers.js +32 -3
- package/lib/inventory/goBuildInfo.js +140 -0
- package/lib/inventory/gtfobins.js +13 -0
- package/lib/inventory/hbom.js +67 -7
- package/lib/inventory/hbomAnalysis.js +16 -0
- package/lib/inventory/hostRuntimeProbe.js +686 -0
- package/lib/inventory/hostTopology.js +21 -0
- package/lib/inventory/jvmLocalRepos.js +9 -2
- package/lib/inventory/jvmToolEnv.js +29 -0
- package/lib/inventory/licenseEnhancer.js +12 -0
- package/lib/inventory/mavenCentralMirror.js +534 -0
- package/lib/inventory/nodeBuildInfo.js +218 -0
- package/lib/inventory/nodeHostProbe.js +202 -0
- package/lib/inventory/osPackagePurl.js +244 -0
- package/lib/inventory/osPackageResolver.js +271 -87
- package/lib/inventory/osinfo.js +79 -31
- package/lib/inventory/osqueryTransform.js +114 -5
- package/lib/inventory/phpBuildInfo.js +438 -0
- package/lib/inventory/phpHostProbe.js +185 -0
- package/lib/inventory/rootfsCerts.js +426 -0
- package/lib/inventory/rubyBuildInfo.js +972 -0
- package/lib/inventory/rubyHostProbe.js +218 -0
- package/lib/inventory/scalaCoords.js +4 -3
- package/lib/inventory/scalaDedupe.js +440 -0
- package/lib/inventory/source.js +256 -5
- package/lib/inventory/spdx.js +144 -0
- package/lib/inventory/traceRunner.js +5 -1
- package/lib/inventory/tracedCrypto.js +129 -0
- package/lib/managers/binary.js +866 -186
- package/lib/managers/docker.js +245 -12
- package/lib/parsers/email.js +277 -0
- package/lib/parsers/largeJson.js +62 -13
- package/lib/parsers/vscodeExtensionId.js +183 -0
- package/lib/server/server.js +198 -2
- package/lib/stages/postgen/annotator.js +17 -1
- package/lib/stages/postgen/introspection/reflect.js +4 -1
- package/lib/stages/postgen/postgen.js +5 -5
- package/lib/stages/postgen/ruleEngine.js +71 -6
- package/lib/stages/postgen/specVersionCompat.js +30 -0
- package/lib/stages/pregen/envAudit.js +1 -0
- package/lib/stages/pregen/pregen.js +231 -62
- package/lib/validator/bomValidator.js +32 -6
- package/lib/validator/generated/runtime.mjs +13 -0
- package/lib/validator/generated/validate-1.6.mjs +27 -8
- package/lib/validator/generated/validate-1.7.mjs +32 -13
- package/lib/validator/generated/validate-2.0.mjs +31 -12
- package/lib/validator/index.js +1 -1
- package/package.json +19 -18
- package/types/bin/cdxgen-secure.d.ts +3 -0
- package/types/bin/cdxgen-secure.d.ts.map +1 -0
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +22 -3
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +43 -0
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts +1 -17
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts +20 -0
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/boundedFile.d.ts +70 -0
- package/types/lib/core/boundedFile.d.ts.map +1 -0
- package/types/lib/core/brotliProbe.d.ts +37 -0
- package/types/lib/core/brotliProbe.d.ts.map +1 -0
- package/types/lib/core/env.d.ts +22 -2
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/httpClient.d.ts +24 -0
- package/types/lib/core/httpClient.d.ts.map +1 -1
- package/types/lib/core/lockPaths.d.ts +19 -0
- package/types/lib/core/lockPaths.d.ts.map +1 -0
- package/types/lib/core/processTree.d.ts +2 -0
- package/types/lib/core/processTree.d.ts.map +1 -1
- package/types/lib/core/projectConfig.d.ts.map +1 -1
- package/types/lib/core/projectTooling.d.ts +41 -0
- package/types/lib/core/projectTooling.d.ts.map +1 -0
- package/types/lib/core/propertySanitizer.d.ts +26 -0
- package/types/lib/core/propertySanitizer.d.ts.map +1 -1
- package/types/lib/core/toolRegistry.d.ts +36 -0
- package/types/lib/core/toolRegistry.d.ts.map +1 -0
- package/types/lib/ecosystems/cppEvidence.d.ts +40 -13
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts +2 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/gemHostProbe.d.ts +28 -0
- package/types/lib/ecosystems/gemHostProbe.d.ts.map +1 -0
- package/types/lib/ecosystems/golem.d.ts +81 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +57 -21
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-bazel.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-dotnet.d.ts +40 -4
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-go.d.ts +85 -13
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +17 -20
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts +28 -2
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +37 -58
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts +14 -3
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +19 -1
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts +144 -12
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -1
- package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
- package/types/lib/ecosystems/vendoredCode.d.ts +26 -0
- package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -1
- package/types/lib/ecosystems/venvProbe.d.ts +40 -0
- package/types/lib/ecosystems/venvProbe.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +88 -8
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/swiftsem.d.ts +10 -0
- package/types/lib/evinser/swiftsem.d.ts.map +1 -1
- package/types/lib/inventory/agentSkillSpec.d.ts +13 -0
- package/types/lib/inventory/agentSkillSpec.d.ts.map +1 -0
- package/types/lib/inventory/apkInstalledDb.d.ts +42 -0
- package/types/lib/inventory/apkInstalledDb.d.ts.map +1 -0
- package/types/lib/inventory/atomUtils.d.ts +33 -0
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/brotliDecoder.d.ts +58 -0
- package/types/lib/inventory/brotliDecoder.d.ts.map +1 -0
- package/types/lib/inventory/cacheDir.d.ts +12 -4
- package/types/lib/inventory/cacheDir.d.ts.map +1 -1
- package/types/lib/inventory/cbomutils.d.ts +17 -0
- package/types/lib/inventory/cbomutils.d.ts.map +1 -1
- package/types/lib/inventory/certificates.d.ts +205 -0
- package/types/lib/inventory/certificates.d.ts.map +1 -0
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/deps.d.ts +2 -1
- package/types/lib/inventory/deps.d.ts.map +1 -1
- package/types/lib/inventory/display.d.ts +18 -0
- package/types/lib/inventory/display.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +162 -7
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dotnetRuntime.d.ts +44 -0
- package/types/lib/inventory/dotnetRuntime.d.ts.map +1 -0
- package/types/lib/inventory/dynamic.d.ts +6 -1
- package/types/lib/inventory/dynamic.d.ts.map +1 -1
- package/types/lib/inventory/envcontext.d.ts +92 -8
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts +29 -10
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
- package/types/lib/inventory/goBuildInfo.d.ts +10 -0
- package/types/lib/inventory/goBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/gtfobins.d.ts.map +1 -1
- package/types/lib/inventory/hbom.d.ts.map +1 -1
- package/types/lib/inventory/hbomAnalysis.d.ts +10 -0
- package/types/lib/inventory/hbomAnalysis.d.ts.map +1 -1
- package/types/lib/inventory/hostRuntimeProbe.d.ts +44 -0
- package/types/lib/inventory/hostRuntimeProbe.d.ts.map +1 -0
- package/types/lib/inventory/hostTopology.d.ts.map +1 -1
- package/types/lib/inventory/jvmToolEnv.d.ts +12 -0
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
- package/types/lib/inventory/licenseEnhancer.d.ts.map +1 -1
- package/types/lib/inventory/mavenCentralMirror.d.ts +170 -0
- package/types/lib/inventory/mavenCentralMirror.d.ts.map +1 -0
- package/types/lib/inventory/nodeBuildInfo.d.ts +95 -0
- package/types/lib/inventory/nodeBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/nodeHostProbe.d.ts +15 -0
- package/types/lib/inventory/nodeHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/osPackagePurl.d.ts +99 -0
- package/types/lib/inventory/osPackagePurl.d.ts.map +1 -0
- package/types/lib/inventory/osPackageResolver.d.ts +44 -2
- package/types/lib/inventory/osPackageResolver.d.ts.map +1 -1
- package/types/lib/inventory/osinfo.d.ts +31 -6
- package/types/lib/inventory/osinfo.d.ts.map +1 -1
- package/types/lib/inventory/osqueryTransform.d.ts +51 -0
- package/types/lib/inventory/osqueryTransform.d.ts.map +1 -1
- package/types/lib/inventory/phpBuildInfo.d.ts +142 -0
- package/types/lib/inventory/phpBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/phpHostProbe.d.ts +35 -0
- package/types/lib/inventory/phpHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/rootfsCerts.d.ts +27 -0
- package/types/lib/inventory/rootfsCerts.d.ts.map +1 -0
- package/types/lib/inventory/rubyBuildInfo.d.ts +271 -0
- package/types/lib/inventory/rubyBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/rubyHostProbe.d.ts +47 -0
- package/types/lib/inventory/rubyHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts +38 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/spdx.d.ts +15 -0
- package/types/lib/inventory/spdx.d.ts.map +1 -1
- package/types/lib/inventory/traceRunner.d.ts +4 -1
- package/types/lib/inventory/traceRunner.d.ts.map +1 -1
- package/types/lib/inventory/tracedCrypto.d.ts +22 -0
- package/types/lib/inventory/tracedCrypto.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts +10 -0
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +24 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/parsers/email.d.ts +19 -0
- package/types/lib/parsers/email.d.ts.map +1 -0
- package/types/lib/parsers/largeJson.d.ts +17 -3
- package/types/lib/parsers/largeJson.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +8 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
- package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts +7 -4
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
- package/types/lib/stages/pregen/pregen.d.ts +26 -9
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
package/lib/ecosystems/golem.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
121
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
+
}
|