Skip to content

ci: attach provenance and SBOM attestations to the published images - #6130

Open
kobihikri wants to merge 1 commit into
MHSanaei:mainfrom
kobihikri:ci/image-provenance-sbom
Open

ci: attach provenance and SBOM attestations to the published images#6130
kobihikri wants to merge 1 commit into
MHSanaei:mainfrom
kobihikri:ci/image-provenance-sbom

Conversation

@kobihikri

Copy link
Copy Markdown

Hi, and thanks for 3x-ui.

.github/workflows/docker.yml publishes to both hsanaeii/3x-ui on Docker Hub and ghcr.io/mhsanaei/3x-ui on every tag, but the pushed manifests carry no provenance or SBOM attestation. Someone pulling the image cannot check that it was built by this workflow, from this repository, at that tag.

That felt worth raising for 3x-ui in particular. It is a panel people deploy on a VPS, usually straight from the image with a one-line install, and it holds the credentials for everything it fronts. Where the image came from is exactly the thing a deployer would want to be able to confirm.

The change is two lines on the single build step:

        with:
          ...
          push: true
          provenance: mode=max
          sbom: true

I used BuildKit's own attestation support rather than a separate signing action for a specific reason: your step pushes one build to two registries at once, and BuildKit attaches the attestation to the image manifest itself, so Docker Hub and GHCR both get it with no extra plumbing and no second credential. It also means no permissions changecontents: read and packages: write stay exactly as they are, and nothing needs id-token.

After a release, anyone can check with:

docker buildx imagetools inspect ghcr.io/mhsanaei/3x-ui:<tag> --format '{{ json .Provenance }}'

Two things worth knowing before merging:

  • mode=max records the full build including build arguments. That is normally right for a public image; provenance: true produces a smaller record if you would rather.
  • Attestations add an extra manifest to the index. Docker Hub and GHCR both handle it, but it is worth knowing if a mirror ever sits in front.

I have not claimed any SLSA level — the attestation is what BuildKit produces, and how the whole build should be characterised is your call.

Disclosure: I used AI assistance to help spot this and prepare the change, and I read the workflow and both push targets myself.

@github-actions github-actions Bot added github_actions Pull requests that update GitHub Actions code enhancement New feature or request labels Jul 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Summary

Two lines on the single docker/build-push-action@v7 step in .github/workflows/docker.yml, enabling BuildKit's provenance: mode=max and sbom: true attestations. The change is minimal, the input syntax is valid for v7, and the "no permissions change needed" claim in the description is correct — BuildKit attestations are attached by the push itself and need nothing beyond the existing contents: read / packages: write. My reservations are about scope (what this does and does not prove) and about the fact that this workflow is never exercised outside a release, not about the diff being wrong.

Findings

Severity: Medium
Confidence: Medium
Category: Security
Location: .github/workflows/docker.yml:58
Problem: BuildKit provenance and SBOM attestations are unsigned. They are metadata written into the pushed index; nothing binds them to GitHub's identity. Anyone with push access to hsanaeii/3x-ui or ghcr.io/mhsanaei/3x-ui — including via a leaked DOCKER_HUB_TOKEN (workflow lines 43-44) — can push an image carrying a hand-authored provenance predicate naming this repository, this workflow and any tag, and imagetools inspect will print it exactly the same way.
Why it matters: the PR description frames the goal as letting a deployer "check that it was built by this workflow, from this repository, at that tag". What this achieves is inspectable build metadata for images that are already legitimate, which is genuinely useful for debugging and SBOM tooling, but it is not tamper-evident and does not detect a compromised registry credential. Merging it under the stronger framing risks the claim ending up in release notes or documentation.
Recommendation: keep the change, describe it as build metadata rather than verifiable origin. If verifiable origin is the actual goal, it needs a signature — actions/attest-build-provenance, or cosign keyless — which does require id-token: write plus attestations: write. That is a real trade-off and worth deciding explicitly rather than inheriting by default; it is not an error in this diff, which is accurate about what it does.

Severity: Medium
Confidence: Low
Category: Reliability
Location: .github/workflows/docker.yml:59
Problem: sbom: true makes BuildKit run the default scanner image (docker/buildkit-syft-scanner) as an extra container step per target platform. This build targets five platforms (line 60: linux/amd64,linux/arm64/v8,linux/arm/v7,linux/arm/v6,linux/386), three of which run under the QEMU emulation configured at lines 34-35. Two consequences: the scan runs emulated on those platforms and adds non-trivial wall-clock to an already long five-platform CGO build, and it depends on the scanner image publishing manifests for linux/386 and linux/arm/v6 — if either is missing, the build fails outright with a no-matching-manifest error. I could not verify the scanner's published platform list from this environment, hence Low confidence.
Why it matters: docker.yml triggers only on push: tags: v*.*.* and workflow_dispatch (lines 7-11), and I confirmed no other workflow in .github/workflows/ builds or pushes this image. Nothing exercises this change on the pull request itself, so a hard failure or a large slowdown surfaces for the first time while cutting a release, when it is most disruptive.
Recommendation: before merging, run the workflow once via workflow_dispatch on this branch to see it succeed end to end and to measure the added time. The platform question can be settled directly with:

docker buildx imagetools inspect docker/buildkit-syft-scanner:stable-1 --raw

If a target platform is unsupported, either pin an explicit generator (sbom: generator=<image>) or drop sbom and keep provenance, which needs no scanner container.

Severity: Low
Confidence: Medium
Category: API design
Location: .github/workflows/docker.yml:58-59
Problem: a factual clarification plus a compatibility note. The images were not previously attestation-free: when provenance is unset, docker/build-push-action defaults to mode=min,inline-only=true, which records minimal provenance in the image config without adding a manifest. This diff moves that to a full mode=max attestation manifest per platform, plus an SBOM manifest per platform, each listed in the index with platform unknown/unknown. Setting provenance explicitly also causes the action to append builder-id=<run URL>, which is the part that actually ties the record to a run.
Why it matters: the tag pages on Docker Hub and GHCR will show these as extra architecture rows, and tooling that enumerates index entries — docker manifest inspect, registry mirrors, skopeo copy --all in air-gapped mirroring setups — will see and may copy them. docker run and Compose, which is how the READMEs and docs/content/docs/*/guide/installation.mdx tell users to consume the image, are unaffected.
Recommendation: no code change required; the description already flags the extra manifest. Worth one line in the release notes for the first tag that carries it, so mirror operators are not surprised.

Positive observations

The permissions analysis in the description holds up: I checked lines 3-5 and 46-51, and BuildKit attestations pushed through the existing registry logins need no id-token. Choosing BuildKit's built-in attestations over a separate signing action is also the right call for a step that pushes one build to two registries, since the attestation rides the manifest and needs no second credential. The provenance: mode=max string form is correct for build-push-action@v7, and the description is honest about mode=max recording build arguments — worth noting that this workflow passes no build-args and the Dockerfile declares only ARG TARGETARCH, so there is nothing sensitive to record here.

Verdict

Comment. The diff is correct and low-risk as written; before merging I would confirm the SBOM scanner covers linux/386 and linux/arm/v6 with a workflow_dispatch run, since a tag-only workflow gives no earlier signal, and would settle whether unsigned provenance meets the intent behind the change.

This review was generated automatically; a maintainer may follow up.

@kobihikri

Copy link
Copy Markdown
Author

Correction — I got a fact wrong in this PR, and I would rather flag it myself than let it sit.

I wrote that the pushed manifest "carries no provenance or SBOM attestation". That is half wrong, and the wrong half matters.

Provenance is already there. For public repositories, docker/build-push-action adds provenance attestations with mode=max by default — Docker's documentation states it plainly: "Public repos: provenance attestations with mode=max are automatically added". I checked published images and they do already carry attestation manifests. So the provenance: mode=max line in my diff makes existing behaviour explicit; it does not add anything new.

The SBOM is genuinely new. That part stands — the same page says "SBOM attestations aren't automatically added to the image", and sbom: true is what enables them.

I also wrote in the caveats that provenance: true gives "a smaller record". That is wrong as well: true resolves to max on a public repo, and the smaller setting is provenance: mode=min.

So the honest description of this PR is: it adds an SBOM attestation, and pins the provenance mode explicitly instead of relying on the default. Both are still defensible — an explicit line means the behaviour will not change quietly if the default ever does — but it is a smaller change than my description implied, and you should judge it on that basis rather than on what I originally wrote.

Happy to retitle and rewrite the description accordingly, or to close this if the SBOM alone is not worth the diff to you. Either is fine — just say which and I will act on it.

Apologies for the inaccuracy. It was caught by a maintainer reviewing the same change on another project, and they were right to.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request github_actions Pull requests that update GitHub Actions code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant