mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
fix(release): require exact-head approval for desktop tags (#3973)
## Summary - require an exact-head trusted approval before desktop auto-tagging - remove rule-suite authorization that `GITHUB_TOKEN` cannot access - pin review pagination to `page=1` and test the deployed `gh` control flow ## Why The previous verifier unconditionally queried repository rule-suite endpoints with `github.token`. Those endpoints require Administration: read, which Actions `GITHUB_TOKEN` cannot receive. Its paginated list request also duplicated page one when no explicit page was supplied. This deliberately removes admin-bypass authorization rather than introducing a second credential during release recovery. Desktop release PRs must now have GitHub's overall `APPROVED` decision and a MEMBER/OWNER/COLLABORATOR approval attached to the exact candidate SHA. ## Validation - `scripts/test-desktop-release-authorization.sh` - `scripts/test-release-ref-contract.sh` - `bash -n scripts/verify-desktop-release-merge.sh scripts/verify-desktop-release-authorization.sh scripts/test-desktop-release-authorization.sh scripts/test-release-ref-contract.sh` - `git diff --check origin/main...HEAD` The new flow test uses a stub `gh` executable, asserts the exact `page=1` request, fails any rule-suite API call, and rejects stale-SHA, untrusted-author, changes-requested review, and non-approved aggregate-decision cases. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
This commit is contained in:
@@ -111,7 +111,6 @@ jobs:
|
|||||||
PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
|
PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
|
||||||
PR_BASE_REF: ${{ github.event.pull_request.base.ref }}
|
PR_BASE_REF: ${{ github.event.pull_request.base.ref }}
|
||||||
PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
|
PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
|
||||||
MERGED_BY: ${{ github.event.pull_request.merged_by.login }}
|
|
||||||
MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }}
|
MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }}
|
||||||
run: |
|
run: |
|
||||||
VERSION="${VERSION#desktop-v}"
|
VERSION="${VERSION#desktop-v}"
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
def expected_default_pull_request_rule($ruleset_id):
|
|
||||||
[.rule_evaluations[] | select(
|
|
||||||
.rule_source.type == "ruleset" and
|
|
||||||
.rule_source.id == $ruleset_id and
|
|
||||||
.enforcement == "active" and
|
|
||||||
.rule_type == "pull_request" and
|
|
||||||
.result == "fail"
|
|
||||||
)] | length == 1;
|
|
||||||
|
|
||||||
.result == "bypass" and expected_default_pull_request_rule($ruleset_id)
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"id":3520068134,"actor_id":15384764,"actor_name":"wpfleger96","before_sha":"6e02e0a9022a1a098c44ee611b4d9784addb10c7","after_sha":"209536ade6c5ebf7fa82671d7ca0b74f599a40cc","ref":"refs/heads/main","repository_id":1174789082,"repository_name":"buzz","pushed_at":"2026-07-31T11:06:21-06:00","result":"bypass","rule_evaluations":[{"rule_source":{"type":"ruleset","id":13596885,"name":"Default"},"enforcement":"active","result":"fail","rule_type":"pull_request","details":"1 review requesting changes by reviewers with write access."},{"rule_source":{"type":"ruleset","id":13596885,"name":"Default"},"enforcement":"active","result":"pass","rule_type":"required_status_checks"},{"rule_source":{"type":"ruleset","id":13596885,"name":"Default"},"enforcement":"active","result":"pass","rule_type":"non_fast_forward"},{"rule_source":{"type":"ruleset","id":13596885,"name":"Default"},"enforcement":"active","result":"pass","rule_type":"deletion"}]}
|
|
||||||
69
scripts/test-desktop-release-authorization.sh
Executable file
69
scripts/test-desktop-release-authorization.sh
Executable file
@@ -0,0 +1,69 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
tmp=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$tmp"' EXIT
|
||||||
|
mkdir -p "$tmp/bin"
|
||||||
|
cat >"$tmp/bin/gh" <<'GH'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
printf '%q ' "$@" >>"$GH_CALLS"
|
||||||
|
printf '\n' >>"$GH_CALLS"
|
||||||
|
|
||||||
|
[[ "${1:-}" == api ]] || { echo "expected gh api" >&2; exit 91; }
|
||||||
|
if [[ "${2:-}" == graphql ]]; then
|
||||||
|
expected_query='query($owner:String!,$repo:String!,$number:Int!){repository(owner:$owner,name:$repo){pullRequest(number:$number){reviewDecision}}}'
|
||||||
|
[[ "$#" -eq 12 && "$3" == -f && "$4" == "query=$expected_query" &&
|
||||||
|
"$5" == -F && "$6" == owner=block &&
|
||||||
|
"$7" == -F && "$8" == repo=buzz &&
|
||||||
|
"$9" == -F && "${10}" == number=123 &&
|
||||||
|
"${11}" == --jq && "${12}" == '.data.repository.pullRequest' ]] || {
|
||||||
|
echo "GraphQL call does not match the deployed query contract" >&2; exit 92;
|
||||||
|
}
|
||||||
|
if [[ -n "${REVIEW_DECISION:-}" ]]; then printf '%s\n' "$REVIEW_DECISION"; else printf '%s\n' '{"reviewDecision":"APPROVED"}'; fi
|
||||||
|
elif [[ "$#" -eq 4 && "$2" == --paginate && "$3" == --slurp && "$4" == "repos/block/buzz/pulls/123/reviews?per_page=100&page=1" ]]; then
|
||||||
|
[[ "${GH_FAIL_REVIEWS:-false}" != true ]] || { echo "simulated reviews API failure" >&2; exit 94; }
|
||||||
|
if [[ -n "${REVIEWS:-}" ]]; then printf '%s\n' "$REVIEWS"; else printf '%s\n' '[[],[{"state":"APPROVED","commit_id":"head","author_association":"MEMBER"}]]'; fi
|
||||||
|
else
|
||||||
|
echo "unexpected or malformed gh call: $*" >&2
|
||||||
|
exit 95
|
||||||
|
fi
|
||||||
|
GH
|
||||||
|
chmod +x "$tmp/bin/gh"
|
||||||
|
|
||||||
|
run_authorization() {
|
||||||
|
(cd "$repo_root" && PATH="$tmp/bin:$PATH" GH_CALLS="$tmp/calls" GH_TOKEN=test \
|
||||||
|
GITHUB_REPOSITORY=block/buzz PR_NUMBER=123 PR_HEAD_SHA=head \
|
||||||
|
REVIEW_DECISION="${REVIEW_DECISION-}" REVIEWS="${REVIEWS-}" GH_FAIL_REVIEWS="${GH_FAIL_REVIEWS-false}" \
|
||||||
|
scripts/verify-desktop-release-authorization.sh)
|
||||||
|
}
|
||||||
|
|
||||||
|
: >"$tmp/calls"
|
||||||
|
run_authorization
|
||||||
|
! grep -Fq 'rule-suites' "$tmp/calls"
|
||||||
|
|
||||||
|
for invalid in \
|
||||||
|
'[[{"state":"APPROVED","commit_id":"stale","author_association":"MEMBER"}]]' \
|
||||||
|
'[[{"state":"APPROVED","commit_id":"head","author_association":"NONE"}]]' \
|
||||||
|
'[[{"state":"CHANGES_REQUESTED","commit_id":"head","author_association":"MEMBER"}]]'; do
|
||||||
|
: >"$tmp/calls"
|
||||||
|
if REVIEWS="$invalid" run_authorization >/dev/null 2>&1; then
|
||||||
|
echo "invalid approval was accepted: $invalid" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
: >"$tmp/calls"
|
||||||
|
if REVIEW_DECISION='{"reviewDecision":"CHANGES_REQUESTED"}' run_authorization >/dev/null 2>&1; then
|
||||||
|
echo "changes-requested review decision was accepted" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
: >"$tmp/calls"
|
||||||
|
if GH_FAIL_REVIEWS=true run_authorization >/dev/null 2>&1; then
|
||||||
|
echo "reviews API failure was ignored" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "desktop release authorization passed"
|
||||||
@@ -64,24 +64,15 @@ grep -Fq 'git/refs' "$auto_tag"
|
|||||||
grep -Fq 'TAG_PREFIX="desktop-v"' "$auto_tag"
|
grep -Fq 'TAG_PREFIX="desktop-v"' "$auto_tag"
|
||||||
grep -Fq 'target_sha=${{ github.event.pull_request.merge_commit_sha }}' "$auto_tag"
|
grep -Fq 'target_sha=${{ github.event.pull_request.merge_commit_sha }}' "$auto_tag"
|
||||||
grep -Fq 'scripts/verify-desktop-release-merge.sh' "$auto_tag"
|
grep -Fq 'scripts/verify-desktop-release-merge.sh' "$auto_tag"
|
||||||
|
"$repo_root/scripts/test-desktop-release-authorization.sh"
|
||||||
bypass_filter="$repo_root/scripts/desktop-release-bypass-authorized.jq"
|
if rg -q 'rule-suites|desktop-release-bypass-authorized|MERGED_BY' \
|
||||||
bypass_fixture="$repo_root/scripts/fixtures/desktop-release-rule-suite-bypass.json"
|
"$repo_root/scripts/verify-desktop-release-merge.sh" \
|
||||||
jq -e --argjson ruleset_id 13596885 -f "$bypass_filter" "$bypass_fixture" >/dev/null || {
|
"$repo_root/scripts/verify-desktop-release-authorization.sh" \
|
||||||
echo "real squash-bypass fixture was rejected" >&2
|
"$auto_tag"; then
|
||||||
|
echo "desktop auto-tag still depends on unavailable rule-suite authorization" >&2
|
||||||
exit 1
|
exit 1
|
||||||
}
|
fi
|
||||||
for mutation in \
|
|
||||||
'.result = "pass"' \
|
|
||||||
'(.rule_evaluations[] | select(.rule_type == "pull_request")).result = "pass"' \
|
|
||||||
'(.rule_evaluations[] | select(.rule_type == "pull_request")).rule_source.id = 0' \
|
|
||||||
'(.rule_evaluations[] | select(.rule_type == "pull_request")).enforcement = "evaluate"' \
|
|
||||||
'del(.rule_evaluations[] | select(.rule_type == "pull_request"))'; do
|
|
||||||
if jq "$mutation" "$bypass_fixture" | jq -e --argjson ruleset_id 13596885 -f "$bypass_filter" >/dev/null; then
|
|
||||||
echo "bypass filter accepted invalid fixture mutation: $mutation" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
review_filter="$repo_root/scripts/review-decision-approved.jq"
|
review_filter="$repo_root/scripts/review-decision-approved.jq"
|
||||||
for fixture in \
|
for fixture in \
|
||||||
'{"reviewDecision":"CHANGES_REQUESTED"}' \
|
'{"reviewDecision":"CHANGES_REQUESTED"}' \
|
||||||
|
|||||||
15
scripts/verify-desktop-release-authorization.sh
Executable file
15
scripts/verify-desktop-release-authorization.sh
Executable file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
: "${PR_HEAD_SHA:?}"
|
||||||
|
: "${PR_NUMBER:?}"
|
||||||
|
: "${GITHUB_REPOSITORY:?}"
|
||||||
|
: "${GH_TOKEN:?}"
|
||||||
|
|
||||||
|
review="$(gh api graphql -f query='query($owner:String!,$repo:String!,$number:Int!){repository(owner:$owner,name:$repo){pullRequest(number:$number){reviewDecision}}}' -F owner="${GITHUB_REPOSITORY%/*}" -F repo="${GITHUB_REPOSITORY#*/}" -F number="$PR_NUMBER" --jq '.data.repository.pullRequest')"
|
||||||
|
reviews="$(gh api --paginate --slurp "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/reviews?per_page=100&page=1")"
|
||||||
|
valid_approvals="$(jq --arg sha "$PR_HEAD_SHA" '[.[][] | select(.state == "APPROVED" and .commit_id == $sha and (.author_association == "MEMBER" or .author_association == "OWNER" or .author_association == "COLLABORATOR"))] | length' <<<"$reviews")"
|
||||||
|
if ! jq -e -f scripts/review-decision-approved.jq <<<"$review" >/dev/null || [[ "$valid_approvals" -eq 0 ]]; then
|
||||||
|
echo "release lacks an exact-head approval" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -3,14 +3,10 @@ set -euo pipefail
|
|||||||
|
|
||||||
: "${PR_HEAD_SHA:?}"
|
: "${PR_HEAD_SHA:?}"
|
||||||
: "${MERGE_SHA:?}"
|
: "${MERGE_SHA:?}"
|
||||||
: "${MERGED_BY:?}"
|
|
||||||
: "${VERSION:?}"
|
: "${VERSION:?}"
|
||||||
: "${PR_NUMBER:?}"
|
: "${PR_NUMBER:?}"
|
||||||
: "${GH_TOKEN:?}"
|
: "${GH_TOKEN:?}"
|
||||||
|
|
||||||
# This ID is the release-authority policy anchor. A bypass of another ruleset
|
|
||||||
# must never authorize a desktop release.
|
|
||||||
readonly DEFAULT_RULESET_ID=13596885
|
|
||||||
required_checks=(
|
required_checks=(
|
||||||
"Desktop E2E Integration"
|
"Desktop E2E Integration"
|
||||||
"Desktop"
|
"Desktop"
|
||||||
@@ -47,41 +43,7 @@ git merge-base --is-ancestor "$MERGE_SHA" origin/main || { echo "squash commit i
|
|||||||
git checkout --detach "$PR_HEAD_SHA"
|
git checkout --detach "$PR_HEAD_SHA"
|
||||||
scripts/desktop_release.py validate --candidate "$PR_HEAD_SHA" --version "$VERSION" --repo "$GITHUB_REPOSITORY"
|
scripts/desktop_release.py validate --candidate "$PR_HEAD_SHA" --version "$VERSION" --repo "$GITHUB_REPOSITORY"
|
||||||
|
|
||||||
review="$(gh api graphql -f query='query($owner:String!,$repo:String!,$number:Int!){repository(owner:$owner,name:$repo){pullRequest(number:$number){reviewDecision}}}' -F owner="${GITHUB_REPOSITORY%/*}" -F repo="${GITHUB_REPOSITORY#*/}" -F number="$PR_NUMBER" --jq '.data.repository.pullRequest')"
|
scripts/verify-desktop-release-authorization.sh
|
||||||
reviews="$(gh api --paginate --slurp "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/reviews?per_page=100")"
|
|
||||||
valid_approvals="$(jq --arg sha "$PR_HEAD_SHA" '[.[][] | select(.state == "APPROVED" and .commit_id == $sha and (.author_association == "MEMBER" or .author_association == "OWNER" or .author_association == "COLLABORATOR"))] | length' <<<"$reviews")"
|
|
||||||
review_authorized=false
|
|
||||||
if jq -e -f scripts/review-decision-approved.jq <<<"$review" >/dev/null && [[ "$valid_approvals" -gt 0 ]]; then
|
|
||||||
review_authorized=true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Rule suites are GitHub's durable record that a permitted bypass actor landed
|
|
||||||
# this exact main update. The suite does not identify the matching bypass grant,
|
|
||||||
# so the Default ruleset's bypass list is itself the release-authority policy.
|
|
||||||
bypass_authorized=false
|
|
||||||
for attempt in {1..5}; do
|
|
||||||
suites="$(gh api --paginate --slurp "repos/$GITHUB_REPOSITORY/rulesets/rule-suites?ref=refs/heads/main&per_page=100")"
|
|
||||||
mapfile -t suite_ids < <(jq -r --arg before "$base_sha" --arg after "$MERGE_SHA" --arg actor "$MERGED_BY" '
|
|
||||||
.[][] | select(.ref == "refs/heads/main" and .before_sha == $before and .after_sha == $after and .actor_name == $actor and .result == "bypass") | .id
|
|
||||||
' <<<"$suites")
|
|
||||||
if [[ "${#suite_ids[@]}" -gt 1 ]]; then
|
|
||||||
echo "multiple rule suites matched the release landing" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [[ "${#suite_ids[@]}" -eq 1 ]]; then
|
|
||||||
suite="$(gh api "repos/$GITHUB_REPOSITORY/rulesets/rule-suites/${suite_ids[0]}")"
|
|
||||||
if jq -e --argjson ruleset_id "$DEFAULT_RULESET_ID" -f scripts/desktop-release-bypass-authorized.jq <<<"$suite" >/dev/null; then
|
|
||||||
bypass_authorized=true
|
|
||||||
fi
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
[[ "$attempt" -eq 5 ]] || sleep "$attempt"
|
|
||||||
done
|
|
||||||
|
|
||||||
[[ "$review_authorized" == true || "$bypass_authorized" == true ]] || {
|
|
||||||
echo "release lacks an exact-head approval or authorized Default-ruleset bypass" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
checks="$(gh api --paginate --slurp "repos/$GITHUB_REPOSITORY/commits/$PR_HEAD_SHA/check-runs?per_page=100")"
|
checks="$(gh api --paginate --slurp "repos/$GITHUB_REPOSITORY/commits/$PR_HEAD_SHA/check-runs?per_page=100")"
|
||||||
for required in "${required_checks[@]}"; do
|
for required in "${required_checks[@]}"; do
|
||||||
|
|||||||
Reference in New Issue
Block a user