From ec06439892f8c5aa570996b1789f0b54c0fd53df Mon Sep 17 00:00:00 2001 From: rldyourmnd Date: Sun, 13 Sep 2026 05:35:08 +0500 Subject: [PATCH 1/2] fix: validate release evidence before expensive build gates Signed-off-by: rldyourmnd --- .gds/bundle.lock.yaml | 14 +-- .gds/compiled-policy.json | 4 +- .github/workflows/gds-ci.yml | 6 +- .github/workflows/release-bundle.yml | 45 +++++--- core/cli/root.go | 2 +- core/cmd/gds-controller/main.go | 2 +- core/compiler/types.go | 2 +- docs/contracts/bundle-release-v1.md | 6 ++ .../control-plane/.claude/CLAUDE.md | 4 +- .../control-plane/.gds/bundle.lock.yaml | 16 +-- .../control-plane/.gds/compiled-policy.json | 4 +- .../.github/workflows/gds-ci.yml | 4 +- .../projections/control-plane/AGENTS.md | 8 +- tests/test_release_evidence_input.py | 100 ++++++++++++++++++ 14 files changed, 168 insertions(+), 49 deletions(-) create mode 100644 tests/test_release_evidence_input.py diff --git a/.gds/bundle.lock.yaml b/.gds/bundle.lock.yaml index 9f7ea33..5a7577d 100644 --- a/.gds/bundle.lock.yaml +++ b/.gds/bundle.lock.yaml @@ -2,17 +2,17 @@ schema_version: 1 bundle: - version: "0.9.3-dev" + version: "0.9.4-dev" release_sequence: 0 channel: "development" - source_tree_digest: "sha256:dbbd338cb723846e6f140f16fd2e76594fea9377bf792c03fbae60a54e88ee01" - digest: "sha256:d8b7f50891831664b429e19e5df040fcc7e1fd6d1b544cf8c8ebf3147cc2afef" + source_tree_digest: "sha256:85d792bf33db30ad34ecd6ad0bf7bf0bdf84c41778432e87b45a89e200dbe18b" + digest: "sha256:7d089792b096f905b6b9568f62fef25b568f5e37b1ed8a17543380bf8e9d28e3" projection: - input_digest: "sha256:a56667c2156bdae3807b57a647c5f92c07223bf37b0edefbb4a50eb0038bd3e1" - output_digest: "sha256:5a94f145b67e94ed66d769850bed7a5e2a9ea29439d5aebcd400fab18eb49ec1" + input_digest: "sha256:7eac6db12d8a42a5003f12d2e22b63e17f20aff3b49764e000b94a9a782702f2" + output_digest: "sha256:2174c5325ae3eb87af133acd34eab9b63dedb780c795e562a134fd4608c55d68" files: - path: ".gds/compiled-policy.json" - digest: "sha256:26f4268e1b6df625045487c49b80342e94c5c6a81f2d8dc966f1a3a05021ac37" + digest: "sha256:37ece9020fd9eeed4ee90582600c5e50cb3a7e32ee773719d1ccfad2d9cd1b6d" - path: ".github/workflows/gds-ci.yml" - digest: "sha256:e12974ce96d1fccf477eaab11d2def7a22240df5d0a4e5bfb78c5c2b70e8fc7d" + digest: "sha256:1269d657b675dc22f6e72ddd885a11d14519d8039d97a8d86627f296333b2390" diff --git a/.gds/compiled-policy.json b/.gds/compiled-policy.json index 0509719..7bcb40b 100644 --- a/.gds/compiled-policy.json +++ b/.gds/compiled-policy.json @@ -2,8 +2,8 @@ "schema_version": 1, "compiled_policy": { "repository_id": "repo_01M0EZ7TB3KNXNSP78Z8M64WXG", - "bundle_version": "0.9.3-dev", - "digest": "sha256:497a28d43aad2a561c0144598da07c453bd1d48de59f02a5321c88f03f27e291" + "bundle_version": "0.9.4-dev", + "digest": "sha256:1b0f8c63bd653f2ff4f2478acd77ed18785a1591d801d51171df93aa7a280092" }, "sources": [ { diff --git a/.github/workflows/gds-ci.yml b/.github/workflows/gds-ci.yml index 174d1ef..a73d99c 100644 --- a/.github/workflows/gds-ci.yml +++ b/.github/workflows/gds-ci.yml @@ -1,8 +1,8 @@ # GENERATED FILE - DO NOT EDIT DIRECTLY # generator: gds -# bundle: 0.9.3-dev -# source-tree-digest: sha256:dbbd338cb723846e6f140f16fd2e76594fea9377bf792c03fbae60a54e88ee01 -# input-digest: sha256:a56667c2156bdae3807b57a647c5f92c07223bf37b0edefbb4a50eb0038bd3e1 +# bundle: 0.9.4-dev +# source-tree-digest: sha256:85d792bf33db30ad34ecd6ad0bf7bf0bdf84c41778432e87b45a89e200dbe18b +# input-digest: sha256:7eac6db12d8a42a5003f12d2e22b63e17f20aff3b49764e000b94a9a782702f2 # output-digest: sha256:15b87303cd0692fd8e5d88281771dded9fdc2fc027d600681155cb4e71f42386 # edit-source: # - .gds/repository.yaml diff --git a/.github/workflows/release-bundle.yml b/.github/workflows/release-bundle.yml index 64c203b..3b6f071 100644 --- a/.github/workflows/release-bundle.yml +++ b/.github/workflows/release-bundle.yml @@ -77,16 +77,6 @@ jobs: go-version: "1.27.1" cache: true - - name: Run release gates - shell: bash - env: - GDS_TEST_PYTHON: ${{ runner.temp }}/gds-release-python/bin/python - run: | - python3 -m venv "${GDS_TEST_PYTHON%/bin/python}" - export PATH="${GDS_TEST_PYTHON%/python}:$PATH" - "$GDS_TEST_PYTHON" -m pip install --quiet --require-hashes -r requirements/test.txt - scripts/validate_release.sh - - name: Materialize bounded signed harness evidence input id: harness-evidence shell: bash @@ -99,19 +89,42 @@ jobs: echo "arguments=" >> "$GITHUB_OUTPUT" exit 0 fi - [ -n "$HARNESS_EVIDENCE_BUNDLE_BASE64" ] - [ -n "$HARNESS_EVIDENCE_TRUST_POLICY_BASE64" ] - [ -n "$HARNESS_EVIDENCE_TRUST_POLICY_DIGEST" ] + if [ -z "$HARNESS_EVIDENCE_BUNDLE_BASE64" ] || [ -z "$HARNESS_EVIDENCE_TRUST_POLICY_BASE64" ]; then + echo "Signed harness evidence archive and trust policy must be supplied together" >&2 + exit 1 + fi + expected_digest="${HARNESS_EVIDENCE_TRUST_POLICY_DIGEST#sha256:}" + [[ "$expected_digest" =~ ^[0-9a-f]{64}$ ]] || { + echo "HARNESS_EVIDENCE_TRUST_POLICY_DIGEST must be an independent SHA-256 pin (64 lowercase hex digits, optionally prefixed with sha256:)" >&2 + exit 1 + } install -d -m 0700 "$EVIDENCE_INPUT_ROOT" printf '%s' "$HARNESS_EVIDENCE_BUNDLE_BASE64" | base64 --decode > "$EVIDENCE_INPUT_ROOT/evidence.tar.gz" printf '%s' "$HARNESS_EVIDENCE_TRUST_POLICY_BASE64" | base64 --decode > "$EVIDENCE_INPUT_ROOT/trust.json" - test "sha256:$(sha256sum "$EVIDENCE_INPUT_ROOT/trust.json" | cut -d' ' -f1)" = "$HARNESS_EVIDENCE_TRUST_POLICY_DIGEST" - go run ./core/cmd/gds-release-builder \ + actual_digest="$(sha256sum "$EVIDENCE_INPUT_ROOT/trust.json" | cut -d' ' -f1)" + if [ "$actual_digest" != "$expected_digest" ]; then + echo "Harness evidence trust policy does not match the independent repository pin" >&2 + exit 1 + fi + if ! go run ./core/cmd/gds-release-builder \ --extract-harness-evidence-archive "$EVIDENCE_INPUT_ROOT/evidence.tar.gz" \ --extract-harness-evidence-destination "$EVIDENCE_INPUT_ROOT/records" \ - > "$RUNNER_TEMP/gds-harness-evidence-materialization.json" + > "$RUNNER_TEMP/gds-harness-evidence-materialization.json"; then + cat "$RUNNER_TEMP/gds-harness-evidence-materialization.json" >&2 + exit 1 + fi echo "arguments=--harness-evidence-directory $EVIDENCE_INPUT_ROOT/records --harness-evidence-trust-policy $EVIDENCE_INPUT_ROOT/trust.json" >> "$GITHUB_OUTPUT" + - name: Run release gates + shell: bash + env: + GDS_TEST_PYTHON: ${{ runner.temp }}/gds-release-python/bin/python + run: | + python3 -m venv "${GDS_TEST_PYTHON%/bin/python}" + export PATH="${GDS_TEST_PYTHON%/python}:$PATH" + "$GDS_TEST_PYTHON" -m pip install --quiet --require-hashes -r requirements/test.txt + scripts/validate_release.sh + - name: Build and independently verify release unit env: RELEASE_OUTPUT_ROOT: ${{ runner.temp }}/gds-release-output diff --git a/core/cli/root.go b/core/cli/root.go index 5acdf09..a3d43c9 100644 --- a/core/cli/root.go +++ b/core/cli/root.go @@ -22,7 +22,7 @@ import ( // Version is the development default; a release build overrides it with the // exact tag via -X. It carries the -dev suffix so an unstamped binary can // never claim to be a released one. -var Version = "0.9.3-dev" +var Version = "0.9.4-dev" type options struct { json bool diff --git a/core/cmd/gds-controller/main.go b/core/cmd/gds-controller/main.go index f6413f5..2add2b9 100644 --- a/core/cmd/gds-controller/main.go +++ b/core/cmd/gds-controller/main.go @@ -25,7 +25,7 @@ import ( // version is the development default; the release builder stamps the exact // tag via -X main.version. The -dev suffix keeps an unstamped binary honest. -var version = "0.9.3-dev" +var version = "0.9.4-dev" func main() { ctx, stop := signal.NotifyContext( diff --git a/core/compiler/types.go b/core/compiler/types.go index 5884731..cd01759 100644 --- a/core/compiler/types.go +++ b/core/compiler/types.go @@ -12,7 +12,7 @@ import ( // policy-owner checkout. It tracks the current release line with a -dev // suffix so a development bundle is dated honestly; the development channel // field, not this string, is what classifies the bundle. -const DevelopmentBundleVersion = "0.9.3-dev" +const DevelopmentBundleVersion = "0.9.4-dev" type PolicySource struct { SchemaVersion int `json:"schema_version"` diff --git a/docs/contracts/bundle-release-v1.md b/docs/contracts/bundle-release-v1.md index a3e428a..0cfee9b 100644 --- a/docs/contracts/bundle-release-v1.md +++ b/docs/contracts/bundle-release-v1.md @@ -118,6 +118,12 @@ The independent local `bundle-trust.yaml` binds: - the exact GitHub CLI version and extracted executable digest for every supported release target (`darwin`/`linux` × `amd64`/`arm64`). +The signed harness-evidence input is decoded and checked before the expensive +release gates. Its independent repository pin accepts 64 lowercase SHA-256 hex +digits with an optional `sha256:` prefix; missing, malformed, or mismatched pins +fail before building. A supplied policy never authorizes its own digest. +The release builder separately verifies the signed records and their freshness. + The trusted-root digest is obtained and reviewed out of band. A trusted root delivered beside an attestation is not self-authenticating. Producer CI and the consumer both compare it with the independent local pin before using it. diff --git a/tests/golden/projections/control-plane/.claude/CLAUDE.md b/tests/golden/projections/control-plane/.claude/CLAUDE.md index 0306569..edbd470 100644 --- a/tests/golden/projections/control-plane/.claude/CLAUDE.md +++ b/tests/golden/projections/control-plane/.claude/CLAUDE.md @@ -1,9 +1,9 @@