From 293afed12a0afffdda779a8120e29a8f598daa59 Mon Sep 17 00:00:00 2001 From: Thomas Juul Dyhr Date: Wed, 27 May 2026 10:20:20 +0200 Subject: [PATCH] fix(security): codeql v4, SLSA provenance, remove ruleset admin bypass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - scorecard.yml: upgrade github/codeql-action/upload-sarif from v3 to v4 (v3 deprecated December 2026; SHA 7211b7c already used in security.yml) - release-consolidated.yml: add actions/attest-build-provenance@v4.1.0 to build-release job so every platform binary gets a SLSA provenance attestation on release; fixes Scorecard Signed-Releases score (0/10 → improving) - merge-own-pr.sh: extend to also relax/restore the "Protect main" ruleset (id 15969824) alongside the legacy branch-protection API; strict restore now sets bypass_actors: [] so admin bypass is removed after each merge, fixing Scorecard Branch-Protection "apply to administrators" warning Co-Authored-By: Claude Sonnet 4.6 --- .github/scripts/merge-own-pr.sh | 70 +++++++++++++++++++++- .github/workflows/release-consolidated.yml | 9 +++ .github/workflows/scorecard.yml | 2 +- 3 files changed, 77 insertions(+), 4 deletions(-) diff --git a/.github/scripts/merge-own-pr.sh b/.github/scripts/merge-own-pr.sh index a99fe30..c8f937d 100755 --- a/.github/scripts/merge-own-pr.sh +++ b/.github/scripts/merge-own-pr.sh @@ -1,12 +1,15 @@ #!/usr/bin/env bash # Usage: ./merge-own-pr.sh -# Merges a PR authored by the repo owner by temporarily relaxing branch -# protection (review count 0), squash-merging, then restoring (review count 1). +# Squash-merges a PR authored by the repo owner by temporarily relaxing both +# the legacy branch-protection rules and the "Protect main" ruleset, then +# restoring strict settings (no admin bypass after restore). set -euo pipefail PR="${1:?Usage: $0 }" REPO="docdyhr/batless" +RULESET_ID="15969824" +# ── Legacy branch-protection (belt-and-suspenders) ────────────────────────── STRICT_BP='{ "required_status_checks": {"strict": true, "contexts": ["CI Status"]}, "enforce_admins": true, @@ -33,15 +36,76 @@ RELAXED_BP='{ "required_linear_history": true }' +# ── Repository ruleset (no bypass_actors in strict — admins are enforced) ──── +STRICT_RULESET=$(cat <<'JSON' +{ + "name": "Protect main: require PR and CI", + "target": "branch", + "enforcement": "active", + "conditions": {"ref_name": {"exclude": [], "include": ["~DEFAULT_BRANCH"]}}, + "rules": [ + {"type": "pull_request", "parameters": { + "required_approving_review_count": 1, + "dismiss_stale_reviews_on_push": true, + "required_reviewers": [], + "require_code_owner_review": true, + "require_last_push_approval": true, + "required_review_thread_resolution": false, + "allowed_merge_methods": ["squash"] + }}, + {"type": "required_status_checks", "parameters": { + "strict_required_status_checks_policy": true, + "do_not_enforce_on_create": false, + "required_status_checks": [{"context": "CI Status"}] + }}, + {"type": "deletion"}, + {"type": "non_fast_forward"} + ], + "bypass_actors": [] +} +JSON +) + +RELAXED_RULESET=$(cat <<'JSON' +{ + "name": "Protect main: require PR and CI", + "target": "branch", + "enforcement": "active", + "conditions": {"ref_name": {"exclude": [], "include": ["~DEFAULT_BRANCH"]}}, + "rules": [ + {"type": "pull_request", "parameters": { + "required_approving_review_count": 0, + "dismiss_stale_reviews_on_push": true, + "required_reviewers": [], + "require_code_owner_review": false, + "require_last_push_approval": false, + "required_review_thread_resolution": false, + "allowed_merge_methods": ["squash"] + }}, + {"type": "required_status_checks", "parameters": { + "strict_required_status_checks_policy": true, + "do_not_enforce_on_create": false, + "required_status_checks": [{"context": "CI Status"}] + }}, + {"type": "deletion"}, + {"type": "non_fast_forward"} + ], + "bypass_actors": [] +} +JSON +) + restore() { echo "Restoring branch protection..." echo "$STRICT_BP" | gh api --method PUT "repos/$REPO/branches/main/protection" --input - > /dev/null - echo "Branch protection restored (1 required review)." + echo "$STRICT_RULESET" | gh api --method PUT "repos/$REPO/rulesets/$RULESET_ID" --input - > /dev/null + echo "Branch protection restored (1 required review, no admin bypass)." } trap restore EXIT echo "Relaxing branch protection for merge..." echo "$RELAXED_BP" | gh api --method PUT "repos/$REPO/branches/main/protection" --input - > /dev/null +echo "$RELAXED_RULESET" | gh api --method PUT "repos/$REPO/rulesets/$RULESET_ID" --input - > /dev/null echo "Merging PR #$PR..." gh api --method PUT "repos/$REPO/pulls/$PR/merge" -f merge_method=squash --jq '.message' diff --git a/.github/workflows/release-consolidated.yml b/.github/workflows/release-consolidated.yml index 8269ff7..c199b7f 100644 --- a/.github/workflows/release-consolidated.yml +++ b/.github/workflows/release-consolidated.yml @@ -81,6 +81,10 @@ jobs: name: Build (${{ matrix.target }}) runs-on: ${{ matrix.os }} needs: [validate, test] + permissions: + attestations: write + id-token: write + contents: read strategy: matrix: include: @@ -129,6 +133,11 @@ jobs: tar -czf "batless-${{ matrix.target }}.tar.gz" -C "./target/${{ matrix.target }}/release" "$binary_name" fi + - name: Attest build provenance + uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 + with: + subject-path: "batless-${{ matrix.target }}.*" + - name: Upload release artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 653c933..6c1d3eb 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -32,7 +32,7 @@ jobs: publish_results: true - name: Upload to code-scanning - uses: github/codeql-action/upload-sarif@03e4368ac7daa2bd82b3e85262f3bf87ee112f57 # v3 + uses: github/codeql-action/upload-sarif@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4 with: sarif_file: results.sarif category: scorecard