From 87838472b21c6d35c2347e48bf2cd2a71feba512 Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Fri, 22 May 2026 12:31:47 -0400 Subject: [PATCH] ci: add OSPO branch protection and environment-scoped workflows Adds repo ruleset on `main` (PR + CI `test` check required, blocks force-push and deletion, admin bypass). Wires publishing/secret-using workflows to GitHub Environments: release.yml -> release, sign-windows.yml + release-tray.yml -> signing (required reviewer), news-sync.yml -> news-sync. Documents the new approval gate in the CLAUDE.md release section. Follow-up (manual): scope SIGNPATH_* secrets to the signing environment and YOUTUBE_API_KEY to the news-sync environment. --- .github/rulesets/main-protection.json | 37 +++++++++++++++++++++++++++ .github/workflows/news-sync.yml | 1 + .github/workflows/release-tray.yml | 2 ++ .github/workflows/release.yml | 1 + .github/workflows/sign-windows.yml | 1 + CLAUDE.md | 10 +++++--- 6 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 .github/rulesets/main-protection.json diff --git a/.github/rulesets/main-protection.json b/.github/rulesets/main-protection.json new file mode 100644 index 0000000..8132af8 --- /dev/null +++ b/.github/rulesets/main-protection.json @@ -0,0 +1,37 @@ +{ + "name": "main-protection", + "target": "branch", + "enforcement": "active", + "conditions": { + "ref_name": { + "include": ["refs/heads/main"], + "exclude": [] + } + }, + "rules": [ + {"type": "deletion"}, + {"type": "non_fast_forward"}, + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 1, + "dismiss_stale_reviews_on_push": false, + "require_code_owner_review": false, + "require_last_push_approval": false, + "required_review_thread_resolution": false + } + }, + { + "type": "required_status_checks", + "parameters": { + "strict_required_status_checks_policy": false, + "required_status_checks": [ + {"context": "test"} + ] + } + } + ], + "bypass_actors": [ + {"actor_id": 5, "actor_type": "RepositoryRole", "bypass_mode": "always"} + ] +} diff --git a/.github/workflows/news-sync.yml b/.github/workflows/news-sync.yml index e1e83e2..332e986 100644 --- a/.github/workflows/news-sync.yml +++ b/.github/workflows/news-sync.yml @@ -11,6 +11,7 @@ permissions: jobs: sync-news: runs-on: ubuntu-latest + environment: news-sync steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/release-tray.yml b/.github/workflows/release-tray.yml index d432703..6c7bb81 100644 --- a/.github/workflows/release-tray.yml +++ b/.github/workflows/release-tray.yml @@ -11,6 +11,7 @@ permissions: jobs: build-tray: if: github.event.workflow_run.conclusion == 'success' + environment: signing permissions: contents: write strategy: @@ -131,6 +132,7 @@ jobs: aggregate-checksums: needs: build-tray runs-on: ubuntu-latest + environment: signing permissions: contents: write steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8ab2a4b..78f0c25 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,6 +11,7 @@ on: jobs: release: runs-on: ubuntu-latest + environment: release permissions: contents: write env: diff --git a/.github/workflows/sign-windows.yml b/.github/workflows/sign-windows.yml index 191fd4d..658dfde 100644 --- a/.github/workflows/sign-windows.yml +++ b/.github/workflows/sign-windows.yml @@ -11,6 +11,7 @@ permissions: jobs: sign: runs-on: ubuntu-latest + environment: signing permissions: contents: write outputs: diff --git a/CLAUDE.md b/CLAUDE.md index 7b9d19c..37b46c7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -225,10 +225,12 @@ gh release edit vX.Y.Z --draft=false --latest --notes "release notes here" **Pipeline sequence:** -1. `release.yml` (workflow_dispatch) → creates tag, builds CLI binaries via GoReleaser, creates draft release -2. `release-tray.yml` (workflow_run on Release success) → builds tray binaries for 3 platforms (CGO, ~3min), uploads to same draft release, aggregates `tray-checksums.txt` -3. `sign-windows.yml` (workflow_run on Tray success) → Authenticode-signs Windows `.exe` binaries via SignPath.io (best-effort) -4. Manual: `gh release edit --draft=false` publishes the release +1. `release.yml` (workflow_dispatch, env: `release`) → creates tag, builds CLI binaries via GoReleaser, creates draft release +2. `release-tray.yml` (workflow_run on Release success, env: `signing`) → builds tray binaries for 3 platforms (CGO, ~3min), uploads to same draft release, aggregates `tray-checksums.txt` +3. `sign-windows.yml` (workflow_run on Tray success, env: `signing`) → **pauses for required-reviewer approval** (OSPO gate), then Authenticode-signs Windows `.exe` binaries via SignPath.io and publishes the release (best-effort) +4. Manual: `gh release edit --draft=false` only needed if signing is skipped — otherwise the signing job publishes automatically after approval + +**OSPO compliance:** `main` is protected by ruleset (`main-protection`: requires PR + CI `test` check, blocks force-push and deletion, admins can bypass). Workflows that touch secrets or publish artifacts run in named environments: `release`, `signing` (required reviewer = repo admin), `news-sync`. SignPath secrets and `YOUTUBE_API_KEY` should be scoped to their respective environments rather than the org/repo level. Ruleset spec: [.github/rulesets/main-protection.json](.github/rulesets/main-protection.json). **Artifacts per release:** CLI binaries (linux/amd64, linux/arm64, darwin/amd64, darwin/arm64, windows/amd64) + tray binaries (linux/amd64, darwin/arm64, windows/amd64) + checksums + tray-checksums + Scoop manifest + Homebrew cask.