UNPKG

snyk-go-plugin

Version:
125 lines 5.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.escapeModulePath = exports.buildDistributionUrl = exports.decodeH1ToSha256Hex = exports.getComponentMetadataLabels = exports.parseGoSum = void 0; const H1_PREFIX = 'h1:'; const SHA256_BYTES = 32; const DEFAULT_GOPROXY = 'https://proxy.golang.org'; const GO_MOD_SUFFIX = '/go.mod'; /** * Parse a go.sum file into a map of `<module>@<version>` -> its file-tree * (`h1:`) hash. go.sum records two lines per module version: * <module> <version> h1:<base64>= -> the module .zip hash (kept here) * <module> <version>/go.mod h1:<base64>= -> the go.mod hash (ignored) * We read the hash from go.sum rather than `go list` because `Module.Sum` is * only emitted by go >= 1.23, whereas the go.sum format is stable across all * supported go versions. See https://go.dev/ref/mod#go-sum-files */ function parseGoSum(goSumContents) { const hashes = {}; for (const rawLine of goSumContents.split('\n')) { const [modulePath, versionField, hash] = rawLine.trim().split(/\s+/); if (!modulePath || !versionField || !hash) { continue; // blank or malformed line } if (versionField.endsWith(GO_MOD_SUFFIX)) { continue; // go.mod hash, not the module file-tree hash } hashes[`${modulePath}@${versionField}`] = hash; } return hashes; } exports.parseGoSum = parseGoSum; /** * Build the component-metadata labels for a single Go module version. Produces: * - `hash:sha-256` the module's file-tree hash, decoded to lowercase hex * - `distribution:url` the module proxy download URL for the .zip * `h1` is the module's `h1:` hash as recorded in go.sum. `goproxy` is the * effective GOPROXY value as reported by `go env GOPROXY` (see * buildDistributionUrl). Either label is omitted when it cannot be produced * (missing/invalid hash, or no proxy to derive a URL from). */ function getComponentMetadataLabels(modulePath, version, h1, goproxy) { const labels = {}; const sha256Hex = decodeH1ToSha256Hex(h1); if (sha256Hex) { labels['hash:sha-256'] = sha256Hex; } const distributionUrl = buildDistributionUrl(modulePath, version, goproxy); if (distributionUrl) { labels['distribution:url'] = distributionUrl; } return labels; } exports.getComponentMetadataLabels = getComponentMetadataLabels; /** * A go module `h1:` hash is the base64-encoded SHA-256 of the module's dirhash * manifest (see https://go.dev/ref/mod#go-sum-files). SBOM consumers expect a * lowercase hex digest, so decode base64 -> hex. Returns undefined when the * value is missing or not a well-formed 32-byte digest. */ function decodeH1ToSha256Hex(h1) { if (!h1 || !h1.startsWith(H1_PREFIX)) { return undefined; } const base64 = h1.slice(H1_PREFIX.length); const buf = Buffer.from(base64, 'base64'); // Guard against short/garbage input: require an exact SHA-256 digest and a // clean base64 round-trip (Buffer.from is otherwise lenient). if (buf.length !== SHA256_BYTES || buf.toString('base64') !== base64) { return undefined; } return buf.toString('hex'); } exports.decodeH1ToSha256Hex = decodeH1ToSha256Hex; /** * Derive the module proxy download URL for a module version, e.g. * https://proxy.golang.org/github.com/!burnt!sushi/toml/@v/v1.2.3.zip * `goproxy` is the effective GOPROXY value as reported by `go env GOPROXY` * (which already applies env-var > go-env-file > built-in-default precedence). * Honours it when it points at an http(s) proxy; returns undefined for * `off`/`direct`/private setups where a public URL would be misleading. */ function buildDistributionUrl(modulePath, version, goproxy) { const proxy = resolveGoProxyBase(goproxy); if (!proxy) { return undefined; } return `${proxy}/${escapeModulePath(modulePath)}/@v/${escapeModulePath(version)}.zip`; } exports.buildDistributionUrl = buildDistributionUrl; function resolveGoProxyBase(goproxy) { if (!goproxy) { // `go env GOPROXY` returned nothing (or the lookup failed): fall back to // the public proxy, which is also go's built-in default. return DEFAULT_GOPROXY; } // GOPROXY is a list separated by commas or pipes; the first entry wins. const first = goproxy.split(/[,|]/)[0].trim(); if (!/^https?:\/\//.test(first)) { // "off", "direct", "none" or empty: no proxy URL we can safely derive. return undefined; } let parsed; try { parsed = new URL(first); } catch { return undefined; } // Private GOPROXY setups sometimes embed basic-auth credentials in the URL // (e.g. https://user:pass@proxy.corp/). Strip them so they never end up in // component metadata, which is attached to scan results and shipped off-host. parsed.username = ''; parsed.password = ''; return parsed.toString().replace(/\/+$/, ''); } /** * Go escapes module paths and versions for case-insensitive filesystems by * replacing each uppercase letter with `!` followed by its lowercase form. * See https://go.dev/ref/mod#goproxy-protocol */ function escapeModulePath(value) { return value.replace(/[A-Z]/g, (c) => '!' + c.toLowerCase()); } exports.escapeModulePath = escapeModulePath; //# sourceMappingURL=component-metadata.js.map