Pin every developer tool install by hash; make local lint match CI #68
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
Policy: "ALL external references must be pinned by cryptographic hash...
Version tags are server-mutable and therefore remote code execution
vulnerabilities... There are zero exceptions to this rule."
The
Dockerfileand the Gitea workflow comply. The developer-facing installpath does not:
script/bootstrap:141-143installsgolangci-lintfrom whatever the OSpackage manager offers — nix, apt, brew, or apk — with no version
constraint at all. This is not merely unpinned, it is unversioned.
Makefile:36:go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28.1— mutable tag.
Makefile:51(devprereqs):go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2— mutable tag, and a second, weaker source of truth competing with
script/bootstrap.script/bootstrapnever installsgofumptorprettier, both of whichscript/fmtrequires. A developer who followsmake setupcannot runmake fmt.This is not theoretical. On this machine
script/bootstrapproducedgolangci-lint v2.10.1 while the repo pins v2.12.2, and the two
disagree by 10 findings. That skew already caused a green PR to look red
locally, and it will keep costing review time until it is fixed.
Definition of done
script/bootstrapinstalls the exact pinnedgolangci-lintversion therepo targets, verified by hash, on every supported platform — not
"whatever the package manager has".
gofumptandprettierare installed byscript/bootstrapat pinnedversions, so
make fmtworks on a fresh clone aftermake setup.Makefile:36and thedevprereqstarget either install by content hashor are deleted in favour of
script/bootstrapbeing the single source oftruth. Prefer deletion — two install paths is the actual defect.
# tool vX.Y.Z, YYYY-MM-DDcomment, perpolicy.
golangci-lintversion appears in exactly one place that theMakefile,Dockerfile, andscript/bootstrapall derive from, so theycannot drift again.
script/bootstrapon a machine with a differentgolangci-lintalready on
PATHresults in the pinned version being used byscript/lint— not the pre-existing one.make checkpasses anddocker build .succeeds.TODO.mdupdated inthe same commit.
Implementation requirements
curl | sh. Download a specific release archive, verify a hardcodedsha256 with the existing
verify_sha256helper inscript/bootstrap, theninstall. That helper already exists and is used for nvm; follow it.
go install pkg@v1.2.3is not hash pinning — the tag is mutable. Ifyou keep a
go installpath, it must resolve through ago.sum-verifiedmechanism (a
tools.gowith the tool as a module dependency, built withthe repo's own
go.sum), which is content-verified. Otherwise fetch arelease archive and verify its hash.
script/bootstrapmust stay POSIX sh,set -eu, no bashisms, and stayidempotent — re-running it must not reinstall or fail.
OS/arch and select the right one. Do not pin only the Linux amd64 hash and
silently fall through to an unverified install elsewhere.
script/lintshould assert the linter version matches the pin, addthat check and make the mismatch message tell the developer to run
make bootstrap.(closes #68).Scope additions folded in from the PR #88 review, so they are fixed
alongside the bootstrap work they belong with rather than as a separate
issue.
The prettier half of this issue is already done. PR #88 added
package.json/yarn.lockpinning prettier 3.9.6 bysha512, andscript/bootstrapnow installs node, yarn, and the locked JS deps. Thatpin is verified enforced, not merely asserted — corrupting the lockfile
hash makes
docker build .fail atIntegrity check failed for "prettier".So what remains here is: gofumpt, golangci-lint, and the
Makefile Go tool installs. Adjust the definition of done accordingly;
do not redo the prettier work.
Four additional items to fix as part of this issue:
Make the prettier version check a hard failure.
script/prettiercurrently prefers
node_modules/.bin/prettierbut falls back to aPATHprettier of unknown version with only a stderr warning. Differentprettier versions format differently, so this is the same version-skew
trap that already cost a review cycle when a local golangci-lint v2.10.1
disagreed with the pinned v2.12.2 by ten findings. Compare the resolved
binary's
--versionagainst the pin inpackage.jsonand fail hard onmismatch, naming
script/bootstrapin the message.Apply the identical treatment to
gofumptandgolangci-lintinscript/fmt,script/fmt-check-go, andscript/lint— those arecurrently invoked with no version check at all, which is worse. That
consistency is the reason the prettier fallback was not treated as
blocking on #88.
Correct the
--frozen-lockfileexplanation. The comment inscript/bootstrap(and the #88 commit message) states that--frozen-lockfileenforces the integrity hash. It does not — the flagonly guards lockfile-versus-manifest consistency; integrity verification
happens at fetch time regardless. The behaviour is right, the stated
reason is wrong, and a wrong reason in a comment about supply-chain
pinning will mislead the next person to touch it.
Remove the dead
make fmt-check-mdtarget, or wire it up. It iscurrently unreachable.
Record the Dockerfile lint-stage deviation in
TODO.md. The lintstage runs
make fmt-check-gorather than the canonical policyDockerfile's
make fmt-check, because thegolangci/golangci-lintimage has no node and the markdown check lives in its own
mdfmtstage.That is deliberate and correct, but it is a visible divergence from the
template — without a note, a future policy audit will "correct" it back
and silently drop the markdown gate.
Also cosmetic, fix while you are in there:
package.jsoninvents"version": "0.1.0"for a private manifest that has no version semantics,and
script/fmt-checkprints its "Checking formatting" banner twice.