name: CSO Runtime Qualification # A private evaluator sends only gate results, public runtime identities, and an # optional opaque evaluation reference. Its held-out assertions remain outside # this repository. GitHub authenticates the dispatch actor; the protected # environment supplies the human review gate. The unprotected summary job runs # first so the reviewer sees what the dispatch claims before approving. on: repository_dispatch: types: [cso-runtime-qualified] permissions: contents: read packages: read attestations: read jobs: dispatch-summary: if: github.ref == 'refs/heads/main' && github.event_name == 'repository_dispatch' runs-on: ubuntu-24.04 timeout-minutes: 5 permissions: {} steps: - name: Require the bounded payload and summarize it for the approver run: | set -euo pipefail jq -e ' .action == "cso-runtime-qualified" and ((.client_payload | keys) as $keys | $keys == ["statements"] or ( $keys == ["evaluationRef", "statements"] and (.client_payload.evaluationRef | type == "string" and test("^sha256:[a-f0-9]{64}$")) )) and (.client_payload.statements | type == "array" and length == 10) and (.client_payload.statements | all( type == "object" and (.runtimeId | type == "string" and test("^[a-z0-9][a-z0-9._-]{0,100}$")) and (.image | type == "string" and test("^ghcr\\.io/garrytan/gstack/cso-staging/[a-z0-9-]+-(amd64|arm64)@sha256:[a-f0-9]{64}$")) and (.sourceCommit | type == "string" and test("^[a-f0-9]{40}$")) )) and ([.client_payload.statements[].sourceCommit] | unique | length == 1) ' "$GITHUB_EVENT_PATH" jq -r ' "## CSO runtime qualification dispatch", "", "- evaluationRef: `\(.client_payload.evaluationRef // "none")`", "- sourceCommit: `\(.client_payload.statements[0].sourceCommit)`", "", "| runtimeId | image |", "| --- | --- |", (.client_payload.statements[] | "| `\(.runtimeId)` | `\(.image)` |") ' "$GITHUB_EVENT_PATH" >> "$GITHUB_STEP_SUMMARY" qualify: needs: dispatch-summary if: github.ref == 'refs/heads/main' && github.event_name == 'repository_dispatch' runs-on: ubuntu-24.04 timeout-minutes: 40 environment: cso-runtime-release permissions: contents: read packages: read attestations: read actions: read steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: persist-credentials: false fetch-depth: 0 - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 with: bun-version: 1.4.2 - name: Require the configured private-evaluator actor env: CSO_QUALIFICATION_ACTOR: ${{ vars.CSO_QUALIFICATION_ACTOR }} run: | set -euo pipefail test -n "$CSO_QUALIFICATION_ACTOR" test "$GITHUB_ACTOR" = "$CSO_QUALIFICATION_ACTOR" - name: Recheck public visibility and anonymous pulls for every qualified digest env: GH_TOKEN: ${{ github.token }} run: | set -euo pipefail mkdir public-image-evidence jq -c '.client_payload.statements[]' "$GITHUB_EVENT_PATH" | while IFS= read -r statement; do runtime_id="$(printf '%s' "$statement" | jq -er '.runtimeId | select(test("^[a-z0-9][a-z0-9._-]{0,100}$"))')" image="$(printf '%s' "$statement" | jq -er '.image | select(test("^ghcr\\.io/garrytan/gstack/cso-staging/[a-z0-9-]+-(amd64|arm64)@sha256:[a-f0-9]{64}$"))')" platform="$(printf '%s' "$statement" | jq -er '.platform | select(. == "linux/amd64" or . == "linux/arm64")')" bun run scripts/cso-public-ghcr.ts verify \ --image "$image" --platform "$platform" --repository "$GITHUB_REPOSITORY" \ --output "public-image-evidence/$runtime_id.json" --remove-after done - name: Verify each staged image attestation and normalize qualification statements env: GH_TOKEN: ${{ github.token }} run: | set -euo pipefail signer="https://github.com/$GITHUB_REPOSITORY/.github/workflows/cso-runtime-images.yml@refs/heads/main" workflow="https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" qualified_at="$(date -u +%Y-%m-%dT%H:%M:%S.000Z)" mkdir qualification-evidence jq -c '.client_payload.statements[]' "$GITHUB_EVENT_PATH" | while IFS= read -r statement; do runtime_id="$(printf '%s' "$statement" | jq -er '.runtimeId | select(test("^[a-z0-9][a-z0-9._-]{0,100}$"))')" image="$(printf '%s' "$statement" | jq -er '.image | select(test("^ghcr\\.io/garrytan/gstack/cso-staging/[a-z0-9-]+-(amd64|arm64)@sha256:[a-f0-9]{64}$"))')" source_commit="$(printf '%s' "$statement" | jq -er '.sourceCommit | select(test("^[a-f0-9]{40}$"))')" output="qualification-evidence/$runtime_id" mkdir "$output" gh attestation verify "oci://$image" --repo "$GITHUB_REPOSITORY" \ --cert-identity "$signer" --source-ref refs/heads/main --source-digest "$source_commit" \ --deny-self-hosted-runners --predicate-type https://slsa.dev/provenance/v1 \ --format json > "$output/provenance-verification.json" gh attestation verify "oci://$image" --repo "$GITHUB_REPOSITORY" \ --cert-identity "$signer" --source-ref refs/heads/main --source-digest "$source_commit" \ --deny-self-hosted-runners --predicate-type https://spdx.dev/Document/v2.3 \ --format json > "$output/sbom-verification.json" provenance_digest="sha256:$(sha256sum "$output/provenance-verification.json" | cut -d ' ' -f 1)" sbom_digest="sha256:$(sha256sum "$output/sbom-verification.json" | cut -d ' ' -f 1)" printf '%s' "$statement" | jq \ --arg workflow "$workflow" --arg qualifiedAt "$qualified_at" \ --arg provenanceDigest "$provenance_digest" --arg sbomDigest "$sbom_digest" \ '{ schemaVersion:.schemaVersion,helperAbi:.helperAbi,state:.state, buildRevision:.buildRevision,runtimeId:.runtimeId,stack:.stack, platform:.platform,image:.image,versions:.versions,sourceCommit:.sourceCommit, checks:.checks,workflow:$workflow,qualifiedAt:$qualifiedAt, provenanceDigest:$provenanceDigest,sbomDigest:$sbomDigest }' \ > "$output/qualified-runtime.json" done - name: Require one successful protected-main staging run with every native gate passed env: GH_TOKEN: ${{ github.token }} run: | set -euo pipefail mkdir staging-run-evidence run_id="$(bun run scripts/cso-staging-run.ts run-id --repository "$GITHUB_REPOSITORY" \ qualification-evidence/*/provenance-verification.json)" gh api "repos/$GITHUB_REPOSITORY/actions/runs/$run_id" > staging-run-evidence/run.json gh api --paginate --slurp "repos/$GITHUB_REPOSITORY/actions/runs/$run_id/jobs?filter=latest&per_page=100" \ > staging-run-evidence/jobs.json bun run scripts/cso-staging-run.ts verify --repository "$GITHUB_REPOSITORY" --run-id "$run_id" \ --event "$GITHUB_EVENT_PATH" --run staging-run-evidence/run.json --jobs staging-run-evidence/jobs.json gh run download "$run_id" --repo "$GITHUB_REPOSITORY" \ --name cso-requalification-triggers --dir requalification-download mv requalification-download/requalification.json qualification-evidence/requalification.json rmdir requalification-download - name: Enforce the complete release contract before retaining evidence run: | set -euo pipefail bun run scripts/cso-runtime-promotion.ts \ --evidence-root qualification-evidence \ --output runtime-catalog.proof.json rm runtime-catalog.proof.json - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 with: name: cso-qualified-runtime-statements path: | qualification-evidence public-image-evidence staging-run-evidence if-no-files-found: error retention-days: 30