fix(deps): move to Go 1.26.8 to resolve five govulncheck findings - #419
Merged
Conversation
govulncheck reported the build as affected by five vulnerabilities that Kensa
actually calls, not merely imports. Reproduced on an unmodified checkout of
main before changing anything:
GO-2026-6218 net/url internal/output/pdf.go:252, via maroto
GO-2026-6090 crypto/tls internal/output/pdf.go:252, via maroto
GO-2026-5026 net/http internal/output/pdf.go:252, via maroto
GO-2026-6088 encoding/xml internal/catalog/stig.go:58, xml.Unmarshal
GO-2026-5972 encoding/asn1 internal/evidence/load.go:107, via
x509.ParsePKIXPublicKey
Two of those sit on paths that read input a host operator did not author. The
XML one is reached from STIG benchmark parsing, and the ASN.1 one from reading
a public key during evidence verification, which is the path `kensa verify`
uses to decide whether a remediation record is genuine.
All five are standard-library issues fixed in Go 1.26.6, so the fix is the
toolchain rather than any dependency. Every CI job resolves its Go from
go-version-file: go.mod, the release workflow does the same, and the Makefile
derives GOTOOLCHAIN for the vuln scan from the same directive, so moving the
one line moves all of them together.
After the bump the scan reports zero called vulnerabilities, down from five.
The uncalled counts fall as well, from three to one in imported packages and
from eighteen to seventeen in required modules, which is consistent with
standard-library fixes rather than with anything being hidden.
The comment in .golangci.yml that quotes the go.mod pin is updated with it.
The one further down, naming the toolchain the linter binary itself was built
under, is left alone: that is a different toolchain, and it was not measured
here. The build requirement stated in the operator guide is corrected, since
building now needs 1.26.6 rather than 1.26.5.
OpenWatch, the only consumer of the frozen api/, already declares go 1.26.6,
so raising the directive converges with it instead of forcing a bump there.
Verified: 84 packages pass, the release build is reproducible under 1.26.6,
go mod tidy stays a no-op, golangci-lint reports 0 issues, all 151 specs meet
their thresholds, and the docs, manpage, catalog, comment-lint and CLI smoke
gates pass.
Two pre-existing data races surfaced while running the suite under -race and
are NOT caused by this change; each was reproduced on an unmodified main. The
signalfd one fails under both toolchains with an empty bin/. The dispatcher
one, concurrent writes to a bytes.Buffer in OpenAgent, appears on main under
1.26.5 as soon as bin/ is populated, and disappears on this branch once bin/
is removed. CI runs go test without -race, so neither is gated today. They
need their own issue.
Founder direction: take the current Go 1.26 patch instead of the oldest release that happens to clear the findings. Go 1.26.6 is where the standard-library repairs for all five called findings shipped. Go 1.26.8 carries those repairs and adds the later patch fixes in the line: net/http in 1.26.7, then cgo, the compiler, the runtime, debug/elf and os in 1.26.8. Kensa was not shown to exercise those later fixes; they are taken because they arrive with the release, and the changelog says so rather than implying broader coverage. Confirmed against the official release history that 1.26.8, released 2026-09-01, is the highest and current Go 1.26 patch.
…cessary
The suppression existed because the staticcheck bundled with golangci-lint
v2.12.2, built under go1.26.5, stopped treating testing.T.Fatal as a call that
does not return. It then read `if x == nil { t.Fatal(...) }` as a guard that
falls through, and reported the following dereference as a possible nil
dereference. CI installs the linter with install-mode goinstall under
GOTOOLCHAIN=local, so the linter is rebuilt with whatever Go the job selected,
and this change selects a different one. The comment could not simply be
carried forward with a new number in it.
Measured instead, in a clean worktree at this head, with the linter installed
exactly as CI installs it and reporting "built with go1.26.8": the whole tree
lints clean with the suppression gone.
A pair of controls establishes that the silence means something. A test
function whose nil guard does NOT terminate is still reported, at both the
guard and the dereference, so SA5011 is live on test files. A test function
whose guard calls t.Fatal is not reported, which is the shape the suppression
was covering and is exactly the case that used to be wrong. 252 test files
call t.Fatal, and none of them produce a finding.
So the rule is removed rather than re-dated, and production SA5011 enforcement
is unchanged because it was never suppressed outside _test.go.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Moves the
godirective to 1.26.8, the current Go 1.26 patch, closing fivemodule-scope findings that govulncheck classifies as called.
That classification is static and module-level. It is not demonstrated runtime
reachability for all five; the dossier below states what was established for
each one.
Confirmed against the official release history: 1.26.8 (2026-09-01) is the
highest and current 1.26 patch. 1.26.6 (2026-08-13) is where the repairs
for all five findings shipped; 1.26.8 carries those plus the later patch fixes
in the line —
net/httpin 1.26.7, and cgo, the compiler, the runtime,debug/elfandosin 1.26.8. Kensa was not shown to exercise those laterfixes; they arrive with the release.
Scan result
Fresh scans on two clean worktrees, base at
0152886(the PR's actual mergebase) and head at
18fe8ee.make vulnexitEvidence dossier
Each base finding, classified by what was actually established. No path below
is described as observed production behavior unless it was observed.
GO-2026-5972 —
encoding/asn1asn1.Unmarshal, viax509.ParsePKIXPublicKeyinternal/evidence/load.go:107(evidence.LoadVerifier)kensa verify—cmd/kensa/verify.go:137, the shippedkensabinary.pubfile in the operator's trust directory. The envelope selects which file viasigning_key_id(constrained to 64-char lower-hex), but the bytes parsed are the local operator-placed file, not envelope contentkensa;kensa-keygenalso linksencoding/asn1)evidence: LoadVerifier: PKIX parse …: asn1: structure error: tags don't match … publicKeyInfo @2— the text is verbatim fromencoding/asn1GO-2026-6088 —
encoding/xmlxml.Unmarshal—internal/catalog/stig.go:58catalog.parseSTIG, reached fromStore.IngestSTIG(internal/catalog/store.go:62)internal/catalogXCCDF parsing path is not present in shipped Kensa artifactscatalog/sources/manifest.json) selects five vendored STIG XCCDF files. The catalog-drift workflow parses those files when rebuilding the benchmark catalogStore.IngestSTIG,parseSTIG, andxml.Unmarshalkensa-catalog ingest stig <os> <release> <xccdf-path>accepts a caller-supplied path, but no tracked operational caller for that interface was found. It is not described here as a production, developer, or CI use caseinternal/catalog/stig.gocall path. That call path is absent from shipped artifacts, althoughencoding/xmlremains linked through an unrelated PDF dependency chain. The Go patch still removes the module-wide vulnerability reported for the dev/CI toolSupporting evidence:
.goreleaser.yamlbuilds exactlykensa,kensa-validate,kensa-keygenand
kensa-systemd-helper;kensa-catalogis not among them.go list -depson all four shipped mains reports
internal/catalog= 0.Makefile:326-331buildskensa-catalogand runsbuild catalog/sources rulesthencheck catalog/baseline.json; thecatalog-driftjob in.github/workflows/ci.ymlrunsmake catalog-checkon every pull request.
cmd/kensa-catalog/main.go:302is the build path thatiterates manifest entries into
Store.IngestSTIG.Ubuntu 22.04 / 24.04 STIG
Manual-xccdf.xmlsources.git grep 'ingest stig'over tracked files returns only the command's ownusage string, doc comment and help text — no Makefile, workflow or script
caller. The absence of a tracked caller is not proof the command is never
used; it is the limit of what the repository shows.
pkg/kensa/catalog.go, the public catalog read model, imports onlysort,strconv,strings,apiandinternal/mappings. It does not parse XCCDFor XML.
Honest caveat:
kensadoes linkencoding/xml, but through pdfcpu/gofpdf,not through
internal/catalog. The reported call site is dev/CI-only; thepackage is present in the shipped binary by another route.
GO-2026-6090 —
crypto/tlsFour traces. They divide into two kinds.
internal/agent/framing.go:164(io.ReadFull) andinternal/agent/dispatcher/setup.go:170(io.WriteString)cmd.StdinPipe()/cmd.StdoutPipe()— OS pipes to an SSH/sudo subprocess. No non-test Kensa code importscrypto/tlsornet/http, and nothing constructs atls.Connio.ReadFull(io.Reader)andio.WriteString(io.Writer)admittls.Connas an implementation because the package is linked; the concrete wiring is pipesinternal/output/pdf.go:252via maroto — see belowcrypto/tlsenterskensaonly vianet/http, which enters only via pdfcpuGO-2026-5026 (
net/http) and GO-2026-6218 (net/url)internal/output/pdf.go:252(output.generateAndWrite→maroto.Maroto.Generate)-o pdf:<file>on the shippedkensabinaryrowandtext; it constructstext.NewColexclusively, uses no image or external-resource component, and does not importnet/httpstrace -f -e trace=%network: zero socket/connect calls (the only entries are Go runtimeSIGURG). A controlcurlunder the identical filter produced 30, so the filter worksBuild propagation
Built from a clean worktree at the final head, release settings
(
CGO_ENABLED=0,-tags netgo), both release arches:go version -mkensakensa-validatekensa-keygenkensa-systemd-helpergoreleaser checkpasses; a local snapshot produces the documented 11artifacts and the packaged
kensareportsgo1.26.8. The release config stillbuilds exactly those four binaries —
kensa-catalogis not among them.Merging this PR does not modify any previously released or deployed binary.
A new build and release is required to deliver the patched runtime.
Linter suppression — resolved by experiment
The
.golangci.ymlSA5011 suppression was attributed to golangci-lint v2.12.2built under go1.26.5. CI installs the linter with
install-mode: goinstallunder
GOTOOLCHAIN: local, so it is rebuilt with whatever Go the job selects —the comment could not be carried forward with a new number.
Measured in a disposable clean worktree at the final head, with the linter
installed exactly as CI installs it and reporting
built with go1.26.8:t.FatalThe controls establish the silence is meaningful: SA5011 is live on test files,
and the
t.Fatalshape the suppression existed for is no longer flagged. 252test files call
t.Fataland none produce a finding. The suppression isremoved, not re-dated. Production SA5011 enforcement is unchanged — it was
never suppressed outside
_test.go.Verification on the final head
go version(go1.26.8),go test ./...,make build,make lint,make vuln,make spec-sync,make spec-coverage-strict(151 specs),make docs-check,make manpage-check,make cli-smoke,make docs-style,make docs-style-sync,make comment-lint,make catalog-check,make mod-tidy-check,git diff --check— all pass. Static-linking verifypasses for all three checked binaries.
Race testing
Identical prerequisites on both sides. No head-only race.
bin/populatedbin/absentIdentical failing test signatures on both sides:
TestSignalfd_ConcurrentCloseDuringWait,TestOpenAgent_LocalStub,TestOpenAgent_AnnounceLine.The race suite is not clean, and this PR does not claim it is. Both races
are pre-existing, reproduce identically on base, and are separate work. CI runs
go testwithout-race, so neither is gated today.Scope
Four files:
go.mod,.golangci.yml,docs/guide/01-install.md,CHANGELOG.md. No Go source, noapi/, no engine, handlers, capture,rollback, specs, or rule YAML. No
VERSIONbump. No tests added.Failure-mode analysis — accepted
Status: founder accepted the documented residual risk on 2026-09-11. The
analysis below is unchanged from the version reviewed; only its status is.
1. What could this change do wrong in production?
carries cgo, compiler, runtime,
debug/elfandoschanges beyond thesecurity repairs, and Kensa was not shown to exercise them.
used: ASN.1 parsing in
kensa verify, XML parsing in the dev catalog tool,and the PDF dependency chain.
suite; lint; specs; docs; manpage; CLI smoke; a release snapshot producing 11
artifacts.
distribution and kernel at runtime; any live-fleet remediate or rollback
cycle; SBOM generation (run by CI, not locally — syft absent).
focused experiment observed none.
2. What state is captured, and can the system be restored?
capture and no rollback path to exercise.
it restores the vulnerable runtime.
supported patched toolchain.
3. What real-world edge case is not fully covered?
required.
CPU or workload, nor against hostile input generally.
crypto/tlsagent traces and the PDFnet/http/net/urltraces remainconservative static traces; their concrete production wiring is pipes and
text-only PDF components respectively, and no TLS or HTTP client construction
exists in non-test Kensa code.
represented as fixed.
Residual risk
Adopting a patched toolchain exchanges five module-scope findings that
govulncheck classifies as called — one of them demonstrated on the shipped
kensa verifypath, one demonstrated in the dev/CI catalog path, and threeconservative static traces with no network activity observed —
for the unquantified risk that an unrelated compiler or runtime fix in 1.26.7
or 1.26.8 changes behavior Kensa depends on. Offline gates covering both
release architectures found no such change. The residual risk is that offline
gates do not run on production hosts.
Founder accepted the documented residual risk on 2026-09-11.
Pre-merge workflow run (manual dispatch)
A pull_request run skips
govulncheckandRelease snapshot, so CI wasdispatched manually against the exact branch head.
Run 34620518191,
event=workflow_dispatch,headSha=18fe8ee67bcdf7d43f48c15d082a7b63d76b6f0f— identical to the PR head. 16 jobs, 16 success, 0 skipped.
From the logs, not the status counts:
go version go1.26.8 linux/amd64,GOVERSION='go1.26.8'No vulnerabilities found./Your code is affected by 0 vulnerabilities., zeroVulnerability #blocksArtifact count: 11, all 11 present and named for18fe8eeOK SBOM present (116 components), 11 shipping direct deps included, anchored in checksumsOK sudoers fragment is 0440 root:root + config in rpm and debgo1.26.8One log line worth pre-empting: the snapshot job prints
GoVersion: go1.26.3.That is syft 1.46.0, a prebuilt third-party SBOM tool downloaded by its own
installer, reporting the toolchain it was built with. goreleaser v2.15.4 on
the line above reports
go1.26.8because CI builds it from source with theselected Go. Neither affects the Kensa artifacts, which are built by the
job's
go1.26.8.No dependency or generated-file drift: the
go.modrequire block is identicalto base,
go.sumis unchanged,make mod-tidy-checkandmake proto-checkpass, and the tracked tree is clean.