diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d765107..519c482 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - go: ['1.26'] + go: ['1.26.10'] steps: - name: Checkout code @@ -74,13 +74,16 @@ jobs: - name: Set up Go uses: actions/setup-go@v7 with: - go-version: '1.26.6' + go-version: '1.26.10' cache: true - # Pinned to the patch, not the minor: `1.26` resolved to 1.26.5 while the - # advisories below were fixed in 1.26.6, so the scan would have passed on - # a toolchain that still shipped them. Bump this when govulncheck fails - # on the standard library -- that is the gate doing its job. + # Pinned to the patch, not the minor: a floating `1.26` resolves to + # whatever the runner has that day (it shipped 1.26.8 into v3.8.2, which + # still carried CVE-2026-78667, -78669 and -97031), so the scan could + # pass on a toolchain that still shipped the advisories below. Every + # go-version in this repo is pinned to the same patch as the toolchain + # line in go.mod. Bump them together when govulncheck fails on the + # standard library -- that is the gate doing its job. # Known-vulnerable dependencies, and the standard library this is built # against. Nothing in CI reported either before. - name: Run govulncheck @@ -105,7 +108,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v7 with: - go-version: '1.26' + go-version: '1.26.10' - name: golangci-lint uses: golangci/golangci-lint-action@v9 @@ -126,7 +129,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v7 with: - go-version: '1.26' + go-version: '1.26.10' # Validates configs/examples/*.yaml against the real schema with # `check-config`. These files back the documented examples, so a config @@ -148,7 +151,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v7 with: - go-version: '1.26' + go-version: '1.26.10' - name: Build all platforms run: make build-all diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 566b645..2f97574 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,7 +22,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v7 with: - go-version: '1.26' + go-version: '1.26.10' - name: Get version from tag id: version diff --git a/CHANGELOG.md b/CHANGELOG.md index 12f24e7..6558435 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Security + +- **Rebuilt against a patched Go toolchain and a patched `golang.org/x/net`.** + No behaviour change: the same code as 3.8.2. The 3.8.2 binaries were built by + a floating `go-version: '1.26'`, which resolved to 1.26.8 on the day of the + release and carried three denial-of-service advisories in the standard + library — CVE-2026-78667 (`net/http`, crafted HTTP `Range` headers), + CVE-2026-78669 (`net/http2`, excessive HTTP/2 SETTINGS frames) and + CVE-2026-97031 (`crypto/tls`, multiple ECH outer extension references) — and + the vendored `golang.org/x/net` v0.58.0 carried CVE-2026-78669 a second time + in its own HTTP/2 implementation. cbox-init serves HTTP on all three of the + metrics, readiness and management ports, so none of these were dead code. + The toolchain is pinned to 1.26.10 and `golang.org/x/net` to v0.60.0, and + every `go-version` in CI and in the release workflow is pinned to the same + patch, so the toolchain that builds a release is the one the gate scanned + rather than whatever the runner resolves that day. + ## [3.8.2] - 2026-10-08 ### Fixed diff --git a/go.mod b/go.mod index 25b1d93..b8ded77 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,14 @@ module github.com/cboxdk/init go 1.26.0 // Pinned to the patch the supply-chain gate requires: govulncheck flags -// standard-library advisories fixed in 1.26.6, so a local build on an older -// 1.26.x would fail `make check` where CI (which pins 1.26.6) passes. Bump this -// in lockstep with the go-version in .github/workflows/ci.yml when the gate -// next fails on the standard library. -toolchain go1.26.6 +// standard-library advisories fixed in 1.26.10 (CVE-2026-78667 net/http, +// CVE-2026-78669 net/http2, CVE-2026-97031 crypto/tls), so a local build on an +// older 1.26.x would fail `make check` where CI (which pins 1.26.10) passes. +// Bump this in lockstep with the go-version in .github/workflows/ci.yml and +// .github/workflows/release.yml when the gate next fails on the standard +// library -- the published binaries record the toolchain that built them, so +// the pin is what keeps a release scannable and auditable. +toolchain go1.26.10 require ( github.com/cboxdk/fpm-tune v1.3.0 @@ -27,7 +30,7 @@ require ( go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.46.0 go.opentelemetry.io/otel/sdk v1.46.0 go.opentelemetry.io/otel/trace v1.46.0 - golang.org/x/sys v0.47.0 + golang.org/x/sys v0.48.0 google.golang.org/grpc v1.83.2 gopkg.in/yaml.v3 v3.0.1 ) @@ -78,8 +81,8 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0 // indirect go.opentelemetry.io/otel/metric v1.46.0 // indirect go.opentelemetry.io/proto/otlp v1.11.0 // indirect - golang.org/x/net v0.58.0 // indirect - golang.org/x/text v0.41.0 // indirect + golang.org/x/net v0.60.0 // indirect + golang.org/x/text v0.42.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect google.golang.org/protobuf v1.36.12 // indirect diff --git a/go.sum b/go.sum index b0c9c0c..bb5cafd 100644 --- a/go.sum +++ b/go.sum @@ -157,16 +157,16 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= -golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= -golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= +golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU= +golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= -golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= -golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= +golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= +golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= diff --git a/sbom.json b/sbom.json index 82837c7..b4dedf2 100644 --- a/sbom.json +++ b/sbom.json @@ -1569,11 +1569,11 @@ "version": "v1.11.0" }, { - "bom-ref": "pkg:golang/golang.org/x/net@v0.58.0?type=module", + "bom-ref": "pkg:golang/golang.org/x/net@v0.60.0?type=module", "hashes": [ { "alg": "SHA-256", - "content": "ca7586eeba988b871ca5312e3d9d901961e4b5510cf433028fdcb30c4d10ed3a" + "content": "efda79d2d7d9966d09f587e80ec4a2eb7f6a49734657013338f2c2c4cd85b185" } ], "licenses": [ @@ -1584,17 +1584,17 @@ } ], "name": "golang.org/x/net", - "purl": "pkg:golang/golang.org/x/net@v0.58.0?type=module", + "purl": "pkg:golang/golang.org/x/net@v0.60.0?type=module", "scope": "required", "type": "library", - "version": "v0.58.0" + "version": "v0.60.0" }, { - "bom-ref": "pkg:golang/golang.org/x/sys@v0.47.0?type=module", + "bom-ref": "pkg:golang/golang.org/x/sys@v0.48.0?type=module", "hashes": [ { "alg": "SHA-256", - "content": "a3b5c63af6500800c1410e18ed536ad9d456411ec998e516f0ac71e19b0d816b" + "content": "6db5ff8bfe8c813f4154b33d453d6d866c4bd38c9e4c085b133e12c9a75b5e8a" } ], "licenses": [ @@ -1605,17 +1605,17 @@ } ], "name": "golang.org/x/sys", - "purl": "pkg:golang/golang.org/x/sys@v0.47.0?type=module", + "purl": "pkg:golang/golang.org/x/sys@v0.48.0?type=module", "scope": "required", "type": "library", - "version": "v0.47.0" + "version": "v0.48.0" }, { - "bom-ref": "pkg:golang/golang.org/x/text@v0.41.0?type=module", + "bom-ref": "pkg:golang/golang.org/x/text@v0.42.0?type=module", "hashes": [ { "alg": "SHA-256", - "content": "bf3fec780d259d7f3b3ad86ed9fff42f6e11720ad70fdfd81534ae1a38f7ac7f" + "content": "25b3995e07de08f53d81a715b5896226a3a10fece1ac4a8ae0b7dda6695466a2" } ], "licenses": [ @@ -1626,10 +1626,10 @@ } ], "name": "golang.org/x/text", - "purl": "pkg:golang/golang.org/x/text@v0.41.0?type=module", + "purl": "pkg:golang/golang.org/x/text@v0.42.0?type=module", "scope": "required", "type": "library", - "version": "v0.41.0" + "version": "v0.42.0" }, { "bom-ref": "pkg:golang/google.golang.org/genproto/googleapis/api@v0.0.0-20260819154853-08b0e4226688?type=module", @@ -1763,7 +1763,7 @@ "pkg:golang/go.opentelemetry.io/otel/exporters/stdout/stdouttrace@v1.46.0?type=module", "pkg:golang/go.opentelemetry.io/otel/sdk@v1.46.0?type=module", "pkg:golang/go.opentelemetry.io/otel/trace@v1.46.0?type=module", - "pkg:golang/golang.org/x/sys@v0.47.0?type=module", + "pkg:golang/golang.org/x/sys@v0.48.0?type=module", "pkg:golang/google.golang.org/grpc@v1.83.2?type=module", "pkg:golang/gopkg.in/yaml.v3@v3.0.1?type=module" ], @@ -1815,8 +1815,8 @@ "pkg:golang/github.com/tklauser/numcpus@v0.11.0?type=module", "pkg:golang/github.com/xo/terminfo@v0.0.0-20220910002029-abceb7e1c41e?type=module", "pkg:golang/github.com/yusufpapurcu/wmi@v1.2.4?type=module", - "pkg:golang/golang.org/x/sys@v0.47.0?type=module", - "pkg:golang/golang.org/x/text@v0.41.0?type=module", + "pkg:golang/golang.org/x/sys@v0.48.0?type=module", + "pkg:golang/golang.org/x/text@v0.42.0?type=module", "pkg:golang/google.golang.org/protobuf@v1.36.12?type=module" ], "ref": "pkg:golang/github.com/cboxdk/fpm-tune@v1.3.0?type=module" @@ -1832,7 +1832,7 @@ "pkg:golang/github.com/tklauser/go-sysconf@v0.3.16?type=module", "pkg:golang/github.com/tklauser/numcpus@v0.11.0?type=module", "pkg:golang/github.com/yusufpapurcu/wmi@v1.2.4?type=module", - "pkg:golang/golang.org/x/sys@v0.47.0?type=module" + "pkg:golang/golang.org/x/sys@v0.48.0?type=module" ], "ref": "pkg:golang/github.com/cboxdk/phpfpm@v1.2.0?type=module" }, @@ -1864,8 +1864,8 @@ "pkg:golang/github.com/muesli/termenv@v0.16.0?type=module", "pkg:golang/github.com/rivo/uniseg@v0.4.7?type=module", "pkg:golang/github.com/xo/terminfo@v0.0.0-20220910002029-abceb7e1c41e?type=module", - "pkg:golang/golang.org/x/sys@v0.47.0?type=module", - "pkg:golang/golang.org/x/text@v0.41.0?type=module" + "pkg:golang/golang.org/x/sys@v0.48.0?type=module", + "pkg:golang/golang.org/x/text@v0.42.0?type=module" ], "ref": "pkg:golang/github.com/charmbracelet/bubbles@v1.0.0?type=module" }, @@ -1887,8 +1887,8 @@ "pkg:golang/github.com/muesli/termenv@v0.16.0?type=module", "pkg:golang/github.com/rivo/uniseg@v0.4.7?type=module", "pkg:golang/github.com/xo/terminfo@v0.0.0-20220910002029-abceb7e1c41e?type=module", - "pkg:golang/golang.org/x/sys@v0.47.0?type=module", - "pkg:golang/golang.org/x/text@v0.41.0?type=module" + "pkg:golang/golang.org/x/sys@v0.48.0?type=module", + "pkg:golang/golang.org/x/text@v0.42.0?type=module" ], "ref": "pkg:golang/github.com/charmbracelet/bubbletea@v1.3.10?type=module" }, @@ -1902,7 +1902,7 @@ "pkg:golang/github.com/lucasb-eyer/go-colorful@v1.3.0?type=module", "pkg:golang/github.com/mattn/go-runewidth@v0.0.19?type=module", "pkg:golang/github.com/xo/terminfo@v0.0.0-20220910002029-abceb7e1c41e?type=module", - "pkg:golang/golang.org/x/sys@v0.47.0?type=module" + "pkg:golang/golang.org/x/sys@v0.48.0?type=module" ], "ref": "pkg:golang/github.com/charmbracelet/colorprofile@v0.4.1?type=module" }, @@ -1919,7 +1919,7 @@ "pkg:golang/github.com/muesli/termenv@v0.16.0?type=module", "pkg:golang/github.com/rivo/uniseg@v0.4.7?type=module", "pkg:golang/github.com/xo/terminfo@v0.0.0-20220910002029-abceb7e1c41e?type=module", - "pkg:golang/golang.org/x/sys@v0.47.0?type=module" + "pkg:golang/golang.org/x/sys@v0.48.0?type=module" ], "ref": "pkg:golang/github.com/charmbracelet/lipgloss@v1.1.0?type=module" }, @@ -1945,13 +1945,13 @@ "pkg:golang/github.com/mattn/go-runewidth@v0.0.19?type=module", "pkg:golang/github.com/rivo/uniseg@v0.4.7?type=module", "pkg:golang/github.com/xo/terminfo@v0.0.0-20220910002029-abceb7e1c41e?type=module", - "pkg:golang/golang.org/x/sys@v0.47.0?type=module" + "pkg:golang/golang.org/x/sys@v0.48.0?type=module" ], "ref": "pkg:golang/github.com/charmbracelet/x/cellbuf@v0.0.15?type=module" }, { "dependsOn": [ - "pkg:golang/golang.org/x/sys@v0.47.0?type=module" + "pkg:golang/golang.org/x/sys@v0.48.0?type=module" ], "ref": "pkg:golang/github.com/charmbracelet/x/term@v0.2.2?type=module" }, @@ -1976,13 +1976,13 @@ }, { "dependsOn": [ - "pkg:golang/golang.org/x/sys@v0.47.0?type=module" + "pkg:golang/golang.org/x/sys@v0.48.0?type=module" ], "ref": "pkg:golang/github.com/erikgeiser/coninput@v0.0.0-20211004153227-1c3628e74d0f?type=module" }, { "dependsOn": [ - "pkg:golang/golang.org/x/sys@v0.47.0?type=module" + "pkg:golang/golang.org/x/sys@v0.48.0?type=module" ], "ref": "pkg:golang/github.com/fsnotify/fsnotify@v1.10.1?type=module" }, @@ -1997,7 +1997,7 @@ }, { "dependsOn": [ - "pkg:golang/golang.org/x/sys@v0.47.0?type=module" + "pkg:golang/golang.org/x/sys@v0.48.0?type=module" ], "ref": "pkg:golang/github.com/go-ole/go-ole@v1.2.6?type=module" }, @@ -2016,9 +2016,9 @@ "pkg:golang/go.opentelemetry.io/otel/metric@v1.46.0?type=module", "pkg:golang/go.opentelemetry.io/otel/sdk@v1.46.0?type=module", "pkg:golang/go.opentelemetry.io/otel/trace@v1.46.0?type=module", - "pkg:golang/golang.org/x/net@v0.58.0?type=module", - "pkg:golang/golang.org/x/sys@v0.47.0?type=module", - "pkg:golang/golang.org/x/text@v0.41.0?type=module", + "pkg:golang/golang.org/x/net@v0.60.0?type=module", + "pkg:golang/golang.org/x/sys@v0.48.0?type=module", + "pkg:golang/golang.org/x/text@v0.42.0?type=module", "pkg:golang/google.golang.org/genproto/googleapis/api@v0.0.0-20260819154853-08b0e4226688?type=module", "pkg:golang/google.golang.org/genproto/googleapis/rpc@v0.0.0-20260819154853-08b0e4226688?type=module", "pkg:golang/google.golang.org/grpc@v1.83.2?type=module", @@ -2037,7 +2037,7 @@ }, { "dependsOn": [ - "pkg:golang/golang.org/x/sys@v0.47.0?type=module" + "pkg:golang/golang.org/x/sys@v0.48.0?type=module" ], "ref": "pkg:golang/github.com/mattn/go-isatty@v0.0.20?type=module" }, @@ -2059,7 +2059,7 @@ }, { "dependsOn": [ - "pkg:golang/golang.org/x/sys@v0.47.0?type=module" + "pkg:golang/golang.org/x/sys@v0.48.0?type=module" ], "ref": "pkg:golang/github.com/muesli/cancelreader@v0.2.2?type=module" }, @@ -2069,7 +2069,7 @@ "pkg:golang/github.com/lucasb-eyer/go-colorful@v1.3.0?type=module", "pkg:golang/github.com/mattn/go-isatty@v0.0.20?type=module", "pkg:golang/github.com/rivo/uniseg@v0.4.7?type=module", - "pkg:golang/golang.org/x/sys@v0.47.0?type=module" + "pkg:golang/golang.org/x/sys@v0.48.0?type=module" ], "ref": "pkg:golang/github.com/muesli/termenv@v0.16.0?type=module" }, @@ -2078,7 +2078,7 @@ }, { "dependsOn": [ - "pkg:golang/golang.org/x/sys@v0.47.0?type=module" + "pkg:golang/golang.org/x/sys@v0.48.0?type=module" ], "ref": "pkg:golang/github.com/power-devops/perfstat@v0.0.0-20240221224432-82ca36839d55?type=module" }, @@ -2091,9 +2091,9 @@ "pkg:golang/github.com/prometheus/client_model@v0.6.3?type=module", "pkg:golang/github.com/prometheus/common@v0.71.0?type=module", "pkg:golang/github.com/prometheus/procfs@v0.21.1?type=module", - "pkg:golang/golang.org/x/net@v0.58.0?type=module", - "pkg:golang/golang.org/x/sys@v0.47.0?type=module", - "pkg:golang/golang.org/x/text@v0.41.0?type=module", + "pkg:golang/golang.org/x/net@v0.60.0?type=module", + "pkg:golang/golang.org/x/sys@v0.48.0?type=module", + "pkg:golang/golang.org/x/text@v0.42.0?type=module", "pkg:golang/google.golang.org/protobuf@v1.36.12?type=module" ], "ref": "pkg:golang/github.com/prometheus/client_golang@v1.24.1?type=module" @@ -2113,16 +2113,16 @@ "pkg:golang/github.com/prometheus/client_golang@v1.24.1?type=module", "pkg:golang/github.com/prometheus/client_model@v0.6.3?type=module", "pkg:golang/github.com/prometheus/procfs@v0.21.1?type=module", - "pkg:golang/golang.org/x/net@v0.58.0?type=module", - "pkg:golang/golang.org/x/sys@v0.47.0?type=module", - "pkg:golang/golang.org/x/text@v0.41.0?type=module", + "pkg:golang/golang.org/x/net@v0.60.0?type=module", + "pkg:golang/golang.org/x/sys@v0.48.0?type=module", + "pkg:golang/golang.org/x/text@v0.42.0?type=module", "pkg:golang/google.golang.org/protobuf@v1.36.12?type=module" ], "ref": "pkg:golang/github.com/prometheus/common@v0.71.0?type=module" }, { "dependsOn": [ - "pkg:golang/golang.org/x/sys@v0.47.0?type=module" + "pkg:golang/golang.org/x/sys@v0.48.0?type=module" ], "ref": "pkg:golang/github.com/prometheus/procfs@v0.21.1?type=module" }, @@ -2141,7 +2141,7 @@ "pkg:golang/github.com/tklauser/go-sysconf@v0.3.16?type=module", "pkg:golang/github.com/tklauser/numcpus@v0.11.0?type=module", "pkg:golang/github.com/yusufpapurcu/wmi@v1.2.4?type=module", - "pkg:golang/golang.org/x/sys@v0.47.0?type=module", + "pkg:golang/golang.org/x/sys@v0.48.0?type=module", "pkg:golang/gopkg.in/yaml.v3@v3.0.1?type=module" ], "ref": "pkg:golang/github.com/shirou/gopsutil/v3@v3.24.5?type=module" @@ -2155,7 +2155,7 @@ "pkg:golang/github.com/tklauser/go-sysconf@v0.3.16?type=module", "pkg:golang/github.com/tklauser/numcpus@v0.11.0?type=module", "pkg:golang/github.com/yusufpapurcu/wmi@v1.2.4?type=module", - "pkg:golang/golang.org/x/sys@v0.47.0?type=module", + "pkg:golang/golang.org/x/sys@v0.48.0?type=module", "pkg:golang/gopkg.in/yaml.v3@v3.0.1?type=module" ], "ref": "pkg:golang/github.com/shirou/gopsutil/v4@v4.26.8?type=module" @@ -2176,13 +2176,13 @@ { "dependsOn": [ "pkg:golang/github.com/tklauser/numcpus@v0.11.0?type=module", - "pkg:golang/golang.org/x/sys@v0.47.0?type=module" + "pkg:golang/golang.org/x/sys@v0.48.0?type=module" ], "ref": "pkg:golang/github.com/tklauser/go-sysconf@v0.3.16?type=module" }, { "dependsOn": [ - "pkg:golang/golang.org/x/sys@v0.47.0?type=module" + "pkg:golang/golang.org/x/sys@v0.48.0?type=module" ], "ref": "pkg:golang/github.com/tklauser/numcpus@v0.11.0?type=module" }, @@ -2226,7 +2226,7 @@ "pkg:golang/go.opentelemetry.io/otel/sdk@v1.46.0?type=module", "pkg:golang/go.opentelemetry.io/otel/trace@v1.46.0?type=module", "pkg:golang/go.opentelemetry.io/proto/otlp@v1.11.0?type=module", - "pkg:golang/golang.org/x/sys@v0.47.0?type=module", + "pkg:golang/golang.org/x/sys@v0.48.0?type=module", "pkg:golang/google.golang.org/protobuf@v1.36.12?type=module" ], "ref": "pkg:golang/go.opentelemetry.io/otel/exporters/otlp/otlptrace@v1.46.0?type=module" @@ -2246,9 +2246,9 @@ "pkg:golang/go.opentelemetry.io/otel/sdk@v1.46.0?type=module", "pkg:golang/go.opentelemetry.io/otel/trace@v1.46.0?type=module", "pkg:golang/go.opentelemetry.io/proto/otlp@v1.11.0?type=module", - "pkg:golang/golang.org/x/net@v0.58.0?type=module", - "pkg:golang/golang.org/x/sys@v0.47.0?type=module", - "pkg:golang/golang.org/x/text@v0.41.0?type=module", + "pkg:golang/golang.org/x/net@v0.60.0?type=module", + "pkg:golang/golang.org/x/sys@v0.48.0?type=module", + "pkg:golang/golang.org/x/text@v0.42.0?type=module", "pkg:golang/google.golang.org/genproto/googleapis/api@v0.0.0-20260819154853-08b0e4226688?type=module", "pkg:golang/google.golang.org/genproto/googleapis/rpc@v0.0.0-20260819154853-08b0e4226688?type=module", "pkg:golang/google.golang.org/grpc@v1.83.2?type=module", @@ -2267,7 +2267,7 @@ "pkg:golang/go.opentelemetry.io/otel/metric@v1.46.0?type=module", "pkg:golang/go.opentelemetry.io/otel/sdk@v1.46.0?type=module", "pkg:golang/go.opentelemetry.io/otel/trace@v1.46.0?type=module", - "pkg:golang/golang.org/x/sys@v0.47.0?type=module" + "pkg:golang/golang.org/x/sys@v0.48.0?type=module" ], "ref": "pkg:golang/go.opentelemetry.io/otel/exporters/stdout/stdouttrace@v1.46.0?type=module" }, @@ -2292,7 +2292,7 @@ "pkg:golang/go.opentelemetry.io/otel@v1.46.0?type=module", "pkg:golang/go.opentelemetry.io/otel/metric@v1.46.0?type=module", "pkg:golang/go.opentelemetry.io/otel/trace@v1.46.0?type=module", - "pkg:golang/golang.org/x/sys@v0.47.0?type=module" + "pkg:golang/golang.org/x/sys@v0.48.0?type=module" ], "ref": "pkg:golang/go.opentelemetry.io/otel/sdk@v1.46.0?type=module" }, @@ -2306,9 +2306,9 @@ { "dependsOn": [ "pkg:golang/github.com/grpc-ecosystem/grpc-gateway/v2@v2.30.0?type=module", - "pkg:golang/golang.org/x/net@v0.58.0?type=module", - "pkg:golang/golang.org/x/sys@v0.47.0?type=module", - "pkg:golang/golang.org/x/text@v0.41.0?type=module", + "pkg:golang/golang.org/x/net@v0.60.0?type=module", + "pkg:golang/golang.org/x/sys@v0.48.0?type=module", + "pkg:golang/golang.org/x/text@v0.42.0?type=module", "pkg:golang/google.golang.org/genproto/googleapis/api@v0.0.0-20260819154853-08b0e4226688?type=module", "pkg:golang/google.golang.org/genproto/googleapis/rpc@v0.0.0-20260819154853-08b0e4226688?type=module", "pkg:golang/google.golang.org/grpc@v1.83.2?type=module", @@ -2318,22 +2318,22 @@ }, { "dependsOn": [ - "pkg:golang/golang.org/x/sys@v0.47.0?type=module", - "pkg:golang/golang.org/x/text@v0.41.0?type=module" + "pkg:golang/golang.org/x/sys@v0.48.0?type=module", + "pkg:golang/golang.org/x/text@v0.42.0?type=module" ], - "ref": "pkg:golang/golang.org/x/net@v0.58.0?type=module" + "ref": "pkg:golang/golang.org/x/net@v0.60.0?type=module" }, { - "ref": "pkg:golang/golang.org/x/sys@v0.47.0?type=module" + "ref": "pkg:golang/golang.org/x/sys@v0.48.0?type=module" }, { - "ref": "pkg:golang/golang.org/x/text@v0.41.0?type=module" + "ref": "pkg:golang/golang.org/x/text@v0.42.0?type=module" }, { "dependsOn": [ - "pkg:golang/golang.org/x/net@v0.58.0?type=module", - "pkg:golang/golang.org/x/sys@v0.47.0?type=module", - "pkg:golang/golang.org/x/text@v0.41.0?type=module", + "pkg:golang/golang.org/x/net@v0.60.0?type=module", + "pkg:golang/golang.org/x/sys@v0.48.0?type=module", + "pkg:golang/golang.org/x/text@v0.42.0?type=module", "pkg:golang/google.golang.org/genproto/googleapis/rpc@v0.0.0-20260819154853-08b0e4226688?type=module", "pkg:golang/google.golang.org/grpc@v1.83.2?type=module", "pkg:golang/google.golang.org/protobuf@v1.36.12?type=module" @@ -2357,9 +2357,9 @@ "pkg:golang/go.opentelemetry.io/otel/metric@v1.46.0?type=module", "pkg:golang/go.opentelemetry.io/otel/sdk@v1.46.0?type=module", "pkg:golang/go.opentelemetry.io/otel/trace@v1.46.0?type=module", - "pkg:golang/golang.org/x/net@v0.58.0?type=module", - "pkg:golang/golang.org/x/sys@v0.47.0?type=module", - "pkg:golang/golang.org/x/text@v0.41.0?type=module", + "pkg:golang/golang.org/x/net@v0.60.0?type=module", + "pkg:golang/golang.org/x/sys@v0.48.0?type=module", + "pkg:golang/golang.org/x/text@v0.42.0?type=module", "pkg:golang/google.golang.org/genproto/googleapis/api@v0.0.0-20260819154853-08b0e4226688?type=module", "pkg:golang/google.golang.org/genproto/googleapis/rpc@v0.0.0-20260819154853-08b0e4226688?type=module", "pkg:golang/google.golang.org/protobuf@v1.36.12?type=module" diff --git a/tools/sbomnorm/main.go b/tools/sbomnorm/main.go index b247cdd..e1d57bf 100644 --- a/tools/sbomnorm/main.go +++ b/tools/sbomnorm/main.go @@ -4,7 +4,8 @@ // generated rather than on the dependency set, which would make the committed // SBOM drift on every commit and on every developer machine: // -// - the root component's version is a git pseudo-version derived from HEAD +// - the root component's version is a git pseudo-version derived from HEAD, +// or missing entirely where git cannot resolve one // - purls carry the generating host's goos/goarch qualifiers // - metadata.tools records hashes of the cyclonedx-gomod binary, which differ // per platform and per build of the tool @@ -77,21 +78,29 @@ func normaliseRootVersion(doc map[string]any) { return } - oldVersion, _ := component["version"].(string) component["version"] = rootVersion name, _ := component["name"].(string) - if oldVersion == "" || name == "" { + if name == "" { return } - // The same version appears inside the root purl and bom-ref. - old := name + "@" + oldVersion - replacement := name + "@" + rootVersion + // The version also appears inside the root purl and bom-ref, and it may be + // absent there: cyclonedx-gomod cannot resolve a version for the main + // module in every checkout (inside a git worktree it warns "failed to + // determine version of main module" and emits a version-less purl), and + // normalising only the `version` field would then leave a document that + // differs from the one CI generates -- which passes `sbom-check` locally + // and fails it in CI. Pin the purl to rootVersion in both cases. + // + // The trailing `?` or `"` is what keeps this off a longer module path that + // merely starts with the root's name. + rootPurl := regexp.MustCompile(`pkg:golang/` + regexp.QuoteMeta(name) + `(?:@[^"?]*)?([?"])`) + replacement := "pkg:golang/" + name + "@" + rootVersion + "$1" if raw, err := json.Marshal(doc); err == nil { var replaced map[string]any - if err := json.Unmarshal(bytes.ReplaceAll(raw, []byte(old), []byte(replacement)), &replaced); err == nil { + if err := json.Unmarshal(rootPurl.ReplaceAll(raw, []byte(replacement)), &replaced); err == nil { for k, v := range replaced { doc[k] = v } diff --git a/tools/sbomnorm/main_test.go b/tools/sbomnorm/main_test.go new file mode 100644 index 0000000..c0cc34d --- /dev/null +++ b/tools/sbomnorm/main_test.go @@ -0,0 +1,138 @@ +package main + +import ( + "encoding/json" + "strings" + "testing" +) + +// doc builds a minimal SBOM shaped like the parts normaliseRootVersion +// touches: the root component plus the dependency graph that repeats its +// bom-ref, and one real dependency that must be left alone. +func doc(rootPurl string, version any) map[string]any { + component := map[string]any{ + "name": "github.com/cboxdk/init", + "bom-ref": rootPurl, + "purl": rootPurl, + "licenses": []any{"MIT"}, + } + if version != nil { + component["version"] = version + } + return map[string]any{ + "metadata": map[string]any{"component": component}, + "dependencies": []any{ + map[string]any{"ref": rootPurl}, + map[string]any{"ref": "pkg:golang/golang.org/x/net@v0.60.0?type=module"}, + }, + } +} + +func rootRefs(t *testing.T, d map[string]any) (purl, bomRef, depRef string) { + t.Helper() + raw, err := json.Marshal(d) + if err != nil { + t.Fatalf("marshal: %v", err) + } + var got struct { + Metadata struct { + Component struct { + Purl string `json:"purl"` + BomRef string `json:"bom-ref"` + Version string `json:"version"` + } `json:"component"` + } `json:"metadata"` + Dependencies []struct { + Ref string `json:"ref"` + } `json:"dependencies"` + } + if err := json.Unmarshal(raw, &got); err != nil { + t.Fatalf("unmarshal: %v", err) + } + if got.Metadata.Component.Version != rootVersion { + t.Errorf("version = %q, want %q", got.Metadata.Component.Version, rootVersion) + } + return got.Metadata.Component.Purl, got.Metadata.Component.BomRef, got.Dependencies[0].Ref +} + +func TestNormaliseRootVersion(t *testing.T) { + const want = "pkg:golang/github.com/cboxdk/init@" + rootVersion + "?type=module" + + tests := []struct { + name string + rootPurl string + version any + }{ + { + // The ordinary case: a checkout where git resolves a version. + name: "pseudo-version", + rootPurl: "pkg:golang/github.com/cboxdk/init@v3.8.2-2-ge91c6bd?type=module", + version: "v3.8.2-2-ge91c6bd", + }, + { + // Inside a git worktree cyclonedx-gomod cannot resolve a version + // and emits a version-less purl. Normalising only the `version` + // field there produced an SBOM that differed from CI's. + name: "version-less purl", + rootPurl: "pkg:golang/github.com/cboxdk/init?type=module", + version: nil, + }, + { + name: "empty version string", + rootPurl: "pkg:golang/github.com/cboxdk/init?type=module", + version: "", + }, + { + // Idempotent: re-running must not stack versions. + name: "already normalised", + rootPurl: want, + version: rootVersion, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + d := doc(tc.rootPurl, tc.version) + normaliseRootVersion(d) + + purl, bomRef, depRef := rootRefs(t, d) + for field, got := range map[string]string{"purl": purl, "bom-ref": bomRef, "dependencies[0].ref": depRef} { + if got != want { + t.Errorf("%s = %q, want %q", field, got, want) + } + } + }) + } +} + +// A dependency must not be rewritten just because the root is. +func TestNormaliseRootVersionLeavesDependencies(t *testing.T) { + d := doc("pkg:golang/github.com/cboxdk/init?type=module", nil) + normaliseRootVersion(d) + + raw, err := json.Marshal(d) + if err != nil { + t.Fatalf("marshal: %v", err) + } + if !strings.Contains(string(raw), "pkg:golang/golang.org/x/net@v0.60.0?type=module") { + t.Errorf("dependency purl was rewritten: %s", raw) + } +} + +// A module path that merely starts with the root's name keeps its own version. +func TestNormaliseRootVersionDoesNotMatchLongerPath(t *testing.T) { + d := doc("pkg:golang/github.com/cboxdk/init?type=module", nil) + deps := d["dependencies"].([]any) + d["dependencies"] = append(deps, map[string]any{ + "ref": "pkg:golang/github.com/cboxdk/init/tools@v1.0.0?type=module", + }) + normaliseRootVersion(d) + + raw, err := json.Marshal(d) + if err != nil { + t.Fatalf("marshal: %v", err) + } + if !strings.Contains(string(raw), "pkg:golang/github.com/cboxdk/init/tools@v1.0.0?type=module") { + t.Errorf("longer module path was rewritten: %s", raw) + } +}