From 5810e91e324c075916789983693fda7cf0ac2ea5 Mon Sep 17 00:00:00 2001 From: Sylvester Damgaard Date: Tue, 20 Oct 2026 15:41:55 +0200 Subject: [PATCH 1/2] build: rebuild on Go 1.26.10 and x/net v0.60.0 (CVE-2026-78667, -78669, -97031) The 3.8.2 binaries were built by a floating `go-version: '1.26'`, which resolved to 1.26.8 and shipped three HIGH denial-of-service advisories in the standard library -- CVE-2026-78667 (net/http, crafted Range headers), CVE-2026-78669 (net/http2, excessive HTTP/2 SETTINGS frames) and CVE-2026-97031 (crypto/tls, multiple ECH outer extension references) -- plus CVE-2026-78669 a second time in x/net v0.58.0's own HTTP/2. cbox-init serves HTTP on the metrics, readiness and management ports, so none of these were dead code, and the downstream image scan refused to promote any tag. No behaviour change. The toolchain is pinned to 1.26.10 and x/net to v0.60.0 (which carries x/sys v0.48.0 and x/text v0.42.0 with it), and every `go-version` in CI and in the release workflow is pinned to the same patch, so a release is built by the toolchain that was scanned rather than whatever the runner resolves that day. Verified: `trivy rootfs --severity CRITICAL,HIGH --ignore-unfixed` on the linux/amd64 binary reports 0 findings, against 4 on the same source built with 1.26.8 and x/net v0.58.0; `go version -m` on the binary records go1.26.10 and golang.org/x/net v0.60.0. --- .github/workflows/ci.yml | 21 +++--- .github/workflows/release.yml | 2 +- CHANGELOG.md | 17 +++++ go.mod | 19 +++-- go.sum | 12 +-- sbom.json | 136 +++++++++++++++++----------------- 6 files changed, 115 insertions(+), 92 deletions(-) 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..5ebc98c 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,11 +1763,11 @@ "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" ], - "ref": "pkg:golang/github.com/cboxdk/init@0.0.0?type=module" + "ref": "pkg:golang/github.com/cboxdk/init?type=module" }, { "ref": "pkg:golang/github.com/aymanbagabas/go-osc52/v2@v2.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" @@ -2375,7 +2375,7 @@ ], "metadata": { "component": { - "bom-ref": "pkg:golang/github.com/cboxdk/init@0.0.0?type=module", + "bom-ref": "pkg:golang/github.com/cboxdk/init?type=module", "externalReferences": [ { "type": "vcs", @@ -2390,7 +2390,7 @@ } ], "name": "github.com/cboxdk/init", - "purl": "pkg:golang/github.com/cboxdk/init@0.0.0?type=module", + "purl": "pkg:golang/github.com/cboxdk/init?type=module", "type": "application", "version": "0.0.0" }, From d342ebfe170ffd8f8a81a69496c8bc5ce0a27dff Mon Sep 17 00:00:00 2001 From: Sylvester Damgaard Date: Tue, 20 Oct 2026 17:39:16 +0200 Subject: [PATCH 2/2] fix(sbom): pin the root purl version when git cannot resolve one `make sbom-check` failed in CI on a document that passed locally. sbomnorm normalises the root component's version to 0.0.0 in the `version` field unconditionally, but rewrote the version inside the root purl and bom-ref by replacing the literal `name@oldVersion` -- and returned early when oldVersion was empty. 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. Regenerating there therefore wrote `pkg:golang/github.com/cboxdk/init?type=module` where a normal checkout writes `...@0.0.0`, and the result passed `sbom-check` locally (both sides of its diff were generated the same way) while failing it in CI. The purl and bom-ref are now pinned to 0.0.0 whether or not a version was resolved, matching the `version` field that was already being set. A shallow clone and a git worktree now produce byte-identical SBOMs. sbom.json is regenerated: the only change against 3.8.2's committed document is the three dependency bumps from the previous commit, with the root module's identity back to what it has always been. Tests cover both paths, the already-normalised input, and that neither a dependency nor a longer module path sharing the root's prefix is rewritten. --- sbom.json | 6 +- tools/sbomnorm/main.go | 23 ++++-- tools/sbomnorm/main_test.go | 138 ++++++++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 10 deletions(-) create mode 100644 tools/sbomnorm/main_test.go diff --git a/sbom.json b/sbom.json index 5ebc98c..b4dedf2 100644 --- a/sbom.json +++ b/sbom.json @@ -1767,7 +1767,7 @@ "pkg:golang/google.golang.org/grpc@v1.83.2?type=module", "pkg:golang/gopkg.in/yaml.v3@v3.0.1?type=module" ], - "ref": "pkg:golang/github.com/cboxdk/init?type=module" + "ref": "pkg:golang/github.com/cboxdk/init@0.0.0?type=module" }, { "ref": "pkg:golang/github.com/aymanbagabas/go-osc52/v2@v2.0.1?type=module" @@ -2375,7 +2375,7 @@ ], "metadata": { "component": { - "bom-ref": "pkg:golang/github.com/cboxdk/init?type=module", + "bom-ref": "pkg:golang/github.com/cboxdk/init@0.0.0?type=module", "externalReferences": [ { "type": "vcs", @@ -2390,7 +2390,7 @@ } ], "name": "github.com/cboxdk/init", - "purl": "pkg:golang/github.com/cboxdk/init?type=module", + "purl": "pkg:golang/github.com/cboxdk/init@0.0.0?type=module", "type": "application", "version": "0.0.0" }, 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) + } +}