Skip to content

ci: scan, describe and sign the published container image - #193

Merged
Bccorb merged 1 commit into
mainfrom
ci/image-supply-chain
Aug 29, 2026
Merged

ci: scan, describe and sign the published container image#193
Bccorb merged 1 commit into
mainfrom
ci/image-supply-chain

Conversation

@Bccorb

@Bccorb Bccorb commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Closes #163.

What was there

docker-publish.yml built and pushed to ghcr.io with no scan, no SBOM, and no
signature. An adopter pulling ghcr.io/fells-code/seamless-auth-api had no way
to verify what was inside a tag or that it came from this repository.

What the workflow does now

  1. Builds and scans before pushing. Fixable high and critical findings fail
    the release, so a bad image is never published rather than being withdrawn
    after the fact. Findings also go to the security tab, including when the gate
    failed, so what blocked a release is visible outside the job log.
  2. Attaches an SPDX SBOM and a max-detail provenance statement to the image,
    so the registry can answer what is inside a tag and where it was built.
  3. Signs the pushed digest with cosign, keyless. No signing key to store or
    rotate; the workflow's OIDC token is exchanged for a short-lived certificate.
  4. Prints the digest and the exact verification commands to the job summary,
    so the answer to "how do I check this" is in the run rather than in someone's
    memory.

The exclusions, both deliberate and one of them load-bearing

Unfixed advisories do not block. An advisory with no available fix is real, but a
release held up by something no change here can resolve only trains people to
bypass the gate.

Neither does npm's own bundled tree inside the Node base image. This one is not
theoretical.
I scanned node:24-slim before trusting the gate, and it
currently carries four fixed HIGH findings, all under
usr/local/lib/node_modules/npm/node_modules/:

HIGH  brace-expansion  (CVE-2026-14257, CVE-2026-69152)
HIGH  ip-address       (CVE-2026-69192)
HIGH  tar              (CVE-2026-73566)

They are fixed status, so ignore-unfixed does not filter them, and the gate
as I first wrote it would have failed on the very first release. The container
runs node dist/server.js and never invokes npm, and nothing in this repository
can patch them ahead of an upstream image rebuild. Scoping the skip to that one
path keeps high blocking for /app/node_modules, which is what this repository
actually controls.

Re-run with the skip in place: exit 0.

Why the image is built twice

A multi-platform build cannot be loaded into the local daemon, so it cannot be
scanned before it is pushed. The scan builds linux/amd64 alone and loads it;
the push builds both platforms. Buildx caches the layers, so the second build is
cheap.

What is not verified

The gate has not run against a full application build. The Dockerfile installs a
compiler toolchain, builds the API, then clones and builds the admin dashboard,
which is not quick to reproduce locally and would not be conclusive against a
GitHub runner anyway. The base image is the part most likely to trip the gate and
that part is checked. The first tagged release is the first real run, and if it
does trip, the finding will be in the security tab rather than only in the log.

Adopters pulling the image had no way to verify what was inside a tag or that it
came from this repository.

The image is now built and scanned before it is pushed, rather than after, so a
bad image is never published. Fixable high and critical findings fail the
release and every finding is reported to the security tab. An SPDX SBOM and a
max-detail provenance statement are attached to the image, and the pushed digest
is signed with cosign keyless, so there is no signing key to store or rotate.
The job summary prints the digest and the exact verification commands.

Two exclusions, both deliberate. Unfixed advisories do not block, because a
release held up by something no change here can resolve trains people to bypass
the gate. Neither does npm's own bundled tree inside the node base image: the
container runs node directly and never invokes npm. That second one is not
theoretical, node:24-slim currently carries four HIGH findings there, so without
it the gate would have failed on the first release. The application's own
dependencies are still scanned and still block.

A multi-platform build cannot be loaded into the local daemon, so the scan
builds linux/amd64 alone and the push builds both. Buildx caches the layers.

Closes #163
@Bccorb
Bccorb merged commit 7ee36cf into main Aug 29, 2026
2 checks passed
@Bccorb
Bccorb deleted the ci/image-supply-chain branch August 29, 2026 03:42
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.

Published container images have no SBOM, vulnerability scan, or signature

1 participant