Move CLI image base to digest-pinned SUSE BCI micro 15.7 - #162
Merged
Conversation
gcr.io/distroless/static-debian11 is end-of-life. Trivy reports Metadata.OS.EOSL=true and warns that detection is insufficient because Debian 11 no longer receives security updates, so a clean scan on that base was partly a dead advisory feed rather than a clean image. Five tzdata advisories sat below the dev gate only because the gate filters UNKNOWN severity. registry.suse.com/bci/bci-micro:15.7 is SLES 15-SP7 with a live SUSE feed, publishes both linux/amd64 and linux/arm64/v8, and needs no package installation, so no zypper step is introduced. The digest is the multi-arch index digest so both GoReleaser per-arch builds resolve it under --platform.
LouisLotter
approved these changes
Sep 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
docker/Dockerfile.goreleaserbuilt the publishedstackstate/stackstate-cli2imageFROM gcr.io/distroless/static-debian11. Debian 11 is end-of-life, and Trivy says so explicitly —Metadata.OS.EOSListrue, with a warning that "vulnerability detection may be insufficient because security updates are not provided". A green scan on that base was partly a dead advisory feed, not a clean image.It was already hiding rows. Scanning the currently published
3.9.0images with UNKNOWN severity included (the dev gate passes onlyCRITICAL,HIGH,MEDIUM,LOW, so UNKNOWN is filtered before the evaluator reads the report) surfaces fivetzdataadvisories on both architectures:What changed
One
FROMline, toregistry.suse.com/bci/bci-micro:15.7, digest-pinned. SUSE BCI is the mandated base family andbci-microcan do the job, so no exception is claimed.The digest is deliberately the multi-arch index digest
sha256:44f5c047…, not a per-arch one..goreleaser.ymlbuilds this same Dockerfile twice, once with--platform=linux/amd64and once with--platform=linux/arm64/v8; a per-arch pin would break one of them. Verified both resolve.No package installation is added, so there is no
zypperstep —bci-microships what the CLI needs already.Validation
Built on this branch's head from the real GoReleaser
ldflagsunder Go 1.25.13 (go.mod's pin — the Go line is deliberately untouched;go1.25.13has no open stdlib advisory and 1.26 would add exposure while clearing nothing).Both published architectures covered — the scan resolves two per-arch digests.
distroless/static-debian11(published 3.9.0)bci-micro:15.7(this PR)Metadata.OS.EOSLTooling: Trivy 0.74.0, Grype 0.117.0. The 21-vs-3 OS package count matters: the clean result is a live SUSE feed reading a real rpmdb, not an empty scan.
Command surface, on the migrated amd64 image:
sts version,sts version -o json(all three fields correct), andsts --helplisting all 24 command groups.TLS trust probe, and it is not vacuous. The CLI's own HTTPS path against a real host:
bci-micro:sts user-session roles --url https://example.comcompletes the handshake and returns a genuine404 Not Foundfrom example.com — the CA bundle at/etc/ssl/ca-bundle.pemis found by Go's x509 loader.FROM scratch(control):tls: failed to verify certificate: x509: certificate signed by unknown authority.So the probe detects the trust store rather than just a zero exit code.
Trade-offs, stated plainly
Attack surface grows. SUSE publishes no distroless-class base, so complying costs a shell and more executables in a customer-facing image:
sts)bash,shThat is the deliberate trade: +9.5 MB and a shell, in exchange for a maintained advisory feed.
distroless/static-debian12would be smaller and shell-free but is not BCI and would need a written exception.bci-microships nozoneinfo; this is behaviourally inert here. Independently re-verified on this head: there is notime.Local,LoadLocation, or.In(...)conversion anywhere in non-test code. The one timestamp renderer,internal/util/string_util.go:120, isv.Format("Mon Jan 2 15:04:05 2006 MST"), which prints the zone already attached to the value the API returned and performs no tzdata lookup. The onlyLoadLocation("EST")isinternal/util/string_util_test.go:39, which runs on the CI host, not in the image.CI does not cover this change.
ci.ymlbuilds the image only in thepublishjob, gated ongithub.ref_type == 'tag'. No PR job buildsdocker/Dockerfile.goreleaser, so a green CI run here is evidence about the Go code, not about the base migration — the build and scan evidence above is what covers it. Worth a follow-up, out of scope for this PR.go vet ./...andgo test ./...pass on this head.Context
Addresses the base-image finding tracked in https://github.com/StackVista/cve-reporter/issues/65, coordinated from https://github.com/StackVista/cve-reporter/issues/32. Neither is auto-closed by this PR: closure needs a later complete dev scan showing both CLI targets clean, and this base change reaches customers only once a release is cut above
v3.9.0.