gum v2.3.1
This tag carries no change to the binary. It exists for two things the v2.3.0 tag does not hold: the expression profile DSL author reference, which the release manifest did not export, and a vulnerability gate that fails on a class of advisory the old gate could not fail on.
Highlights
docs/profile-dsl-reference.mdis in a release tree. The page was writtenscripts/check-govulncheck.pyreplacesgovulncheck ./...as the gate inscripts/govulncheck-allowlist.jsonis the only way to accept one, and eachmake vulncheckruns the same gate from a checkout.
before v2.3.0, but scripts/public-release-manifest.json did not list it, so the v2.3.0 tag carries no copy and a checkout of that tag produces none. The docs site serves main and has published the page since it landed there.
the pull-request workflow and in the release workflow. govulncheck exits 0 when an advisory covers only import paths the build never compiles, so such an advisory used to reach main without failing any gate.
entry names the advisory id, the module, the reason the requirement stays and the date. A finding the build can reach has no allowlist path at all, and an entry for its advisory does not create one.
Install
curl -fsSL https://raw.githubusercontent.com/ehmo/gum/main/install.sh | GUM_VERSION=v2.3.1 bash
gum --version
gum doctorUpgrade notes
None. No configuration, credential, catalog, wire-format or behavior change. No non-test Go file differs from v2.3.0. Upgrading changes the reported version string and nothing else.
Added
scripts/check-govulncheck.py, run by.github/workflows/govulncheck.ymlonscripts/govulncheck-allowlist.json, which records module-level advisories.make vulncheck.TestGovulncheckAllowlistShapeininternal/testmatrix, which holds every
every pull request and by the govulncheck job in .github/workflows/release.yml on every tag. It reads the scanner's JSON and classifies each finding by its most precise trace frame.
Each entry requires id, module, reason and recorded.
allowlist entry to a well-formed advisory id, a module, a reason long enough for a reviewer to check, and an ISO date, and rejects duplicate ids.
Changed
docs/profile-dsl-reference.mdis exported. It is listed in the release- Both
govulncheckjobs invoke the gate script instead of the scanner SECURITY.mdanddocs/security.mdgivemake vulncheckas the local
manifest and in the Reference section of the docs-site navigation.
directly. The scanner stays pinned at golang.org/x/vuln/cmd/govulncheck@v1.3.0 and the scan still covers ./... at symbol granularity.
recipe and state the three ways the gate fails.
Fixed
- A module-level advisory can no longer land unnoticed.
govulncheckprints - A recorded advisory the scan no longer reports fails the gate, so the
those under "vulnerabilities in modules you require" and exits 0, so the previous gate passed whatever appeared there. GO-2026-5932 arrived that way.
allowlist cannot accumulate entries for advisories that are gone.
Security
No vulnerability fix and no dependency change. The gate reports 0 findings the build can reach and one module-level finding, GO-2026-5932 in golang.org/x/crypto, which the allowlist records.
That advisory cannot be cleared by a version bump. It says golang.org/x/crypto/openpgp and its six subpackages are unmaintained, and its affected range opens at 0 and carries no fixed event, so every release of the module is in range and no later release leaves it. gum imports none of those packages. The module stays in the graph for golang.org/x/crypto/cryptobyte, which github.com/google/s2a-go reaches under cloud.google.com/go/auth, so the requirement cannot be dropped either. The allowlist entry records that reasoning next to the id.
Known limitations
Unchanged from the v2.3.0 release notes, with three additions.
- The allowlist holds one permanent entry. GO-2026-5932 has no fixed version,
- The
v2.3.0tag tree carries nodocs/profile-dsl-reference.md, and the - The
v2.3.1tag tree carries a racyTestStdioFramingClean. The test waits
so the entry cannot expire and a reviewer reading the gate's output sees a recorded advisory on every clean run.
v2.0.0 tag tree still fails make fmt-check. A published tag is not moved, because its binaries, checksums and provenance all name that commit.
for a JSON-RPC id on stdout instead of for the frame terminator that follows it, so go test ./cmd/gum at that tag can report non-JSON bytes on a loaded machine. The binary is unaffected: only test code reads the capture. The fix is on main.
Token savings
Unchanged from v2.3.0. This release does not touch output shaping. Measured with the release fixtures using a local build, run from the apps/gum directory of the matching source checkout:
gum gain --fixture-replay --format=toon
gum gain --fixture-replay --format=json| Default format | Total calls | Total tokens in | Total tokens saved | Aggregate savings |
|---|---|---|---|---|
toon |
10 | 3,922 | 210 | 5.35 % |
json |
10 | 3,922 | -12 | 0.31 % overhead |
Verification
All seven jobs in the v2.3.1 release workflow passed: tag validation, live docs match, tests, the new govulncheck gate, the GoReleaser build, the independent four-platform rebuild, and the provenance comparison. The govulncheck job ran scripts/check-govulncheck.py against a scanner installed by the pinned go install, which is the first tag to prove the gate on CI rather than on a maintainer's host.
git checkout v2.3.1 produces docs/profile-dsl-reference.md. The file in the tag hashes to 235b8b5089245378c38ebef0cbf054130a96e1edb6b5f465f6a58da7bf6f3bc9, the same digest as the copy in the source tree. The v2.3.0 tag produces no such file.
The four downloaded archives matched checksums.txt, and each one matched its subject digest in gum-v2.3.1.intoto.jsonl. That statement names commit f8ef8dad227fc1c57925ca02c0d90e60aec0d488 and refs/tags/v2.3.1. The binary extracted from each of the four archives matched its entry in release-binaries.sha256.
A local rebuild reproduced all four published binaries. The command in Reproducibility below, run from a clean clone at tag v2.3.1 on one darwin/arm64 host with GOTOOLCHAIN=go1.26.7, produced these hashes:
| Target | sha256 |
|---|---|
| darwin/amd64 | 9645bf1039abca285337260a1b2da1a44b2f911aaa332e889295a439ddcc2666 |
| darwin/arm64 | d8f20109188b4d4588bac55ca81a343a626ea2a1b946dfab752f0328217bb95f |
| linux/amd64 | a7cbc41cf4530cd27ed4347b8a52b47a9c8ada3fa56e331166a5cbc5ee29746a |
| linux/arm64 | e9fc42db19a0a33a4a224cc167d578d6d4ec3583625ee766d19bc704d6890a55 |
Each hash matches the matching line in release-binaries.sha256.
The Homebrew installation reports 2.3.1 and gum doctor passed every check. The installed binary hashes to the published darwin/arm64 digest. brew audit --strict --online --os=all --arch=all ehmo/tap/gum and brew test ehmo/tap/gum both passed, and the tap-drift workflow confirms both formulae point at this release.
Reproducibility
git clone https://github.com/ehmo/gum.git
cd gum && git checkout v2.3.1
cd apps/gum
GOTOOLCHAIN=go1.26.7 CGO_ENABLED=0 GOOS=<os> GOARCH=<arch> go build -trimpath \
-ldflags='-s -w -X main.version=2.3.1' ./cmd/gum
sha256sum gumBuild from a full clone, not from a linked git worktree. Go embeds the commit revision in the binary, and it silently skips that stamp in a linked worktree, which changes the hash.
Write the rebuilt binary outside the clone. Go stamps vcs.modified=true when the working tree holds any untracked file, so a -o path inside the checkout changes the hash of every build after the first.
