Skip to content

fix(deps): move to Go 1.26.8 to resolve five govulncheck findings - #419

Merged
remyluslosius merged 3 commits into
mainfrom
chore/go-1.26.6
Sep 11, 2026
Merged

fix(deps): move to Go 1.26.8 to resolve five govulncheck findings#419
remyluslosius merged 3 commits into
mainfrom
chore/go-1.26.6

Conversation

@remyluslosius

@remyluslosius remyluslosius commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Moves the go directive to 1.26.8, the current Go 1.26 patch, closing five
module-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/http in 1.26.7, and cgo, the compiler, the runtime,
debug/elf and os in 1.26.8. Kensa was not shown to exercise those later
fixes; they arrive with the release.

Scan result

Fresh scans on two clean worktrees, base at 0152886 (the PR's actual merge
base) and head at 18fe8ee.

base, Go 1.26.5 head, Go 1.26.8
Called vulnerabilities 5 0
Imported, not called 3 1
Required, not called 18 17
make vuln exit 2 0

Evidence 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/asn1

Field Evidence
Vulnerable function asn1.Unmarshal, via x509.ParsePKIXPublicKey
Kensa call site internal/evidence/load.go:107 (evidence.LoadVerifier)
Entrypoint kensa verifycmd/kensa/verify.go:137, the shipped kensa binary
Input origin A .pub file in the operator's trust directory. The envelope selects which file via signing_key_id (constrained to 64-char lower-hex), but the bytes parsed are the local operator-placed file, not envelope content
Distribution Shipped binary (kensa; kensa-keygen also links encoding/asn1)
Reachability Demonstrated. Running the built binary against a malformed PKIX body returns evidence: LoadVerifier: PKIX parse …: asn1: structure error: tags don't match … publicKeyInfo @2 — the text is verbatim from encoding/asn1
Consequence Only what the advisory establishes for malformed ASN.1 input. Reaching it requires content in the trust directory, which is operator-controlled
Go 1.26.8 Absent

GO-2026-6088 — encoding/xml

Field Evidence
Vulnerable function xml.Unmarshalinternal/catalog/stig.go:58
Kensa call site catalog.parseSTIG, reached from Store.IngestSTIG (internal/catalog/store.go:62)
Distribution Dev/CI-only catalog authoring and drift validation. The internal/catalog XCCDF parsing path is not present in shipped Kensa artifacts
Demonstrated input origin The tracked catalog manifest (catalog/sources/manifest.json) selects five vendored STIG XCCDF files. The catalog-drift workflow parses those files when rebuilding the benchmark catalog
Reachability Demonstrated in the dev/CI catalog build path through Store.IngestSTIG, parseSTIG, and xml.Unmarshal
Standalone surface kensa-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 case
Production-host consequence None established from the reported internal/catalog/stig.go call path. That call path is absent from shipped artifacts, although encoding/xml remains linked through an unrelated PDF dependency chain. The Go patch still removes the module-wide vulnerability reported for the dev/CI tool
Go 1.26.8 Absent

Supporting evidence:

  • .goreleaser.yaml builds exactly kensa, kensa-validate, kensa-keygen
    and kensa-systemd-helper; kensa-catalog is not among them. go list -deps
    on all four shipped mains reports internal/catalog = 0.
  • Makefile:326-331 builds kensa-catalog and runs
    build catalog/sources rules then check catalog/baseline.json; the
    catalog-drift job in .github/workflows/ci.yml runs make catalog-check
    on every pull request. cmd/kensa-catalog/main.go:302 is the build path that
    iterates manifest entries into Store.IngestSTIG.
  • The five manifest-selected files are the RHEL 8 / 9 / 10 and
    Ubuntu 22.04 / 24.04 STIG Manual-xccdf.xml sources.
  • git grep 'ingest stig' over tracked files returns only the command's own
    usage 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 only sort,
    strconv, strings, api and internal/mappings. It does not parse XCCDF
    or XML.

Honest caveat: kensa does link encoding/xml, but through pdfcpu/gofpdf,
not through internal/catalog. The reported call site is dev/CI-only; the
package is present in the shipped binary by another route.

GO-2026-6090 — crypto/tls

Four traces. They divide into two kinds.

Field Evidence
Traces #2, #3 internal/agent/framing.go:164 (io.ReadFull) and internal/agent/dispatcher/setup.go:170 (io.WriteString)
Actual wiring cmd.StdinPipe() / cmd.StdoutPipe() — OS pipes to an SSH/sudo subprocess. No non-test Kensa code imports crypto/tls or net/http, and nothing constructs a tls.Conn
Reachability Conservative static trace. io.ReadFull(io.Reader) and io.WriteString(io.Writer) admit tls.Conn as an implementation because the package is linked; the concrete wiring is pipes
Traces #1, #4 internal/output/pdf.go:252 via maroto — see below
Distribution crypto/tls enters kensa only via net/http, which enters only via pdfcpu
Go 1.26.8 Absent

GO-2026-5026 (net/http) and GO-2026-6218 (net/url)

Field Evidence
Kensa call site internal/output/pdf.go:252 (output.generateAndWritemaroto.Maroto.Generate)
Entrypoint -o pdf:<file> on the shipped kensa binary
Input origin Internal values — Kensa's own scan/remediation results
Distribution Shipped binary; the packages enter only through the PDF dependency chain (pdfcpu, gofpdf)
Reachability Conservative static trace. Kensa's PDF code imports only maroto row and text; it constructs text.NewCol exclusively, uses no image or external-resource component, and does not import net/http
Focused experiment All 24 PDF writer tests run under strace -f -e trace=%network: zero socket/connect calls (the only entries are Go runtime SIGURG). A control curl under the identical filter produced 30, so the filter works
Consequence Network activity not observed in PDF generation. This is an absence measured over the writer test surface, not a proof for all inputs
Go 1.26.8 Absent

Build propagation

Built from a clean worktree at the final head, release settings
(CGO_ENABLED=0, -tags netgo), both release arches:

Binary amd64 arm64 go version -m Linkage
kensa go1.26.8 statically linked
kensa-validate go1.26.8 statically linked
kensa-keygen go1.26.8 statically linked
kensa-systemd-helper go1.26.8 statically linked

goreleaser check passes; a local snapshot produces the documented 11
artifacts and the packaged kensa reports go1.26.8. The release config still
builds exactly those four binaries — kensa-catalog is 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.yml SA5011 suppression was attributed to golangci-lint v2.12.2
built under go1.26.5. CI installs the linter with install-mode: goinstall
under 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:

Condition Result
Suppression present 0 issues
Suppression removed, cache cleared 0 issues, 0 SA5011
Control: test whose nil guard does not terminate SA5011 reported at guard and deref
Control: test whose guard calls t.Fatal not reported

The controls establish the silence is meaningful: SA5011 is live on test files,
and the t.Fatal shape the suppression existed for is no longer flagged. 252
test files call t.Fatal and none produce a finding. The suppression is
removed
, 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 verify
passes for all three checked binaries.

Race testing

Identical prerequisites on both sides. No head-only race.

Condition base (1.26.5) head (1.26.8)
bin/ populated 10 races, signalfd + dispatcher 10 races, signalfd + dispatcher
bin/ absent 1 race, signalfd 1 race, signalfd

Identical 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 test without -race, so neither is gated today.

Scope

Four files: go.mod, .golangci.yml, docs/guide/01-install.md,
CHANGELOG.md. No Go source, no api/, no engine, handlers, capture,
rollback, specs, or rule YAML. No VERSION bump. 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?

  • A compiler or runtime patch can change generated-binary behavior. 1.26.8
    carries cgo, compiler, runtime, debug/elf and os changes beyond the
    security repairs, and Kensa was not shown to exercise them.
  • Parser and connection behavior may change where the repaired functions are
    used: ASN.1 parsing in kensa verify, XML parsing in the dev catalog tool,
    and the PDF dependency chain.
  • Tested: linux/amd64 and linux/arm64 static release builds; the full unit
    suite; lint; specs; docs; manpage; CLI smoke; a release snapshot producing 11
    artifacts.
  • Not demonstrated: behavior of the shipped binaries on every supported
    distribution and kernel at runtime; any live-fleet remediate or rollback
    cycle; SBOM generation (run by CI, not locally — syft absent).
  • Kensa PDF generation is not claimed to perform network access. The
    focused experiment observed none.

2. What state is captured, and can the system be restored?

  • This PR performs no managed-host mutation, so there is no host pre-state to
    capture and no rollback path to exercise.
  • Source and build configuration are fully recoverable through Git.
  • Previously built artifacts can be retained for diagnosis.
  • Reverting to an older binary is not an acceptable security remediation:
    it restores the vulnerable runtime.
  • Preferred recovery from a toolchain regression is a forward fix onto a
    supported patched toolchain.

3. What real-world edge case is not fully covered?

  • Merging source does not patch already-deployed binaries; a new release is
    required.
  • Only supported linux/amd64 and linux/arm64 static targets were validated.
  • Testing cannot establish behavior on every production kernel, distribution,
    CPU or workload, nor against hostile input generally.
  • The crypto/tls agent traces and the PDF net/http/net/url traces remain
    conservative 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.
  • The two pre-existing races are not fixed by this PR and are not
    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 verify path, one demonstrated in the dev/CI catalog path, and three
conservative 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 govulncheck and Release snapshot, so CI was
dispatched 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:

Check Log evidence
Toolchain go version go1.26.8 linux/amd64, GOVERSION='go1.26.8'
govulncheck No vulnerabilities found. / Your code is affected by 0 vulnerabilities., zero Vulnerability # blocks
Release snapshot Artifact count: 11, all 11 present and named for 18fe8ee
SBOM OK SBOM present (116 components), 11 shipping direct deps included, anchored in checksums
Packaging OK sudoers fragment is 0440 root:root + config in rpm and deb
Unit / Lint / Static / both Portability jobs all report go1.26.8

One 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.8 because CI builds it from source with the
selected Go. Neither affects the Kensa artifacts, which are built by the
job's go1.26.8.

No dependency or generated-file drift: the go.mod require block is identical
to base, go.sum is unchanged, make mod-tidy-check and make proto-check
pass, and the tracked tree is clean.

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.
@remyluslosius remyluslosius changed the title fix(deps): move to Go 1.26.6 for five called standard-library fixes fix(deps): move to Go 1.26.8 for five called standard-library fixes Sep 11, 2026
@remyluslosius remyluslosius changed the title fix(deps): move to Go 1.26.8 for five called standard-library fixes fix(deps): move to Go 1.26.8 to resolve five govulncheck findings Sep 11, 2026
@remyluslosius
remyluslosius merged commit bf19920 into main Sep 11, 2026
36 checks passed
@remyluslosius
remyluslosius deleted the chore/go-1.26.6 branch September 11, 2026 18:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant