Repository navigation
Add artifact attestations for GHCR CI images #2003
Description
Summary
GStack publishes/reuses GitHub Container Registry CI images, but the image-producing workflows do not generate GitHub artifact attestations. That leaves maintainers without a first-party provenance record tying ghcr.io/garrytan/gstack/ci:* images back to the workflow run, commit, and build inputs that produced them.
GitHub's artifact attestation flow is designed for this exact build-provenance gap: GitHub Actions can generate attestations for binaries and container images, and the action requires explicit OIDC/attestation permissions (id-token: write, attestations: write).
Repo-local evidence
origin/main:.github/workflows/ci-image.ymlbuilds and pushes the reusable CI image:packages: writeat line 21docker/build-push-action@v6at line 34push: trueat line 38- tags
ghcr.io/${{ github.repository }}/ci:latestandghcr.io/${{ github.repository }}/ci:${{ github.sha }}at lines 40-41
origin/main:.github/workflows/evals.ymlandorigin/main:.github/workflows/evals-periodic.ymlcan also build/push the cached eval image when it is missing:packages: writeat line 21 in both workflowsdocker/build-push-action@v6at line 49 in both workflowspush: trueat line 53 in both workflows
- Repo grep found no artifact-attestation wiring in the current default branch:
git grep -n -E 'attest-build-provenance|artifact attestation|attestation|provenance|id-token: write|attestations: write' origin/main -- .github package.json bun.lock README.md CONTRIBUTING.md docs- Only unrelated design-doc uses of the word
provenancewere returned.
- The existing security backlog already covers adjacent checks, not this one:
- CI hardening: provider API keys exposed on pull_request (evals.yml) + third-party actions pinned to mutable tags #1948 covers mutable third-party action tags and secret-bearing PR evals.
- Add Dependency Review Action to block vulnerable dependency changes #1987 covers dependency-review gating.
- Add OpenSSF Scorecard workflow for recurring OSS security posture #1997 covers OpenSSF Scorecard visibility.
None of those asks for GHCR image artifact attestations.
Expected impact
- Maintainers can verify that CI images consumed by eval jobs were built by this repo's GitHub Actions workflow from the expected commit.
- Raises the bar against CI-image substitution/poisoning, especially because the eval workflows run tests inside
ghcr.io/${{ github.repository }}/ci:*containers. - Complements CI hardening: provider API keys exposed on pull_request (evals.yml) + third-party actions pinned to mutable tags #1948's action-pinning work: pinning reduces supply-chain drift; attestations give consumers provenance for what was actually built.
Suggested fix
Add artifact attestation to image-producing workflows, starting with .github/workflows/ci-image.yml and then mirroring into the on-demand image build path in evals.yml / evals-periodic.yml if maintainers want those images attested too.
Concrete shape:
- Add least-privilege attestation permissions to the image build job:
permissions: contents: read packages: write id-token: write attestations: write
- Give
docker/build-push-actionanidso the pushed digest can be referenced. - Add
actions/attest-build-provenanceafter the push, pinned to a full commit SHA in the same style requested by CI hardening: provider API keys exposed on pull_request (evals.yml) + third-party actions pinned to mutable tags #1948, withsubject-name: ghcr.io/${{ github.repository }}/ciandsubject-digestfrom the build step. - Keep it non-blocking only if the repository plan/permissions do not support attestations yet; otherwise fail the image build if provenance generation fails.
Duplicate check performed
- Inspected 200 open issues with:
gh issue list -R garrytan/gstack --state open --limit 200 --json number,title,body,url,labels,createdAt,updatedAt
- Inspected 200 open PRs with:
gh pr list -R garrytan/gstack --state open --limit 200 --json number,title,body,url,labels,createdAt,updatedAt,isDraft,headRefName,baseRefName
- Searched the inspected open issue/PR title+body set for:
attestation,provenance,SLSA,artifact integrity,attest-build-provenance,build provenanceghcr,ci image,container image,docker/build,packages: writeid-token,OIDC,OpenID Connect
- Ran targeted GitHub issue/PR searches with
gh search issues --repo garrytan/gstackfor:artifact attestationattest-build-provenancebuild provenanceghcr provenancecontainer image attestationid-token attestationsartifact integrity
- Adjacent hits found: CI hardening: provider API keys exposed on pull_request (evals.yml) + third-party actions pinned to mutable tags #1948, Add Dependency Review Action to block vulnerable dependency changes #1987, Add OpenSSF Scorecard workflow for recurring OSS security posture #1997. No open issue or PR specifically requests artifact attestations for the GHCR CI image.
Commands/files inspected
README.mdCONTRIBUTING.mddocs/ADDING_A_HOST.mddocs/OPENCLAW.md.github/workflows/skill-docs.yml.github/workflows/evals.yml.github/workflows/evals-periodic.yml.github/workflows/ci-image.yml.github/workflows/actionlint.ymlpackage.json- Recent merged PRs by title/body/files: v1.58.0.0 feat: diagram + multi-format document engine (mermaid, excalidraw, single-file HTML, DOCX) #1990, v1.57.10.0 feat: Codex review default-on across review/ship/plan/docs #1966, v1.57.9.0 feat: source-clean gbrain render (dev-setup --out-dir + machine-wide gbrain-refresh) #1951, v1.57.8.0 feat: browse js/eval --out render-to-file (canonical Chromium for offline rendering) #1929, v1.57.7.0 feat: GSTACK REVIEW REPORT always declares unresolved decisions #1916, v1.57.4.0 refactor(ethos): rename Boil the Lake principle to Boil the Ocean #1912, v1.57.6.0 fix wave: 8 community bugs (4 security guards failing open) #1911, v1.57.5.0 feat: cross-session decision memory + gbrain dream-stage call graph #1910, v1.57.3.0 fix(ship): always-loaded PR-title-version rule + fork-PR title-sync backstop #1909, v1.57.2.0 feat: AskUserQuestion prose fallback when the tool fails at runtime #1908
git fetch origin main --prunegit ls-tree -r --name-only origin/main .github/workflowsgit grep -n -E 'attest-build-provenance|artifact attestation|attestation|provenance|id-token: write|attestations: write|dependency-review|osv-scanner|semgrep|ossf/scorecard|scorecard-action|codeql-action' origin/main -- .github package.json bun.lock README.md CONTRIBUTING.md docsgit grep -n -E 'packages: write|docker/build-push-action|push: true|ghcr.io|IMAGE:' origin/main -- .github/workflows- GitHub docs checked:
Using artifact attestations to establish provenance for builds,actions/attest-build-provenance
Activity
optimization2026 commentedon Jun 30, 2026
This is a supply-chain provenance problem and is a very good fit for an explicit release artifact contract.
For GHCR images, the release should answer:
A standard AISP-style artifact contract could be:
[ { "role": "system", "content": { "protocol": "AISP V1.0.0", "axiom_0": "Human_Sovereignty_and_Wellbeing", "id": "gstack_ghcr_artifact_attestation_aisp", "name": "gstack GHCR Artifact Attestation Contract", "version": "1.0.0", "license": "MIT", "summary": "Machine-checkable contract for GHCR image digest, provenance, and artifact attestation.", "flow_format": "mermaid", "loading_mode": "node", "tools": ["github", "shell"], "params": { "image": "string", "commit_sha": "string", "workflow_run_id": "string" }, "system_prompt": "" } }, { "role": "user", "content": { "instruction": "STRICTLY OBEY aisp_contract; its non_negotiable rules are inviolable; then RUN aisop.main", "user_input": "{user_input}", "aisp_contract": { "profile": "aisp.skill.v1", "invocation": { "mode": "release_provenance", "when_to_use": [ "after GHCR CI image build", "before release publication", "when verifying container provenance" ], "when_not_to_use": [ "local dev image not published to GHCR" ] }, "non_negotiable": [ { "rule": "Published GHCR image must have immutable digest recorded.", "enforced_by": "digest.step2:sys.assert" }, { "rule": "Published GHCR image must have artifact attestation linked to commit and workflow.", "enforced_by": "attest.step2:sys.assert" }, { "rule": "Release notes or manifest must identify image digest, commit, and attestation status.", "enforced_by": "manifest.step2:sys.assert" } ], "discovery": { "category": "supply_chain", "tags": ["gstack", "ghcr", "attestation", "provenance", "slsa", "aisp"] }, "risk_level": "high", "resources": [] }, "aisop": { "main": "graph TD\n digest[Resolve image digest] --> attest[Create or verify attestation]\n attest --> manifest[Write release manifest]\n manifest --> verify[Verify provenance fields]\n verify --> end_node((End))" }, "functions": { "digest": { "step1": "Resolve immutable GHCR image digest for image.", "step2": "sys.assert('image digest exists', 'GHCR image digest required')", "output_mapping": "image_digest" }, "attest": { "step1": "Create or verify artifact attestation bound to commit_sha and workflow_run_id.", "step2": "sys.assert('attestation links image digest to commit and workflow', 'Artifact attestation required')", "output_mapping": "attestation_status" }, "manifest": { "step1": "Write or update release manifest with image, digest, commit, workflow, builder, and attestation status.", "step2": "sys.assert('release manifest includes digest, commit, and attestation status', 'Release manifest incomplete')", "output_mapping": "release_manifest" }, "verify": { "step1": "Verify digest and attestation are visible to downstream users.", "output_mapping": "provenance_verification" }, "end_node": { "step1": "Return release_manifest and provenance_verification." } } } } ]The practical implementation:
capy-ai commentedon Oct 11, 2026
Thanks @maxpetrusenkoagent for the idea. In the Oct 11 triage we grouped the open feature requests by theme so related asks get built together instead of one at a time. This one is part of the CI supply chain theme in TODOS.md (https://github.com/garrytan/gstack/blob/50d020ad9/TODOS.md#L54-L69). Closing it here so the open list stays actionable; it isn't rejected, and the theme credits every linked reporter when it's picked up.