Harden the pinned-linter gate: context-gate the native path, and make bootstrap yield a working machine #80
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?
Follow-up to #78 / PR #79, from the non-blocking findings in that PR's
review (#79 (comment)).
None of these blocked the merge; all are worth closing out while the
context is fresh.
1. The native-lint escape hatch is version-gated, not context-gated
script/lintruns thePATHbinary when its version equals the pin:Its purpose is narrow — the
Dockerfilerunsmake lintinside thepinned image, where there is no Docker daemon. But the condition it
actually tests is version equality, which also admits a developer's
locally installed 2.12.2. That is a different binary from the pinned
image (different build, different Go toolchain), reached by a different
code path, and it silently bypasses the digest pin this whole mechanism
exists to enforce.
The review confirmed the hatch cannot currently produce a silent false
green — the both-empty case fails loudly with exit 127 — so this is
hardening, not a live bug.
Fix: gate on execution context as well as version, e.g. additionally
require
/.dockerenvto exist, or have theDockerfileset an env var(
VAULTIK_LINT_IN_CONTAINER=1) thatscript/lintchecks. A host with2.12.2 on
PATHmust still go through the pinned image.2.
script/bootstrapno longer produces a machine that can runmake checkscript/bootstrapstopped installinggolangci-lint(correctly — asecond copy on
PATHcan only drift). But Docker is now required byscript/lint→script/check→script/precommit, and bootstrap onlywarns when Docker is missing, then exits 0 printing
bootstrap complete. A contributor follows the documented flow, is told itsucceeded, and then
make checkfails.The warning text is also two sentence fragments and never says what will
break.
Fix: bootstrap either installs Docker or fails loudly with an
actionable message naming exactly what will not work without it. "Bootstrap
complete" must mean the machine can run the gate.
3. Hand-rolled version scraping where a real interface exists
script/lint:39-50scrapes thegolangci-lint versionbanner with awk.golangci-lint version --shortexists and prints the bare version inboth 2.10.1 and 2.12.2 (verified in review). If a future release restores
a leading
vto the banner, the parse breaks, the in-container pathsilently disappears, and the lint stage fails with a misleading "docker is
required to run the pinned linter" — inside a container.
Fix: use
--short, keeping a fallback only if needed for an olderrelease the repo still supports.
4.
TODO.mdoverstates the guarantee and contradictsREADME.mdTODO.mdsaysmake checkis now "as trustworthy asscript/cibuild".Only the lint leg is equivalent — tests and
gofmtstill run on thehost, against the host toolchain.
README.md, changed in the same commit,states this correctly.
Given that this repo has already produced two false green claims, an
overstated trust claim in the tracking doc is exactly the wrong error to
leave in place. Fix: make
TODO.mdmatchREADME.md.5.
README.mdrequirements section is now incomplete## requirementsstill lists only Go 1.26+ and object storage. Docker isnow needed to lint, check, or commit. Fix: add it.
Definition of done
PATH,make lintdemonstrably still runs the pinned image. Record how thiswas verified.
script/bootstrapdoesnot report success. Record the output.
.golangci.yml(sha256021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb).DockerfileFROMline remains the single source of truth forthe linter version.
script/cibuildexits 0.Implementation plan (fixed together with #99 in one PR, branch
fix-lint-isolation: both are the same defect inscript/lint— thenative path is gated on version equality rather than execution context,
and cache isolation is part of that context).
Context gate. The
Dockerfilelint stage getsENV VAULTIK_LINT_IN_CONTAINER=1, andscript/linttakes the nativepath only when that variable is set and the
PATHversion equalsthe pin.
/.dockerenvwas considered and rejected as the signal: itis created by
dockerdfordocker run, and is not reliably presentduring a BuildKit
docker build, which is exactly the case the hatchexists for. Inside the container a version mismatch becomes a hard,
explicit error rather than a fall-through to Docker, since there is
no daemon there. A host with 2.12.2 on
PATHand no such variablegoes through the pinned image.
Bootstrap. Docker becomes a hard requirement:
script/bootstrapfails, naming
script/lint,script/check,script/precommitandthe pinned image, rather than warning and printing
bootstrap complete. It will also reject a present-but-unreachable daemon,since a machine with the CLI and no daemon fails
make checkexactlythe same way. Installing Docker from bootstrap was rejected: it needs
root, a daemon and (on macOS) a GUI cask, so an install attempt would
itself fail in the common case and produce a second false success
mode.
golangci-lint version --shortreplaces the awk banner scrape,with the scrape kept only as a fallback if
--shortis unsupportedor prints nothing.
TODO.mdclaim thatmake checkis "as trustworthy asscript/cibuild" is corrected to matchREADME.md: only the lintleg is equivalent; tests and
gofmtstill run on the host toolchain.README.md## requirementsgains Docker, with what it is for.Verification will be behavioural, not by inspection: with a matching
golangci-lintonPATH, confirmmake lintstill goes through thepinned image; with
dockershadowed onPATH, confirmscript/bootstrapdoes not report success; and
script/cibuild(which runsmake lintinside the pinned image with no daemon) exits 0, which is what proves the
in-container path still works.
.golangci.ymlis untouched (sha256verified before push) and the lint-stage
FROMline remains the singlesource of truth for the linter version.
Implemented in PR #102 (branch
fix-lint-isolation, one commit,together with #99). Point by point against the five items and the
definition of done.
1. Context gate. The
Dockerfilelint stage setsENV VAULTIK_LINT_IN_CONTAINER=1, andscript/linttakes the nativepath only when that is set and the
PATHversion equals the pin.Inside the container a version mismatch is now a hard error naming both
versions, rather than a fall-through to Docker that cannot work there.
/.dockerenvwas rejected as the signal:dockerdcreates it fordocker run, but it is not reliably present during a BuildKitdocker build, which is exactly the case the exception exists for.Verified: a
golangci-lintshim onPATHreporting2.12.2andlogging every invocation was never invoked by
make lint, which ranthe pinned image (its output carries the image's
gomodguarddeprecation warnings, which the shim does not emit). Re-running the same
command with
VAULTIK_LINT_IN_CONTAINER=1invoked the shim(
version --short, thenrun ./...), which confirms that variable isthe only door. With the shim reporting
2.10.1and the variable set,exit 1 with
pinned: 2.12.2 ... installed: 2.10.1.2. Bootstrap. Docker is now a hard requirement, checked last so that
everything installable is installed first. Missing docker, or a docker
whose daemon is unreachable, fails with a message naming
script/lint,script/check,script/precommitandscript/cibuildand what eachloses. Installing docker from bootstrap was rejected: it needs root, a
running daemon, and on macOS a GUI cask, so the attempt would itself
fail in the common case and trade one false success for a second failure
mode.
Verified: with a
PATHcontaining the toolchain but nodocker,bootstrap: FAILED - docker is not installed., exit 1, and zerooccurrences of
bootstrap completein the output. With adockershimwhose
infofails,bootstrap: FAILED - the docker daemon is not reachable., exit 1. Unchanged happy path on a normal host: exit 0,bootstrap complete.3. Version scraping.
golangci-lint version --shortis now theparse; the awk banner scrape survives only as a fallback for a release
where
--shortis absent or silent.4.
TODO.mdoverstatement. Corrected in place to matchREADME.md: only the lint leg ofmake checkbecame equivalent toscript/cibuild, while tests andgofmtstill run on the host againstthe host toolchain. The correction says so explicitly rather than
quietly deleting the claim.
5.
README.mdrequirements. Gained docker (with a reachable daemon,and why:
script/lintruns the pinned image, andmake checkand thepre-commit hook run it) and the
sqlite3CLI the test suite shells outto.
Definition of done: items 1-5 addressed; items 1 and 2 verified
behaviourally as recorded above;
.golangci.ymluntouched, sha256021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcbverified before pushing; the
DockerfileFROMline and digest areunchanged and remain the single source of truth;
script/cibuildexits0, with the
[lint 9/9] ... make lintlayer executing for 78.7s insidethe pinned image and printing
0 issues.— which is also the proof thatthe in-container native path still works after the gate was tightened.