From 18666944cd037b15d43c59ab2420be038554cc02 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Aug 2026 17:12:57 +0000 Subject: [PATCH 1/4] Add govulncheck CI job and Dependabot configuration CI built, vetted, tested, and linted but never scanned the Go toolchain or module dependencies for known vulnerabilities, and no Dependabot config existed to keep SHA-pinned Actions or gopkg.in/yaml.v3 current (the sonarqube job's dependabot[bot] guard was already prepared for Dependabot, but the config was never committed). Add a govulncheck job (reachability-based, so noise is low) that runs on every push/PR plus a weekly schedule so a newly-published CVE against an unchanged codebase gets caught without waiting for the next commit. Add .github/dependabot.yml for the github-actions and gomod ecosystems. Document both in ARCHITECTURE.md's CI summary and add govulncheck to CONTRIBUTING.md's pre-PR checklist. --- .github/dependabot.yml | 17 +++++++++++++++++ .github/workflows/ci.yml | 24 ++++++++++++++++++++++++ docs/ARCHITECTURE.md | 8 ++++++-- docs/CONTRIBUTING.md | 7 ++++--- 4 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..0146bbc --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,17 @@ +version: 2 +updates: + # Keep the SHA-pinned GitHub Actions current. Pinning to a SHA is only safe + # if something moves the pin forward when the action publishes a fix. + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + commit-message: + prefix: "ci" + + - package-ecosystem: gomod + directory: "/" + schedule: + interval: weekly + commit-message: + prefix: "deps" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b32ca5b..9d1a3fc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,10 @@ on: branches: [main] pull_request: branches: [main] + schedule: + # Surface newly-published CVEs against an otherwise unchanged codebase + # without waiting for the next push/PR. + - cron: "0 6 * * 1" permissions: contents: read @@ -58,6 +62,26 @@ jobs: env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + govulncheck: + name: Vulnerability scan + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + + - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 + with: + go-version-file: go.mod + cache: true + + # @latest is a deliberate exception to this repo's usual pinning + # policy: we want the newest vulnerability database and analysis + # logic, not a frozen snapshot of both. + - name: Install govulncheck + run: go install golang.org/x/vuln/cmd/govulncheck@latest + + - name: Run govulncheck + run: govulncheck ./... + cross-compile: runs-on: ubuntu-latest needs: build-test-lint diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 08fd1d0..7345d47 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -410,6 +410,10 @@ separate cross-compile job (build-only, `arm`/`arm64`) so a target-platform buil caught even though the runner itself is `amd64` and cannot execute Pi-only code paths. A `sonarqube` job (skipped for Dependabot PRs, which don't receive repository secrets) regenerates coverage as a Go coverage profile and uploads it to SonarCloud together with -the sources, per `sonar-project.properties` at the repo root. +the sources, per `sonar-project.properties` at the repo root. A `govulncheck` job runs +`govulncheck ./...` (reachability-based, so it only fails on vulnerabilities actually +reachable from this code) on every push/PR plus a weekly schedule, so a newly-published CVE +against an unchanged codebase is surfaced without waiting for the next commit. Actions are pinned to commit SHAs (not floating tags) so a compromised or rewritten action -release can't silently change what CI executes. +release can't silently change what CI executes; `.github/dependabot.yml` keeps those pins +and `go.mod` dependencies current. diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 84beae6..8101ba1 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -84,9 +84,10 @@ When a PR is related to an issue, use the `Closes #issuenumber` syntax so the is to the PR automatically and closes when the PR is merged. Follow the PR template in [`.github/pull_request_template.md`](../.github/pull_request_template.md). -Run `make build`, `go vet ./...`, `make test`, and `make lint` locally before opening the -PR — CI runs the same checks (plus a cross-compile check for `arm`/`arm64`) and will not -merge on a red build. +Run `make build`, `go vet ./...`, `make test`, `make lint`, and `govulncheck ./...` (install +via `go install golang.org/x/vuln/cmd/govulncheck@latest`) locally before opening the PR — +CI runs the same checks (plus a cross-compile check for `arm`/`arm64`) and will not merge on +a red build. ## Code style From 3a67d8e72d8712cbda3f66ee23ebb6129d38f31e Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Aug 2026 17:23:54 +0000 Subject: [PATCH 2/4] Pin govulncheck to a fixed version instead of @latest SonarCloud's Quality Gate failed the PR with a MAJOR security finding (githubactions:S8545, "Dependency versions are not predictable") on the "go install ...@latest" line: an unpinned/floating dependency install in CI is a supply-chain risk, the same class of issue the repo's SHA-pinned Actions already guard against. Pin to v1.7.0 (the version @latest resolved to) instead. The vulnerability database itself is fetched fresh over the network at scan time regardless of the installed tool's version, so this keeps the main benefit (current CVE data) while making the build reproducible. --- .github/workflows/ci.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d1a3fc..67f68aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,11 +73,13 @@ jobs: go-version-file: go.mod cache: true - # @latest is a deliberate exception to this repo's usual pinning - # policy: we want the newest vulnerability database and analysis - # logic, not a frozen snapshot of both. + # Pin the tool version for predictable builds (unpinned "@latest" trips + # SonarCloud's S8545 dependency-pinning check). The vulnerability + # database itself is still fetched fresh at run time over the network, + # independent of this pin, so scans stay current; only the analysis + # logic is frozen until this is bumped by hand. - name: Install govulncheck - run: go install golang.org/x/vuln/cmd/govulncheck@latest + run: go install golang.org/x/vuln/cmd/govulncheck@v1.7.0 - name: Run govulncheck run: govulncheck ./... From 105b05060bca4c041788d48f4a7f2be9923cf276 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Aug 2026 17:30:57 +0000 Subject: [PATCH 3/4] Raise go.mod to Go 1.26.7 and bump golangci-lint to v2.13.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Go 1.22 fell out of the two-most-recent-majors support window, so it receives no further security patches. Since PiMonitor ships as a single statically-linked binary with the standard library baked in, the toolchain version is a dependency-security property: the new govulncheck CI job (added alongside this) found 31 real, reachable CVEs in net/http, crypto/tls, crypto/x509, net/url and others that are only fixed in Go 1.23-1.25 — none of them fixable by updating gopkg.in/yaml.v3, since they're in the standard library itself. Go 1.26.7 is the newest patch of the older of the two currently supported majors (1.26 and 1.27), verified against the Go module proxy's toolchain list. No language features were adopted opportunistically; this is a mechanical version bump per docs/CONTRIBUTING.md's now-documented policy of tracking a supported release. golangci-lint v2.12.2 was built with Go 1.25 and refuses to lint a go.mod targeting a newer Go version ("the Go language version used to build golangci-lint is lower than the targeted Go version"), so it is bumped to v2.13.1 (built with Go 1.26.7) to match, as anticipated by issue #107. Closes #107. --- .github/workflows/ci.yml | 2 +- README.md | 2 +- docs/CONTRIBUTING.md | 13 ++++++++++--- go.mod | 2 +- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 67f68aa..1801f9d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: - name: Lint uses: golangci/golangci-lint-action@9fae48acfc02a90574d7c304a1758ef9895495fa # v7.0.1 with: - version: v2.12.2 + version: v2.13.1 sonarqube: name: SonarQube diff --git a/README.md b/README.md index 59ecf92..f33aa19 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ see [`SECURITY.md`](SECURITY.md) for the full threat model. ## Building -Requires Go 1.22+. +Requires Go 1.26+. ```sh make build # native build, for local development -> bin/pimonitor diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 8101ba1..69bf6f8 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -13,14 +13,21 @@ installed it, you can download it [here](https://git-scm.com/downloads) or, if y a GUI-based approach, try [GitHub Desktop](https://desktop.github.com/). Once Git is installed, you'll also need the Go version this project targets (currently -**Go 1.22+**, see [`go.mod`](../go.mod)). Instructions and downloads for your preferred OS +**Go 1.26+**, see [`go.mod`](../go.mod)). Instructions and downloads for your preferred OS can be found [here](https://go.dev/dl/). -For linting, install `golangci-lint` matching the version CI uses (currently `v2.12.2`, +> [!NOTE] +> The `go` directive in `go.mod` tracks a currently-supported Go release (Go supports the +> two most recent major releases). Since PiMonitor ships as a single statically-linked +> binary, the toolchain version is a dependency-security property, not just a build detail +> — it's raised whenever the declared version falls out of that support window, independent +> of any new language features being adopted. + +For linting, install `golangci-lint` matching the version CI uses (currently `v2.13.1`, see [`.github/workflows/ci.yml`](../.github/workflows/ci.yml)): ```sh -go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2 +go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.13.1 ``` > [!IMPORTANT] diff --git a/go.mod b/go.mod index fd7be98..5b22d8d 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module github.com/larslaskowski/pimonitor -go 1.22 +go 1.26.7 require gopkg.in/yaml.v3 v3.0.1 From 4bb7305ddbf0149abbf369e61f17a1fe5631be17 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Aug 2026 17:34:37 +0000 Subject: [PATCH 4/4] Install govulncheck as a go.mod tool dependency, not go install@version SonarCloud's Quality Gate still failed after pinning to "go install .../govulncheck@v1.7.0" (githubactions:S8545, "Dependency versions are not predictable. Use a lock-file enforcing command instead."): the rule flags any `go install pkg@version` in CI regardless of pinning, since it isn't verified against this repo's own go.sum. Go 1.24+ (available now that go.mod targets 1.26.7) supports `tool` dependencies: `go get -tool golang.org/x/vuln/cmd/govulncheck@v1.7.0` records it under a `tool` directive with a real go.sum-checksummed require entry, and `go tool govulncheck ./...` runs that exact, verified build - a genuine lock-file-enforcing command. This also means Dependabot's gomod ecosystem can now track and bump govulncheck automatically, closing the gap the original issue noted ("it must be a tool dependency, which this project does not otherwise have"). The added golang.org/x/vuln/x/tools/x/mod/etc. requires are tool-only: `go list -deps ./cmd/pimonitor` confirms none of them reach the shipped binary. --- .github/workflows/ci.yml | 15 +++++++-------- go.mod | 11 +++++++++++ go.sum | 22 ++++++++++++++++++++++ 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1801f9d..e69f519 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,16 +73,15 @@ jobs: go-version-file: go.mod cache: true - # Pin the tool version for predictable builds (unpinned "@latest" trips - # SonarCloud's S8545 dependency-pinning check). The vulnerability + # govulncheck is a go.mod "tool" dependency (go 1.24+), so `go tool` runs + # a go.sum-verified, checksummed version instead of an unpinned + # `go install ...@latest` (which trips SonarCloud's S8545 + # dependency-pinning check). Dependabot's gomod ecosystem tracks and + # bumps it like any other module requirement. The vulnerability # database itself is still fetched fresh at run time over the network, - # independent of this pin, so scans stay current; only the analysis - # logic is frozen until this is bumped by hand. - - name: Install govulncheck - run: go install golang.org/x/vuln/cmd/govulncheck@v1.7.0 - + # independent of the tool's pinned version, so scans stay current. - name: Run govulncheck - run: govulncheck ./... + run: go tool govulncheck ./... cross-compile: runs-on: ubuntu-latest diff --git a/go.mod b/go.mod index 5b22d8d..65f1a87 100644 --- a/go.mod +++ b/go.mod @@ -3,3 +3,14 @@ module github.com/larslaskowski/pimonitor go 1.26.7 require gopkg.in/yaml.v3 v3.0.1 + +require ( + golang.org/x/mod v0.39.0 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/telemetry v0.0.0-20260811182544-a038080d80e5 // indirect + golang.org/x/tools v0.49.0 // indirect + golang.org/x/vuln v1.7.0 // indirect +) + +tool golang.org/x/vuln/cmd/govulncheck diff --git a/go.sum b/go.sum index a62c313..64ac71b 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,25 @@ +github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU= +github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786/go.mod h1:apVn/GCasLZUVpAJ6oWAuyP7Ne7CEsQbTnc0plM3m+o= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +golang.org/x/mod v0.39.0 h1:UF5zwQdCRRUpHfyPwr7d4UrGiVeldIsogtzWVnczL74= +golang.org/x/mod v0.39.0/go.mod h1:bvIbwjQ0HUFFf5AKukeeYQG4ZBUG9yxQbR9aEweIwYY= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/telemetry v0.0.0-20260811182544-a038080d80e5 h1:ZUSxONxc981v7AW7QUg+I9WwZzSTTJ019ENBYr5pV/Q= +golang.org/x/telemetry v0.0.0-20260811182544-a038080d80e5/go.mod h1:LVehoXe41cL5SCVQilsV7Gg6BNG+Js6P9PhSbYTIUkQ= +golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= +golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= +golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= +golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= +golang.org/x/vuln v1.7.0 h1:4MQBuhmXbz2uepNJrf3v+aaZLGDqw1JluwYboegA1qg= +golang.org/x/vuln v1.7.0/go.mod h1:Xw7zvU3e1bsCYYBXu+w4wcn2Kgn27f34WBCTw8LL5Us= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=