v1.87.0.0 feat: add verified CSO audits and replayable repair bundles (#2852)

* feat(cso): add verified audits and replayable repair bundles

* fix(cso): harden qualification and setup boundaries

* fix(cso): assemble security canaries at runtime

* fix(cso): bound release proof and maintenance work

Co-Authored-By: OpenAI Codex <noreply@openai.com>

* fix(cso): require complete evaluation reports

Co-Authored-By: OpenAI Codex <noreply@openai.com>

* fix(cso): replay expired snapshots from supplied source

Co-Authored-By: OpenAI Codex <noreply@openai.com>

* test(cso): synchronize DNS cancellation assertion

Co-Authored-By: OpenAI Codex <noreply@openai.com>

* chore(ship): exempt repository owner from liveness proof

Co-Authored-By: OpenAI Codex <noreply@openai.com>

* test(cso): make recheck retention overlap deterministic

Co-Authored-By: OpenAI Codex <noreply@openai.com>

* chore: bump version and changelog (v1.85.0.0)

Co-Authored-By: OpenAI Codex <noreply@openai.com>

* fix(cso): pass native release gates

Co-Authored-By: OpenAI Codex <noreply@openai.com>

* chore: move release to v1.86.0.0

Co-Authored-By: OpenAI Codex <noreply@openai.com>

* fix(cso): resolve rechecks by finding

Co-Authored-By: OpenAI Codex <noreply@openai.com>

* chore: move release to v1.87.0.0

Co-Authored-By: OpenAI Codex <noreply@openai.com>

* fix(cso): pass macOS and Windows release gates

Normalize BSD wc output, compare Windows paths by filesystem identity, preserve portable snapshot race coverage, and narrow POSIX-only Windows fixtures.

Co-Authored-By: OpenAI Codex <noreply@openai.com>

* fix(cso): harden native verification gates

* fix(cso): refine Windows native diagnostics

* test(cso): isolate Windows Git startup failure

* test(cso): stabilize Windows native diagnostics

* fix(cso): support hardened Git on Windows

* fix(cso): close final verification gaps

* test(cso): bound cold Docker fixture setup

* fix(cso): restore cross-platform free-suite gates

---------

Co-authored-by: OpenAI Codex <noreply@openai.com>
This commit is contained in:
Garry Tan
2026-09-14 15:14:58 -07:00
committed by GitHub
co-authored by OpenAI Codex
parent 9f81911136
commit 4a3c6a8a3c
160 changed files with 24697 additions and 2288 deletions
+7 -3
View File
@@ -27,9 +27,13 @@ changed. -->
- **Verified live by:**
- **Did NOT test:**
## Liveness proof (required)
## Liveness proof (required for external contributors)
<!-- Attach a screenshot of your own machine with the text `GSTACK PR` typed LIVE
<!-- Repository owner @garrytan is explicitly exempt. Agents must verify the
authenticated GitHub login with `gh api user --jq .login`; Git author metadata
alone is not sufficient.
All other contributors: attach a screenshot of your own machine with the text `GSTACK PR` typed LIVE
into a real surface — terminal prompt, a shell command, your browser
address/search bar, an editor buffer. It must be TYPED INTO A LIVE UI, not drawn,
overlaid, or edited onto the image. A painted-on `GSTACK PR` is an automatic
@@ -37,7 +41,7 @@ close. This confirms a human opened this PR. -->
## Checklist
- [ ] Liveness screenshot attached: `GSTACK PR` typed live into a real surface (not edited onto the image)
- [ ] Liveness screenshot attached (`GSTACK PR` typed live into a real surface) or PR author is @garrytan (owner exemption)
- [ ] This is not a generated-file-only diff (I edited the source/template and regenerated)
- [ ] No ETHOS.md edits, and no changes to voice / founder perspective / YC references
- [ ] New public command / external service / host adapter has an accepted issue linked (or N/A)
+8 -1
View File
@@ -34,11 +34,18 @@ RUN printf 'Acquire::Retries "5";\nAcquire::http::Timeout "30";\nAcquire::https:
# poppler-utils: make-pdf's e2e gates hard-require pdftotext/pdffonts/pdfinfo in CI.
RUN for i in 1 2 3; do \
apt-get update && apt-get install -y --no-install-recommends \
git curl unzip xz-utils ca-certificates jq bc gpg python3 file poppler-utils && break || \
git curl unzip xz-utils ca-certificates jq bc gpg python3 file poppler-utils gcc libc6-dev && break || \
(echo "apt retry $i/3 after failure"; sleep 10); \
done \
&& rm -rf /var/lib/apt/lists/*
# Direct builds produce the trusted CSO launcher and watchdog. Check the exact
# static-C capability here so the cached eval image cannot reach a slice without it.
RUN printf 'int main(void) { return 0; }\n' > /tmp/gstack-cso-cc-probe.c \
&& cc -std=c11 -static /tmp/gstack-cso-cc-probe.c -o /tmp/gstack-cso-cc-probe \
&& /tmp/gstack-cso-cc-probe \
&& rm -f /tmp/gstack-cso-cc-probe.c /tmp/gstack-cso-cc-probe
# GitHub CLI
RUN curl --retry 5 --retry-delay 5 --retry-connrefused -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
| gpg --dearmor -o /usr/share/keyrings/githubcli-archive-keyring.gpg \
+374
View File
@@ -0,0 +1,374 @@
name: Stage CSO Runtime Images
# Pull requests build every reviewed input on its native architecture without
# publishing. The cso-runtime-release environment must require a maintainer
# reviewer and permit deployments from protected main only. Staging digests
# never enter the execution catalog automatically.
on:
pull_request:
paths:
- '.github/workflows/cso-runtime-images.yml'
- 'lib/cso/**'
- 'scripts/cso-image-matrix.ts'
- 'scripts/cso-verify-runtime-base.ts'
- 'scripts/cso-runtime-promotion.ts'
- 'scripts/cso-public-ghcr.ts'
- 'test/cso-*.test.ts'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: cso-runtime-images-${{ github.event.pull_request.number || 'release' }}
cancel-in-progress: false
jobs:
reviewed-inputs:
if: github.event_name == 'pull_request' || (github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch')
runs-on: ubuntu-24.04
outputs:
matrix: ${{ steps.inputs.outputs.matrix }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.0
- name: Validate committed image digests and complete platform matrix
id: inputs
run: |
set -eu
matrix="$(bun run scripts/cso-image-matrix.ts)"
printf 'matrix=%s\n' "$matrix" >> "$GITHUB_OUTPUT"
validate-native:
needs: reviewed-inputs
if: github.event_name == 'pull_request'
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.reviewed-inputs.outputs.matrix) }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 35
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
with:
bun-version: 1.4.0
- uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e
- name: Require native Docker and verify reviewed source manifests
env:
CSO_ARCH: ${{ matrix.arch }}
CSO_PLATFORM: ${{ matrix.platform }}
CSO_STACK: ${{ matrix.stack }}
run: |
set -eu
case "$CSO_ARCH:$(uname -m)" in
amd64:x86_64|arm64:aarch64) ;;
*) echo 'Native architecture mismatch; emulation cannot validate a runtime.' >&2; exit 1 ;;
esac
bun run scripts/cso-verify-runtime-base.ts
- name: Compile native trusted image helpers
run: |
bun build --compile \
--no-compile-autoload-dotenv \
--no-compile-autoload-bunfig \
--no-compile-autoload-tsconfig \
--no-compile-autoload-package-json \
lib/cso/verifier.ts --outfile lib/cso/images/gstack-cso-verifier
bun build --compile \
--no-compile-autoload-dotenv \
--no-compile-autoload-bunfig \
--no-compile-autoload-tsconfig \
--no-compile-autoload-package-json \
lib/cso/preparation-container.ts --outfile lib/cso/images/gstack-cso-preparation
- name: Build the reviewed runtime without publishing
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a
with:
context: lib/cso/images
file: lib/cso/images/${{ matrix.stack }}.Dockerfile
platforms: ${{ matrix.platform }}
build-args: |
BASE_IMAGE=${{ matrix.baseImage }}
UV_IMAGE=${{ matrix.uvImage }}
tags: localhost/gstack-cso-pr-${{ matrix.stack }}:${{ github.sha }}-${{ matrix.arch }}
load: true
push: false
provenance: false
sbom: false
- name: Check the built image identity and fail-closed entrypoint
env:
CSO_IMAGE: localhost/gstack-cso-pr-${{ matrix.stack }}:${{ github.sha }}-${{ matrix.arch }}
run: |
set -eu
test "$(docker image inspect "$CSO_IMAGE" --format '{{.Config.User}}')" = "10001:10001"
test "$(docker image inspect "$CSO_IMAGE" --format '{{json .Config.Entrypoint}}')" = '["/opt/cso/entrypoint"]'
docker run --rm --pull never --network none --read-only --cap-drop ALL \
--security-opt no-new-privileges --entrypoint /opt/cso/entrypoint \
"$CSO_IMAGE" /bin/true
docker image rm "$CSO_IMAGE"
stage:
needs: reviewed-inputs
if: github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch'
environment: cso-runtime-release
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.reviewed-inputs.outputs.matrix) }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 40
permissions:
contents: read
packages: write
id-token: write
attestations: write
artifact-metadata: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.0
- name: Require the declared native architecture and Docker daemon
env:
CSO_ARCH: ${{ matrix.arch }}
run: |
set -eu
case "$CSO_ARCH:$(uname -m)" in
amd64:x86_64|arm64:aarch64) ;;
*) echo 'Native architecture mismatch; emulation cannot qualify a runtime.' >&2; exit 1 ;;
esac
docker --host unix:///var/run/docker.sock info
- uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4
- uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Verify reviewed source manifests and exact base versions
env:
CSO_PLATFORM: ${{ matrix.platform }}
CSO_STACK: ${{ matrix.stack }}
run: bun run scripts/cso-verify-runtime-base.ts
- name: Compile native trusted HTTP verifier for the runtime image
run: |
bun build --compile \
--no-compile-autoload-dotenv \
--no-compile-autoload-bunfig \
--no-compile-autoload-tsconfig \
--no-compile-autoload-package-json \
lib/cso/verifier.ts --outfile lib/cso/images/gstack-cso-verifier
bun build --compile \
--no-compile-autoload-dotenv \
--no-compile-autoload-bunfig \
--no-compile-autoload-tsconfig \
--no-compile-autoload-package-json \
lib/cso/preparation-container.ts --outfile lib/cso/images/gstack-cso-preparation
- name: Prepare immutable staging tag
id: image
env:
CSO_STACK: ${{ matrix.stack }}
CSO_ARCH: ${{ matrix.arch }}
run: |
set -eu
image="ghcr.io/${GITHUB_REPOSITORY,,}/cso-staging/$CSO_STACK-$CSO_ARCH"
printf 'name=%s\n' "$image" >> "$GITHUB_OUTPUT"
printf 'tag=%s:%s-%s\n' "$image" "$GITHUB_SHA" "$GITHUB_RUN_ID" >> "$GITHUB_OUTPUT"
- name: Build native staging image with SBOM and provenance
id: build
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
with:
context: lib/cso/images
file: lib/cso/images/${{ matrix.stack }}.Dockerfile
platforms: ${{ matrix.platform }}
build-args: |
BASE_IMAGE=${{ matrix.baseImage }}
UV_IMAGE=${{ matrix.uvImage }}
tags: ${{ steps.image.outputs.tag }}
push: true
provenance: mode=max
attests: type=sbom,generator=${{ matrix.sbomGeneratorImage }}
- name: Extract the immutable image SBOM
env:
CSO_IMAGE: ${{ steps.image.outputs.name }}@${{ steps.build.outputs.digest }}
run: |
set -eu
docker buildx imagetools inspect "$CSO_IMAGE" --format '{{ json .SBOM.SPDX }}' > sbom.spdx.json
jq -e '.spdxVersion == "SPDX-2.3" and (.packages | type == "array")' sbom.spdx.json
- name: Sign build provenance
uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.2.0
with:
subject-name: ${{ steps.image.outputs.name }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
- name: Sign the image SBOM
uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.2.0
with:
subject-name: ${{ steps.image.outputs.name }}
subject-digest: ${{ steps.build.outputs.digest }}
sbom-path: sbom.spdx.json
push-to-registry: true
- name: Verify provenance and SBOM signer identities
env:
GH_TOKEN: ${{ github.token }}
CSO_IMAGE: ${{ steps.image.outputs.name }}@${{ steps.build.outputs.digest }}
run: |
set -eu
signer="https://github.com/$GITHUB_REPOSITORY/.github/workflows/cso-runtime-images.yml@refs/heads/main"
gh attestation verify "oci://$CSO_IMAGE" --repo "$GITHUB_REPOSITORY" --cert-identity "$signer" --source-ref refs/heads/main --source-digest "$GITHUB_SHA" --deny-self-hosted-runners --predicate-type https://slsa.dev/provenance/v1 --format json > provenance-verification.json
gh attestation verify "oci://$CSO_IMAGE" --repo "$GITHUB_REPOSITORY" --cert-identity "$signer" --source-ref refs/heads/main --source-digest "$GITHUB_SHA" --deny-self-hosted-runners --predicate-type https://spdx.dev/Document/v2.3 --format json > sbom-verification.json
- name: Require a public package and anonymously load the exact staged digest
env:
GH_TOKEN: ${{ github.token }}
CSO_IMAGE: ${{ steps.image.outputs.name }}@${{ steps.build.outputs.digest }}
CSO_PLATFORM: ${{ matrix.platform }}
run: |
set -euo pipefail
bun run scripts/cso-public-ghcr.ts verify \
--image "$CSO_IMAGE" --platform "$CSO_PLATFORM" \
--repository "$GITHUB_REPOSITORY" --output public-image.json
- name: Install helper dependencies and compile trusted tools
run: |
bun install --frozen-lockfile --ignore-scripts
bun run build:cso
- name: Exercise containment against the staged digest
env:
GSTACK_CSO_DOCKER_TESTS: "1"
GSTACK_CSO_TEST_IMAGE: ${{ steps.image.outputs.name }}@${{ steps.build.outputs.digest }}
GSTACK_CSO_TEST_PLATFORM: ${{ matrix.platform }}
GSTACK_CSO_TEST_STACK: ${{ matrix.stack }}
GSTACK_CSO_EXPECTED_VERSIONS: ${{ toJSON(matrix.versions) }}
DOCKER_HOST: unix:///var/run/docker.sock
run: bun test --max-concurrency 1 test/cso-docker-integration.test.ts
- name: Record staging evidence without promoting the catalog
env:
CSO_BUILD_REVISION: ${{ matrix.inputRevision }}
CSO_IMAGE: ${{ steps.image.outputs.name }}@${{ steps.build.outputs.digest }}
CSO_PLATFORM: ${{ matrix.platform }}
CSO_RUNTIME_ID: ${{ matrix.runtimeId }}
CSO_STACK: ${{ matrix.stack }}
CSO_VERSIONS: ${{ toJSON(matrix.versions) }}
run: |
set -eu
sha256sum public-image.json sbom.spdx.json provenance-verification.json sbom-verification.json > evidence.sha256
sbom_digest="sha256:$(sha256sum sbom.spdx.json | cut -d ' ' -f 1)"
provenance_digest="sha256:$(sha256sum provenance-verification.json | cut -d ' ' -f 1)"
workflow="https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
jq -n --arg buildRevision "$CSO_BUILD_REVISION" --arg runtimeId "$CSO_RUNTIME_ID" \
--arg image "$CSO_IMAGE" --arg platform "$CSO_PLATFORM" --arg stack "$CSO_STACK" \
--arg commit "$GITHUB_SHA" --arg workflow "$workflow" --arg sbom "$sbom_digest" \
--arg provenance "$provenance_digest" --argjson versions "$CSO_VERSIONS" \
'{schemaVersion:1,helperAbi:3,state:"staged",buildRevision:$buildRevision,runtimeId:$runtimeId,image:$image,platform:$platform,stack:$stack,versions:$versions,sourceCommit:$commit,workflow:$workflow,sbomDigest:$sbom,provenanceDigest:$provenance,verifiedProvenance:true,qualification:"pending native and private release gates; no automatic promotion"}' > staged-image.json
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: cso-staged-${{ matrix.stack }}-${{ matrix.arch }}
path: |
staged-image.json
public-image.json
sbom.spdx.json
provenance-verification.json
sbom-verification.json
evidence.sha256
if-no-files-found: error
retention-days: 30
qualify-native:
needs: [reviewed-inputs, stage]
if: github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch'
environment: cso-runtime-release
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.reviewed-inputs.outputs.matrix) }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 40
permissions:
contents: read
packages: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.0
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: cso-staged-${{ matrix.stack }}-${{ matrix.arch }}
path: staged-application
- if: matrix.stack == 'rails'
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: cso-staged-postgresql-${{ matrix.arch }}
path: staged-postgresql
- name: Require native Docker and anonymously load the signed staged inputs
env:
GH_TOKEN: ${{ github.token }}
CSO_ARCH: ${{ matrix.arch }}
CSO_PLATFORM: ${{ matrix.platform }}
CSO_STACK: ${{ matrix.stack }}
run: |
set -eu
case "$CSO_ARCH:$(uname -m)" in amd64:x86_64|arm64:aarch64) ;; *) exit 1 ;; esac
docker --host unix:///var/run/docker.sock info
(cd staged-application && sha256sum --check evidence.sha256)
if test "$CSO_STACK" = rails; then
(cd staged-postgresql && sha256sum --check evidence.sha256)
fi
app="$(jq -er --arg platform "$CSO_PLATFORM" --arg stack "$CSO_STACK" 'select(.state == "staged" and .platform == $platform and .stack == $stack) | .image' staged-application/staged-image.json)"
bun run scripts/cso-public-ghcr.ts verify \
--image "$app" --platform "$CSO_PLATFORM" \
--repository "$GITHUB_REPOSITORY" --output application-public-image.json
printf 'GSTACK_CSO_TEST_IMAGE=%s\n' "$app" >> "$GITHUB_ENV"
printf 'GSTACK_CSO_EXPECTED_VERSIONS=%s\n' "$(jq -c '.versions' staged-application/staged-image.json)" >> "$GITHUB_ENV"
if test "$CSO_STACK" = rails; then
pg="$(jq -er --arg platform "$CSO_PLATFORM" 'select(.state == "staged" and .platform == $platform and .stack == "postgresql") | .image' staged-postgresql/staged-image.json)"
bun run scripts/cso-public-ghcr.ts verify \
--image "$pg" --platform "$CSO_PLATFORM" \
--repository "$GITHUB_REPOSITORY" --output postgresql-public-image.json
printf 'GSTACK_CSO_TEST_POSTGRES_IMAGE=%s\n' "$pg" >> "$GITHUB_ENV"
printf 'GSTACK_CSO_TEST_POSTGRES_VERSION=%s\n' "$(jq -er '.versions.postgresql' staged-postgresql/staged-image.json)" >> "$GITHUB_ENV"
fi
- name: Compile trusted helpers and run the matching staged cold-start journey
env:
GSTACK_CSO_DOCKER_TESTS: "1"
GSTACK_CSO_TEST_PLATFORM: ${{ matrix.platform }}
GSTACK_CSO_TEST_STACK: ${{ matrix.stack }}
DOCKER_HOST: unix:///var/run/docker.sock
run: |
set -eu
bun install --frozen-lockfile --ignore-scripts
bun run build:cso
bun run test:cso:docker
- name: Record native gate evidence without claiming runtime qualification
env:
CSO_STACK: ${{ matrix.stack }}
CSO_PLATFORM: ${{ matrix.platform }}
run: |
set -eu
jq --arg stack "$CSO_STACK" --arg platform "$CSO_PLATFORM" '
. + {
state:"native-gates-passed",
nativeChecks:(if $stack == "postgresql" then {
containmentPassed:true,coldStartPassed:true,multiDatabasePassed:true,readinessPassed:true
} else {
containmentPassed:true,coldStartPassed:true,positiveNegativeAssertionsPassed:true,
acquisitionPublicOnlyPassed:true,offlineLifecyclePassed:true
} + (if $stack == "rails" then {
railsSqlitePassed:true,railsPostgresqlPassed:true,nativeExtensionsPassed:true
} else {} end) end),
privateChecks:{heldOutRepairPassed:"pending",accuracyGatesPassed:"pending",secretCanaryPassed:"pending",watchdogCleanupPassed:"pending"},
qualified:false,
promotion:"prohibited until authenticated private qualification evidence completes every release gate"
}' staged-application/staged-image.json > qualification-evidence.json
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: cso-qualification-gates-${{ matrix.stack }}-${{ matrix.arch }}
path: qualification-evidence.json
if-no-files-found: error
retention-days: 30
+181
View File
@@ -0,0 +1,181 @@
name: Propose CSO Runtime Catalog Promotion
# This workflow never deploys a catalog. It converts authenticated
# same-repository qualification artifacts into an attested candidate, verifies
# those exact bytes, and opens a normal source-review PR from a protected job.
on:
workflow_dispatch:
inputs:
qualification_run_id:
description: Successful protected-main run containing cso-qualified-runtime-statements
required: true
type: string
permissions:
contents: read
actions: read
concurrency:
group: cso-runtime-catalog-promotion
cancel-in-progress: false
jobs:
propose:
if: github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch'
runs-on: ubuntu-24.04
timeout-minutes: 40
environment: cso-runtime-release
permissions:
contents: write
pull-requests: write
actions: read
packages: read
id-token: write
attestations: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: true
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
with:
bun-version: 1.4.0
- name: Authenticate the completed qualification run
env:
GH_TOKEN: ${{ github.token }}
CSO_QUALIFICATION_RUN: ${{ inputs.qualification_run_id }}
run: |
set -euo pipefail
case "$CSO_QUALIFICATION_RUN" in
''|*[!0-9]*) echo 'qualification_run_id must be numeric' >&2; exit 1 ;;
esac
gh api "repos/$GITHUB_REPOSITORY/actions/runs/$CSO_QUALIFICATION_RUN" > qualification-run.json
jq -e '
.status == "completed" and .conclusion == "success" and
.head_branch == "main" and .event == "repository_dispatch" and
.path == ".github/workflows/cso-runtime-qualification.yml" and
(.head_sha | test("^[a-f0-9]{40}$"))
' qualification-run.json
mkdir qualification-evidence
gh run download "$CSO_QUALIFICATION_RUN" --repo "$GITHUB_REPOSITORY" \
--name cso-qualified-runtime-statements --dir qualification-evidence
- name: Generate a fail-closed catalog candidate
env:
CSO_QUALIFICATION_RUN: ${{ inputs.qualification_run_id }}
run: |
set -euo pipefail
bun run scripts/cso-runtime-promotion.ts \
--evidence-root qualification-evidence \
--output runtime-catalog.candidate.json
source_commit="$(jq -er '.promotion.sourceCommit' runtime-catalog.candidate.json)"
workflow="$(jq -er '.promotion.workflow' runtime-catalog.candidate.json)"
test "$workflow" = "https://github.com/$GITHUB_REPOSITORY/actions/runs/$CSO_QUALIFICATION_RUN"
qualification_head="$(jq -er '.head_sha' qualification-run.json)"
gh api "repos/$GITHUB_REPOSITORY/compare/$source_commit...$qualification_head" > source-ancestry.json
jq -e '.status == "ahead" or .status == "identical"' source-ancestry.json
bun -e '
import candidate from "./runtime-catalog.candidate.json";
import {validateRuntimeCatalog} from "./lib/cso/runtime-catalog";
validateRuntimeCatalog(candidate);
'
bun run scripts/cso-runtime-promotion.ts validate-transition \
lib/cso/runtime-catalog.json runtime-catalog.candidate.json
- name: Prove the promotion and catalog contracts before signing
run: |
set -euo pipefail
bun install --frozen-lockfile --ignore-scripts
bun test --max-concurrency 1 test/cso-runtime-promotion.test.ts test/cso-distribution.test.ts
- name: Recheck public visibility and anonymous pulls before promotion
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
mkdir public-image-evidence
jq -c '.runtimes[]' runtime-catalog.candidate.json | while IFS= read -r runtime; do
runtime_id="$(printf '%s' "$runtime" | jq -er '.id | select(test("^[a-z0-9][a-z0-9._-]{0,100}$"))')"
image="$(printf '%s' "$runtime" | jq -er '.image')"
platform="$(printf '%s' "$runtime" | 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: Attest the exact review candidate
uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6
with:
subject-path: runtime-catalog.candidate.json
- name: Verify and record the exact candidate attestation
env:
GH_TOKEN: ${{ github.token }}
CSO_QUALIFICATION_RUN: ${{ inputs.qualification_run_id }}
run: |
set -euo pipefail
candidate_sha256="sha256:$(sha256sum runtime-catalog.candidate.json | cut -d ' ' -f 1)"
subject_sha256="${candidate_sha256#sha256:}"
signer="https://github.com/$GITHUB_REPOSITORY/.github/workflows/cso-runtime-promote.yml@refs/heads/main"
verified=0
for attempt in 1 2 3; do
if gh attestation verify runtime-catalog.candidate.json --repo "$GITHUB_REPOSITORY" \
--cert-identity "$signer" --source-ref refs/heads/main --source-digest "$GITHUB_SHA" \
--deny-self-hosted-runners --predicate-type https://slsa.dev/provenance/v1 \
--format json > candidate-attestation-verification.tmp; then
mv candidate-attestation-verification.tmp candidate-attestation-verification.json
verified=1
break
fi
rm -f candidate-attestation-verification.tmp
if test "$attempt" -lt 3; then sleep "$((attempt * 5))"; fi
done
test "$verified" -eq 1
statement_set_digest="$(bun run scripts/cso-attestation-evidence.ts digest \
candidate-attestation-verification.json https://slsa.dev/provenance/v1 "$subject_sha256")"
test "$candidate_sha256" = "sha256:$(sha256sum runtime-catalog.candidate.json | cut -d ' ' -f 1)"
jq -n --arg candidateSha256 "$candidate_sha256" \
--arg statementSetDigest "$statement_set_digest" --arg signer "$signer" \
--arg sourceCommit "$GITHUB_SHA" --arg qualificationRun "$CSO_QUALIFICATION_RUN" \
'{schemaVersion:1,candidateSha256:$candidateSha256,verifiedStatementSetDigest:$statementSetDigest,signer:$signer,sourceCommit:$sourceCommit,qualificationRun:$qualificationRun}' \
> candidate-attestation-evidence.json
- name: Commit the exact verified candidate and open its review PR
env:
GH_TOKEN: ${{ github.token }}
CSO_QUALIFICATION_RUN: ${{ inputs.qualification_run_id }}
run: |
set -euo pipefail
expected_candidate_sha256="$(jq -er '.candidateSha256 | select(test("^sha256:[a-f0-9]{64}$"))' candidate-attestation-evidence.json)"
test "$expected_candidate_sha256" = "sha256:$(sha256sum runtime-catalog.candidate.json | cut -d ' ' -f 1)"
bun run scripts/cso-runtime-promotion.ts validate-transition \
lib/cso/runtime-catalog.json runtime-catalog.candidate.json
cp runtime-catalog.candidate.json lib/cso/runtime-catalog.json
cmp runtime-catalog.candidate.json lib/cso/runtime-catalog.json
bun -e '
import candidate from "./lib/cso/runtime-catalog.json";
import {validateRuntimeCatalog} from "./lib/cso/runtime-catalog";
validateRuntimeCatalog(candidate);
'
bun run build:cso
branch="cso-runtime-catalog-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT"
git switch -c "$branch"
git config user.name github-actions[bot]
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
git add lib/cso/runtime-catalog.json
git commit -m "Promote qualified CSO runtime catalog"
git show HEAD:lib/cso/runtime-catalog.json > committed-runtime-catalog.json
cmp runtime-catalog.candidate.json committed-runtime-catalog.json
test "$expected_candidate_sha256" = "sha256:$(sha256sum committed-runtime-catalog.json | cut -d ' ' -f 1)"
git push --set-upstream origin "$branch"
cat > pr-body.md <<EOF
Promotes the complete CSO runtime catalog from authenticated qualification run $CSO_QUALIFICATION_RUN. The protected promotion workflow verified the exact candidate attestation and committed those same bytes after a previous-revision compare-and-swap.
Validation: runtime promotion contracts, distribution contracts, compiled CSO helper build, candidate attestation verification, and exact committed-byte comparison.
EOF
gh pr create --base main --head "$branch" --title "Promote qualified CSO runtime catalog" --body-file pr-body.md
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: cso-runtime-catalog-candidate
path: |
runtime-catalog.candidate.json
candidate-attestation-verification.json
candidate-attestation-evidence.json
committed-runtime-catalog.json
qualification-run.json
source-ancestry.json
if-no-files-found: error
retention-days: 30
@@ -0,0 +1,109 @@
name: CSO Runtime Qualification
# A private evaluator sends only gate results and public runtime identities.
# Its held-out assertions remain outside this repository. GitHub authenticates
# the dispatch actor; the protected environment supplies the human review gate.
on:
repository_dispatch:
types: [cso-runtime-qualified]
permissions:
contents: read
packages: read
attestations: read
jobs:
qualify:
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
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
with:
bun-version: 1.4.0
- name: Require the configured private-evaluator actor and bounded matrix
env:
CSO_QUALIFICATION_ACTOR: ${{ vars.CSO_QUALIFICATION_ACTOR }}
run: |
set -euo pipefail
test -n "$CSO_QUALIFICATION_ACTOR"
test "$GITHUB_ACTOR" = "$CSO_QUALIFICATION_ACTOR"
jq -e '
.action == "cso-runtime-qualified" and
(.client_payload | keys == ["statements"]) and
(.client_payload.statements | type == "array" and length == 10)
' "$GITHUB_EVENT_PATH"
- 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: 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@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: cso-qualified-runtime-statements
path: |
qualification-evidence
public-image-evidence
if-no-files-found: error
retention-days: 30
+373
View File
@@ -0,0 +1,373 @@
name: Qualify CSO Scanner Images
# Branch dispatches may validate reviewed inputs. Only protected main and the
# cso-scanner-release environment may publish or qualify scanner images. An
# explicit promotion input may then propose a catalog.
on:
workflow_dispatch:
inputs:
promote_catalog:
description: Open a reviewed scanner catalog promotion PR after every gate passes
required: false
default: false
type: boolean
permissions:
contents: read
concurrency:
group: cso-scanner-images-${{ github.ref }}
cancel-in-progress: false
jobs:
reviewed-inputs:
runs-on: ubuntu-24.04
outputs:
matrix: ${{ steps.inputs.outputs.matrix }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.0
- name: Prove scanner normalization and fail-closed behavior
run: |
set -eu
bun install --frozen-lockfile --ignore-scripts
bun test --max-concurrency 1 test/cso-scanners.test.ts test/cso-scanner-executor.test.ts test/cso-scanner-release.test.ts
- name: Validate complete reviewed scanner inputs
id: inputs
run: |
set -eu
matrix="$(bun run scripts/cso-scanner-matrix.ts)"
printf 'matrix=%s\n' "$matrix" >> "$GITHUB_OUTPUT"
stage-and-qualify:
needs: reviewed-inputs
if: github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch'
environment: cso-scanner-release
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.reviewed-inputs.outputs.matrix) }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 45
permissions:
contents: read
packages: write
id-token: write
attestations: write
artifact-metadata: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.0
- name: Require native Docker and the declared architecture
env:
CSO_ARCH: ${{ matrix.arch }}
run: |
set -eu
case "$CSO_ARCH:$(uname -m)" in
amd64:x86_64|arm64:aarch64) ;;
*) echo 'Native architecture mismatch; emulation cannot qualify a scanner.' >&2; exit 1 ;;
esac
docker --host unix:///var/run/docker.sock info
- uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4
- uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Verify reviewed upstream and SBOM-generator attestations
env:
GH_TOKEN: ${{ github.token }}
CSO_BASE_ATTESTATION: ${{ toJSON(matrix.baseAttestation) }}
CSO_SBOM_GENERATOR: ${{ toJSON(matrix.sbomGenerator) }}
run: |
set -eu
verify_reviewed_image() {
declaration="$1"
label="$2"
printf '%s\n' "$declaration" > "$label-declaration.json"
image="$(jq -r '.image' "$label-declaration.json")"
repository="$(jq -r '.repository' "$label-declaration.json")"
repository="${repository#https://github.com/}"
source_commit="$(jq -r '.sourceCommit' "$label-declaration.json")"
signer_workflow="$(jq -r '.signerWorkflow' "$label-declaration.json")"
signer_digest="$(jq -r '.signerDigest' "$label-declaration.json")"
subject_sha256="${image##*@sha256:}"
gh attestation verify "oci://$image" --repo "$repository" --signer-workflow "$signer_workflow" --signer-digest "$signer_digest" --source-digest "$source_commit" --deny-self-hosted-runners --predicate-type https://slsa.dev/provenance/v1 --format json > "$label-provenance-verification.json"
actual_provenance="$(bun run scripts/cso-attestation-evidence.ts digest "$label-provenance-verification.json" https://slsa.dev/provenance/v1 "$subject_sha256")"
test "$actual_provenance" = "$(jq -r '.provenanceStatementDigest' "$label-declaration.json")"
gh attestation verify "oci://$image" --repo "$repository" --signer-workflow "$signer_workflow" --signer-digest "$signer_digest" --source-digest "$source_commit" --deny-self-hosted-runners --predicate-type https://spdx.dev/Document/v2.3 --format json > "$label-sbom-verification.json"
actual_sbom="$(bun run scripts/cso-attestation-evidence.ts digest "$label-sbom-verification.json" https://spdx.dev/Document/v2.3 "$subject_sha256")"
test "$actual_sbom" = "$(jq -r '.sbomStatementDigest' "$label-declaration.json")"
}
verify_reviewed_image "$CSO_BASE_ATTESTATION" upstream-base
verify_reviewed_image "$CSO_SBOM_GENERATOR" sbom-generator
- name: Compile the trusted loopback assertion verifier
run: |
bun build --compile \
--no-compile-autoload-dotenv \
--no-compile-autoload-bunfig \
--no-compile-autoload-tsconfig \
--no-compile-autoload-package-json \
lib/cso/verifier.ts --outfile lib/cso/images/gstack-cso-verifier
- name: Choose the immutable scanner repository
id: image
env:
CSO_SCANNER: ${{ matrix.scanner }}
CSO_ARCH: ${{ matrix.arch }}
run: |
set -eu
name="ghcr.io/${GITHUB_REPOSITORY,,}/cso-scanners/$CSO_SCANNER-$CSO_ARCH"
printf 'name=%s\n' "$name" >> "$GITHUB_OUTPUT"
printf 'tag=%s:%s-%s\n' "$name" "$GITHUB_SHA" "$GITHUB_RUN_ID" >> "$GITHUB_OUTPUT"
- name: Build native scanner wrapper with SBOM and provenance
id: build
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
with:
context: lib/cso
file: lib/cso/scanner-images/scanner.Dockerfile
platforms: ${{ matrix.platform }}
build-args: |
BASE_IMAGE=${{ matrix.baseImage }}
SCANNER_EXECUTABLE=${{ matrix.scannerExecutable }}
tags: ${{ steps.image.outputs.tag }}
push: true
provenance: mode=max
attests: type=sbom,generator=${{ matrix.sbomGenerator.image }}
- name: Extract and validate the wrapper SBOM
env:
CSO_IMAGE: ${{ steps.image.outputs.name }}@${{ steps.build.outputs.digest }}
run: |
set -eu
docker buildx imagetools inspect "$CSO_IMAGE" --format '{{ json .SBOM.SPDX }}' > sbom.spdx.json
jq -e '.spdxVersion == "SPDX-2.3" and (.packages | type == "array")' sbom.spdx.json
- name: Sign build provenance
uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.2.0
with:
subject-name: ${{ steps.image.outputs.name }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
- name: Sign the image SBOM
uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.2.0
with:
subject-name: ${{ steps.image.outputs.name }}
subject-digest: ${{ steps.build.outputs.digest }}
sbom-path: sbom.spdx.json
push-to-registry: true
- name: Verify branch-bound provenance and SBOM identities
env:
GH_TOKEN: ${{ github.token }}
CSO_IMAGE: ${{ steps.image.outputs.name }}@${{ steps.build.outputs.digest }}
run: |
set -eu
signer="https://github.com/$GITHUB_REPOSITORY/.github/workflows/cso-scanner-images.yml@$GITHUB_REF"
gh attestation verify "oci://$CSO_IMAGE" --repo "$GITHUB_REPOSITORY" --cert-identity "$signer" --source-ref "$GITHUB_REF" --source-digest "$GITHUB_SHA" --deny-self-hosted-runners --predicate-type https://slsa.dev/provenance/v1 --format json > provenance-verification.json
gh attestation verify "oci://$CSO_IMAGE" --repo "$GITHUB_REPOSITORY" --cert-identity "$signer" --source-ref "$GITHUB_REF" --source-digest "$GITHUB_SHA" --deny-self-hosted-runners --predicate-type https://spdx.dev/Document/v2.3 --format json > sbom-verification.json
- name: Require a public package and anonymously load the verified immutable image
env:
GH_TOKEN: ${{ github.token }}
CSO_IMAGE: ${{ steps.image.outputs.name }}@${{ steps.build.outputs.digest }}
CSO_PLATFORM: ${{ matrix.platform }}
run: |
set -euo pipefail
bun run scripts/cso-public-ghcr.ts verify \
--image "$CSO_IMAGE" --platform "$CSO_PLATFORM" \
--repository "$GITHUB_REPOSITORY" --output public-image.json
- name: Recompute immutable rule or database content
env:
CSO_IMAGE: ${{ steps.image.outputs.name }}@${{ steps.build.outputs.digest }}
CSO_ASSETS: ${{ toJSON(matrix.assets) }}
run: |
set -eu
printf '%s\n' "$CSO_ASSETS" > declared-assets.json
asset_path="$(jq -r '.semgrepRules.path // .advisoryDatabase.path // empty' declared-assets.json)"
expected="$(jq -r '.semgrepRules.sha256 // .advisoryDatabase.contentSha256 // empty' declared-assets.json)"
if test -n "$asset_path"; then
case "$asset_path" in /policy/catalog/*|/opt/cso/scanner-data/*) ;; *) exit 1 ;; esac
container="$(docker --host unix:///var/run/docker.sock create --pull=never "$CSO_IMAGE" /bin/true)"
trap 'docker --host unix:///var/run/docker.sock rm --force "$container" >/dev/null 2>&1 || true' EXIT
docker --host unix:///var/run/docker.sock cp "$container:$asset_path" scanner-asset
actual="$(bun run scripts/cso-scanner-catalog.ts hash-asset scanner-asset)"
test "$actual" = "$expected"
docker --host unix:///var/run/docker.sock rm --force "$container" >/dev/null
trap - EXIT
else
test "$CSO_ASSETS" = null
fi
- name: Compile the helper and bind the staged profile
env:
CSO_IMAGE: ${{ steps.image.outputs.name }}@${{ steps.build.outputs.digest }}
CSO_SCANNER: ${{ matrix.scanner }}
CSO_PLATFORM: ${{ matrix.platform }}
CSO_VERSION: ${{ matrix.version }}
CSO_CAPABILITIES: ${{ toJSON(matrix.capabilities) }}
CSO_ASSETS: ${{ toJSON(matrix.assets) }}
CSO_APPLICATION_EXECUTABLE: ${{ matrix.applicationExecutable }}
run: |
set -eu
bun install --frozen-lockfile --ignore-scripts
bun run build:cso
policy_hash="$(bun -e "import {ISOLATION_POLICY_HASH} from './lib/cso/docker'; process.stdout.write(ISOLATION_POLICY_HASH)")"
jq -n --arg scanner "$CSO_SCANNER" --arg image "$CSO_IMAGE" --arg platform "$CSO_PLATFORM" --arg version "$CSO_VERSION" --arg policy "$policy_hash" --arg app "$CSO_APPLICATION_EXECUTABLE" --argjson capabilities "$CSO_CAPABILITIES" --argjson assets "$CSO_ASSETS" '{scanner:$scanner,image:$image,platform:$platform,version:$version,isolationPolicyHash:$policy,capabilities:$capabilities,assets:$assets,applicationExecutable:$app}' > staged-profile.json
- name: Exercise common containment and the real scanner adapter
env:
GSTACK_CSO_DOCKER_TESTS: "1"
GSTACK_CSO_SCANNER_DOCKER_TESTS: "1"
GSTACK_CSO_SCANNER_PROFILE: ${{ github.workspace }}/staged-profile.json
GSTACK_CSO_SCANNER_VERSION_HASH: ${{ github.workspace }}/version.sha256
DOCKER_HOST: unix:///var/run/docker.sock
run: |
set -eu
bun test --max-concurrency 1 test/cso-docker-integration.test.ts
bun test --max-concurrency 1 test/cso-scanner-docker-integration.test.ts
- name: Emit one immutable qualified profile
env:
CSO_IMAGE: ${{ steps.image.outputs.name }}@${{ steps.build.outputs.digest }}
CSO_SCANNER: ${{ matrix.scanner }}
CSO_ARCH: ${{ matrix.arch }}
CSO_PLATFORM: ${{ matrix.platform }}
CSO_VERSION: ${{ matrix.version }}
CSO_CAPABILITIES: ${{ toJSON(matrix.capabilities) }}
CSO_ASSETS: ${{ toJSON(matrix.assets) }}
CSO_UPSTREAM: ${{ toJSON(matrix.baseAttestation) }}
CSO_SBOM_GENERATOR: ${{ toJSON(matrix.sbomGenerator) }}
run: |
set -eu
policy_hash="$(jq -r '.isolationPolicyHash' staged-profile.json)"
version_hash="$(cat version.sha256)"
sbom_digest="sha256:$(sha256sum sbom.spdx.json | cut -d' ' -f1)"
provenance_digest="sha256:$(sha256sum provenance-verification.json | cut -d' ' -f1)"
qualified_at="$(date -u +%Y-%m-%dT%H:%M:%S.000Z)"
workflow="https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
version_id="$(printf '%s' "$CSO_VERSION" | tr '[:upper:]+' '[:lower:]-' | tr -cd 'a-z0-9._-')"
profile_id="$CSO_SCANNER-$version_id-$CSO_ARCH"
output="$CSO_SCANNER-$CSO_ARCH.json"
jq -n --arg id "$profile_id" --arg scanner "$CSO_SCANNER" --arg platform "$CSO_PLATFORM" --arg image "$CSO_IMAGE" --arg version "$CSO_VERSION" --arg versionHash "$version_hash" --arg policy "$policy_hash" --arg qualifiedAt "$qualified_at" --arg sourceCommit "$GITHUB_SHA" --arg workflow "$workflow" --arg sbomDigest "$sbom_digest" --arg provenanceDigest "$provenance_digest" --argjson capabilities "$CSO_CAPABILITIES" --argjson assets "$CSO_ASSETS" '{id:$id,scanner:$scanner,state:"qualified",platform:$platform,image:$image,entrypoint:"/opt/cso/entrypoint",executable:"/opt/cso/bin/scanner",version:$version,versionOutputSha256:$versionHash,helperAbi:3,isolationPolicyHash:$policy,capabilities:$capabilities} + (if $assets == null then {} else {assets:$assets} end) + {qualifiedAt:$qualifiedAt,qualification:{sourceCommit:$sourceCommit,workflow:$workflow,sbomDigest:$sbomDigest,provenanceDigest:$provenanceDigest,verifiedProvenance:true,containmentPassed:true,adapterContractPassed:true,offlineAssetsPassed:true}}' > "$output"
printf '%s\n' "$CSO_UPSTREAM" > upstream-evidence.json
printf '%s\n' "$CSO_SBOM_GENERATOR" > sbom-generator-evidence.json
sha256sum "$output" staged-profile.json version.sha256 declared-assets.json public-image.json sbom.spdx.json provenance-verification.json sbom-verification.json upstream-evidence.json sbom-generator-evidence.json upstream-base-*-verification.json sbom-generator-*-verification.json > evidence.sha256
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: cso-scanner-profile-${{ matrix.scanner }}-${{ matrix.arch }}
path: |
${{ matrix.scanner }}-${{ matrix.arch }}.json
staged-profile.json
version.sha256
declared-assets.json
public-image.json
sbom.spdx.json
provenance-verification.json
sbom-verification.json
upstream-evidence.json
sbom-generator-evidence.json
upstream-base-declaration.json
upstream-base-provenance-verification.json
upstream-base-sbom-verification.json
sbom-generator-declaration.json
sbom-generator-provenance-verification.json
sbom-generator-sbom-verification.json
evidence.sha256
if-no-files-found: error
retention-days: 30
catalog-proposal:
needs: [reviewed-inputs, stage-and-qualify]
runs-on: ubuntu-24.04
permissions:
contents: read
actions: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.0
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
pattern: cso-scanner-profile-*
path: qualified-artifacts
- name: Assemble and validate the complete catalog proposal
run: |
set -eu
mkdir qualified-fragments
find qualified-artifacts -mindepth 2 -maxdepth 2 -type f \( -name '*-amd64.json' -o -name '*-arm64.json' \) | sort | while IFS= read -r file; do cp "$file" qualified-fragments/; done
test "$(find qualified-fragments -type f -name '*.json' | wc -l | tr -d ' ')" -eq 12
revision="cso-scanners-$(printf '%s' "$GITHUB_SHA" | cut -c1-12)-$GITHUB_RUN_ID"
workflow="https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
image_prefix="ghcr.io/${GITHUB_REPOSITORY,,}/cso-scanners/"
bun run scripts/cso-scanner-catalog.ts assemble qualified-fragments lib/cso/scanner-images/catalog.json catalog-proposal.json --revision "$revision" --source-commit "$GITHUB_SHA" --workflow "$workflow" --image-prefix "$image_prefix"
bun run scripts/cso-scanner-catalog.ts validate catalog-proposal.json
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: cso-scanner-catalog-proposal
path: catalog-proposal.json
if-no-files-found: error
retention-days: 30
promote-catalog:
needs: catalog-proposal
if: github.ref == 'refs/heads/main' && inputs.promote_catalog == true
environment: cso-scanner-release
runs-on: ubuntu-24.04
permissions:
contents: write
pull-requests: write
actions: read
packages: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: main
persist-credentials: true
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.0
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: cso-scanner-catalog-proposal
path: promotion
- name: Recheck public visibility and anonymous pulls before promotion
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
mkdir public-image-evidence
jq -c '.scanners[]' promotion/catalog-proposal.json | while IFS= read -r profile; do
profile_id="$(printf '%s' "$profile" | jq -er '.id | select(test("^[a-z0-9][a-z0-9._-]{0,100}$"))')"
image="$(printf '%s' "$profile" | jq -er '.image')"
platform="$(printf '%s' "$profile" | 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/$profile_id.json" --remove-after
done
- name: Revalidate and open the reviewable source catalog PR
env:
GH_TOKEN: ${{ github.token }}
run: |
set -eu
bun run scripts/cso-scanner-catalog.ts validate promotion/catalog-proposal.json
bun run scripts/cso-scanner-catalog.ts validate-transition lib/cso/scanner-images/catalog.json promotion/catalog-proposal.json
cp promotion/catalog-proposal.json lib/cso/scanner-images/catalog.json
bun install --frozen-lockfile --ignore-scripts
bun test test/cso-scanners.test.ts test/cso-scanner-executor.test.ts test/cso-scanner-release.test.ts
bun run build:cso
branch="cso-scanner-catalog-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT"
git switch -c "$branch"
git config user.name github-actions[bot]
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
git add lib/cso/scanner-images/catalog.json
git commit -m "Promote qualified CSO scanner catalog"
git push --set-upstream origin "$branch"
cat > pr-body.md <<'EOF'
Promotes the complete scanner catalog produced by native amd64/arm64 qualification for all six CSO scanners. Each profile binds its immutable image, exact version-output hash, offline asset hash when required, containment and adapter gates, SBOM, provenance, source commit, and workflow run.
Validation: scanner catalog contract tests and compiled CSO helper build.
EOF
gh pr create --base main --head "$branch" --title "Promote qualified CSO scanner catalog" --body-file pr-body.md
+92 -2
View File
@@ -44,7 +44,72 @@ permissions:
contents: read
jobs:
free-tests:
cso-macos-launcher:
runs-on: macos-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.0
- name: Install helper dependencies
run: bun install --frozen-lockfile --ignore-scripts
- name: Build and sign the native macOS startup boundary
run: bun run build:cso
- name: Exercise hardened-runtime startup and environment scrubbing
run: bun run test:cso:macos
env:
GSTACK_CSO_MACOS_TESTS: "1"
cso-windows-launcher:
runs-on: windows-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.0
- name: Install helper dependencies
run: bun install --frozen-lockfile --ignore-scripts
- name: Build the native Windows startup boundary from Git Bash
shell: bash
run: bun run build:cso
- name: Exercise native startup, environment, and argument forwarding
run: bun run test:cso:windows
env:
GSTACK_CSO_WINDOWS_TESTS: "1"
cso-docker-integration:
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.0
- name: Require local Docker containment prerequisites
# A missing daemon is a failed gate, never a skipped integration test.
run: |
command -v docker
docker --host unix:///var/run/docker.sock info
command -v cc
- name: Install helper dependencies
run: bun install --frozen-lockfile --ignore-scripts
- name: Compile trusted CSO helper and watchdog
run: bun run build:cso
- name: Run CSO Docker integration gate
run: bun run test:cso:docker
env:
GSTACK_CSO_DOCKER_TESTS: "1"
DOCKER_HOST: unix:///var/run/docker.sock
free-suite:
runs-on: ubicloud-standard-8
timeout-minutes: 20
steps:
@@ -52,7 +117,7 @@ jobs:
with:
persist-credentials: false
- uses: oven-sh/setup-bun@v2
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.0
@@ -122,6 +187,9 @@ jobs:
- name: Build gate binaries (make-pdf e2e gates)
run: bun run build:gates
- name: Build trusted CSO helper used by free contract tests
run: bun run build:cso
# GSTACK_EXPECT_BINARIES=1 arms make-pdf/test/e2e/ci-prereqs.test.ts:
# if a future edit drops the gate build (or poppler), the lane FAILS
# instead of the gates silently self-skipping back to false green.
@@ -163,3 +231,25 @@ jobs:
name: free-test-shard-logs
path: /tmp/gstack-free-test-*.log
if-no-files-found: ignore
# Branch protection already requires the `free-tests` context. Keep that
# stable name as an always-running aggregate so every platform-specific CSO
# gate is merge-blocking without a separate branch-protection migration.
free-tests:
if: always()
needs: [free-suite, cso-macos-launcher, cso-windows-launcher, cso-docker-integration]
runs-on: ubuntu-24.04
timeout-minutes: 2
steps:
- name: Require the free suite and every CSO platform gate
env:
FREE_SUITE_RESULT: ${{ needs.free-suite.result }}
CSO_MACOS_RESULT: ${{ needs.cso-macos-launcher.result }}
CSO_WINDOWS_RESULT: ${{ needs.cso-windows-launcher.result }}
CSO_DOCKER_RESULT: ${{ needs.cso-docker-integration.result }}
run: |
set -eu
test "$FREE_SUITE_RESULT" = success
test "$CSO_MACOS_RESULT" = success
test "$CSO_WINDOWS_RESULT" = success
test "$CSO_DOCKER_RESULT" = success