Skip to content

fix(docker): build mkcert from source; correct the trivyignore paths - #105

Merged
acamarata merged 2 commits into
mainfrom
fix/image-critical-cves
Sep 12, 2026
Merged

acamarata merged 2 commits into
mainfrom
fix/image-critical-cves

Conversation

@acamarata

Copy link
Copy Markdown
Collaborator

The v1.3.6 tag push reached the Trivy CRITICAL gate and was blocked, so nothing was published. Two separate causes — neither of them the tar CVE fixed in d19a32c, which is genuinely gone.

1. mkcert carries four CRITICAL Go stdlib CVEs

usr/local/bin/mkcert (gobinary)
Total: 4 (CRITICAL: 4)
  stdlib  CVE-2023-24538   v1.18 -> 1.19.8, 1.20.3
          CVE-2023-24540
          CVE-2024-24790
          CVE-2025-68121

The upstream pre-built binary is v1.4.4, published 2022-04-26, compiled with Go 1.18. There is no newer release to bump to — v1.4.4 is the latest and has been for four years.

So mkcert is now compiled from that same source with Go 1.27: identical behaviour, patched stdlib.

Two details worth keeping:

  • --platform=$BUILDPLATFORM + GOOS/GOARCH cross-compilation, so the stage stays native on the builder instead of running under QEMU once per architecture.
  • go build -o rather than go install — when cross-compiling, go install writes to /go/bin/${GOOS}_${GOARCH}/, and the COPY would silently miss it.

2. The gcp-service-account suppression never matched

Trivy reports image paths with a leading slash:

/app/.next/server/app/cloud/gcp/page.js

.trivyignore.yaml listed them without one, so the entry matched nothing and this false positive kept blocking the publish. The analysis behind that entry was correct; only the paths were wrong. Both forms are now listed, so it works for an image scan and a filesystem scan alike — still scoped to the two GCP bundles, so a real credential anywhere else in the image still fails the gate.

Verification

docker build --check passes clean and resolves golang:1.27-alpine.

A full local build was skipped deliberately: swap was at 71%, above the 50% throttle in the local-resource-ceiling rule, and this machine has panicked from concurrent heavy compiles before. CI does the real build.

Gate placement — worth a separate fix

Trivy runs only inside docker-publish.yml, which triggers on tag push / dispatch / manual — never on a pull request. So image CVEs cannot be caught before merge; they surface only when a publish is attempted. That is how four CRITICALs sat undetected while multi-arch-check.yml went green on every PR — it builds with --output type=cacheonly and runs no scan at all.

Not fixed here. Flagging it because a green PR is currently not evidence that the image is publishable.

The v1.3.6 tag push reached the Trivy CRITICAL gate and was blocked, so nothing
was published. Two separate causes, neither of them the tar CVE that was fixed
in d19a32c (that one is genuinely gone).

1. usr/local/bin/mkcert carries four CRITICAL Go stdlib vulnerabilities:
   CVE-2023-24538, CVE-2023-24540, CVE-2024-24790 and CVE-2025-68121. The
   upstream pre-built binary is v1.4.4, published 2022-04-26 and compiled with
   Go 1.18. There is no newer release to bump to — v1.4.4 IS the latest and has
   been for four years.

   So mkcert is now compiled from that same source with Go 1.27: identical
   behaviour, patched stdlib. The stage uses --platform=$BUILDPLATFORM with
   GOOS/GOARCH cross-compilation so it stays native on the builder rather than
   running under QEMU once per architecture, and `go build -o` rather than
   `go install`, because when cross-compiling `go install` writes to
   /go/bin/${GOOS}_${GOARCH}/ and the COPY would silently miss it.

2. The gcp-service-account suppression never matched. Trivy reports image paths
   with a leading slash — the gate log shows
   "/app/.next/server/app/cloud/gcp/page.js" — and .trivyignore.yaml listed the
   paths without one. The analysis behind that entry was right; only the paths
   were wrong. Both forms are now listed, so it works for an image scan and a
   filesystem scan alike. Still scoped to the two GCP bundles, so a real
   credential anywhere else in the image still fails the gate.

Verified with `docker build --check` (clean, and it resolves golang:1.27-alpine).
A full local build was skipped deliberately: swap was at 71%, above the 50%
throttle in the local-resource-ceiling rule, and this machine has panicked from
concurrent heavy compiles before. CI does the real build.

Note the gate placement: Trivy only runs inside docker-publish.yml, which runs
on tag push / dispatch / manual — never on a pull request. So image CVEs cannot
be caught before merge; they surface only when a publish is attempted. That is
how four CRITICALs sat undetected. Worth fixing separately.
release.yml pushed nself/nself-admin:<version>, :<major>.<minor>, :<major> and
:latest with `push: true` and NO vulnerability scan, on the same
`push: tags: v*` trigger as docker-publish.yml.

docker-publish.yml has a Trivy CRITICAL gate. This workflow did not. So every
tag push raced two publishers of the same image, and the ungated one could ship
a build the gate had just rejected. A security gate another workflow can walk
around is not a gate.

That is not hypothetical. On the v1.3.6 tag push (2026-09-12) docker-publish
correctly blocked on four CRITICAL Go stdlib CVEs in mkcert, while this workflow
was already mid-push of the same image. It had to be cancelled by hand to stop
:latest moving to a known-vulnerable build. Verified afterwards that no version
tag landed: :latest still reads 2026-06-18, and 1.3.6 / 1.3 were never created.

Image publication now belongs to docker-publish.yml alone. It owns the Trivy
gate, the multi-arch manifest verification (S49-T06), and the NSELF_VERSION
build-arg that this job never even passed — so the image it built pinned
whatever the Dockerfile ARG defaulted to rather than the released CLI.

This workflow keeps the GitHub Release: changelog, archive, release body,
notification. `Extract version from tag` stays, since those steps use it.
@acamarata
acamarata merged commit 0c31881 into main Sep 12, 2026
33 checks passed
@acamarata
acamarata deleted the fix/image-critical-cves branch September 12, 2026 17:02
acamarata added a commit that referenced this pull request Sep 12, 2026
…st (#106)

Three triggers feed this workflow -- tag push, cli-release dispatch and
manual dispatch -- and nothing stopped them overlapping. On 2026-09-12 two
dispatches for 1.3.6 ran at once (17:02:37 and 17:04:52) and one had to be
cancelled by hand.

Both would have pushed :X.Y.Z, :X.Y, :X and :latest. The last writer of
:latest wins, so a slower run of an OLDER version can land on top of a
newer one and silently become what every `docker pull nself/nself-admin`
receives. #105 had just removed release.yml's ungated publisher for the
same class of reason; this is the same problem one layer up, between two
runs of the gated publisher itself.

The group is a constant, not the version. Serialising per-version would
still let 1.3.6 and 1.3.7 race, and the shared floating tags are exactly
what they contend for. One image, one publisher.

cancel-in-progress is false deliberately. Cancelling a publish mid-push is
not a safe stop -- that is what happened to release.yml on v1.3.6, killed
partway through pushing with the manifest unfinished. A queued run costs
minutes; a half-pushed manifest is a broken :latest.

Trade-off worth stating: GitHub keeps only one pending run per group, so a
third request cancels the second while it waits. That is correct for the
duplicate-dispatch case this fixes (same version twice -- drop the
redundant one). For three distinct versions queued at once it would drop
the middle; the Hygiene release-tag gate catches a version whose image
never published, and releases here are sequential and rare.

Pure addition: no existing key changed. Verified the workflow still parses
with all three triggers and the docker-publish job intact.
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