fix(ci): validate equivalent PR merge refs (#12441)
## Summary - validate the live master ref and event base SHA before AWS routing - accept GitHub synthetic merge commits only when the event and live commits have the exact expected base/head parents and identical tree - preserve fail-closed routing for malformed, stale, replaced, or untrusted events ## Canary finding A trusted reopened PR produced two synthetic merge SHAs with different timestamps but identical current base/head parents and tree. The former exact-SHA comparison safely fell back to GitHub-hosted runners, but could not route a valid event to AWS. ## Validation - `actionlint .github/workflows/pr-trusted.yml .github/workflows/pr.yml` - `node --test ./scripts/__tests__/e2e-shard.test.mjs` - real-event gate simulation selects the Fleet label for equivalent merge commits - negative simulations keep an untrusted sender and replaced head on `ubuntu-latest` - AWS routing remains disabled during rotation
This commit is contained in:
parent
c916af0cc0
commit
3b295b05dc
|
|
@ -40,6 +40,7 @@ jobs:
|
|||
EVENT_SENDER_ID: ${{ github.event.sender.id }}
|
||||
EVENT_BASE_REPOSITORY_ID: ${{ github.event.pull_request.base.repo.id }}
|
||||
EVENT_BASE_REF: ${{ github.event.pull_request.base.ref }}
|
||||
EVENT_BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
EVENT_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
EVENT_MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }}
|
||||
RUN_ID: ${{ github.run_id }}
|
||||
|
|
@ -59,6 +60,10 @@ jobs:
|
|||
[[ "$1" =~ ^[1-9][0-9]*$ ]]
|
||||
}
|
||||
|
||||
is_commit_sha() {
|
||||
[[ "$1" =~ ^[0-9a-f]{40}$ ]]
|
||||
}
|
||||
|
||||
is_allowed() {
|
||||
local user_id="$1"
|
||||
jq -e --argjson user_id "$user_id" 'index($user_id) != null' \
|
||||
|
|
@ -86,6 +91,10 @@ jobs:
|
|||
is_allowed "$user_id" || fail_closed "GitHub user ID $user_id is not allowlisted"
|
||||
done
|
||||
|
||||
for commit_sha in "$EVENT_BASE_SHA" "$EVENT_HEAD_SHA" "$EVENT_MERGE_SHA"; do
|
||||
is_commit_sha "$commit_sha" || fail_closed 'event contains a malformed commit SHA'
|
||||
done
|
||||
|
||||
pr_json="$(gh api \
|
||||
-H 'Accept: application/vnd.github+json' \
|
||||
-H 'X-GitHub-Api-Version: 2022-11-28' \
|
||||
|
|
@ -96,18 +105,69 @@ jobs:
|
|||
--argjson repository_id 1170821064 \
|
||||
--argjson author_id "$EVENT_PR_AUTHOR_ID" \
|
||||
--arg base_ref "$EVENT_BASE_REF" \
|
||||
--arg base_sha "$EVENT_BASE_SHA" \
|
||||
--arg head_sha "$EVENT_HEAD_SHA" \
|
||||
--arg merge_sha "$EVENT_MERGE_SHA" '
|
||||
'
|
||||
.state == "open" and
|
||||
.user.id == $author_id and
|
||||
.base.repo.id == $repository_id and
|
||||
.base.ref == $base_ref and
|
||||
.head.sha == $head_sha and
|
||||
($merge_sha != "") and
|
||||
.merge_commit_sha == $merge_sha
|
||||
.base.sha == $base_sha and
|
||||
.head.sha == $head_sha
|
||||
' <<< "$pr_json" >/dev/null 2>&1 \
|
||||
|| fail_closed 'current pull request state does not match the triggering event'
|
||||
|
||||
live_merge_sha="$(jq -r '.merge_commit_sha // empty' <<< "$pr_json")"
|
||||
is_commit_sha "$live_merge_sha" || fail_closed 'current pull request has no valid merge SHA'
|
||||
|
||||
base_ref_json="$(gh api \
|
||||
-H 'Accept: application/vnd.github+json' \
|
||||
-H 'X-GitHub-Api-Version: 2022-11-28' \
|
||||
"/repos/paperclipai/paperclip/git/ref/heads/$EVENT_BASE_REF" 2>/dev/null)" \
|
||||
|| fail_closed 'could not refresh the base branch ref'
|
||||
|
||||
jq -e --arg base_sha "$EVENT_BASE_SHA" '
|
||||
.object.type == "commit" and .object.sha == $base_sha
|
||||
' <<< "$base_ref_json" >/dev/null 2>&1 \
|
||||
|| fail_closed 'base branch moved after the triggering event'
|
||||
|
||||
event_merge_json="$(gh api \
|
||||
-H 'Accept: application/vnd.github+json' \
|
||||
-H 'X-GitHub-Api-Version: 2022-11-28' \
|
||||
"/repos/paperclipai/paperclip/git/commits/$EVENT_MERGE_SHA" 2>/dev/null)" \
|
||||
|| fail_closed 'could not inspect the event merge commit'
|
||||
|
||||
live_merge_json="$(gh api \
|
||||
-H 'Accept: application/vnd.github+json' \
|
||||
-H 'X-GitHub-Api-Version: 2022-11-28' \
|
||||
"/repos/paperclipai/paperclip/git/commits/$live_merge_sha" 2>/dev/null)" \
|
||||
|| fail_closed 'could not inspect the current merge commit'
|
||||
|
||||
validate_merge_commit() {
|
||||
local merge_json="$1"
|
||||
local expected_sha="$2"
|
||||
jq -e \
|
||||
--arg expected_sha "$expected_sha" \
|
||||
--arg base_sha "$EVENT_BASE_SHA" \
|
||||
--arg head_sha "$EVENT_HEAD_SHA" '
|
||||
.sha == $expected_sha and
|
||||
(.parents | length) == 2 and
|
||||
.parents[0].sha == $base_sha and
|
||||
.parents[1].sha == $head_sha and
|
||||
(.tree.sha | test("^[0-9a-f]{40}$"))
|
||||
' <<< "$merge_json" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
validate_merge_commit "$event_merge_json" "$EVENT_MERGE_SHA" \
|
||||
|| fail_closed 'event merge commit does not match the current base and head'
|
||||
validate_merge_commit "$live_merge_json" "$live_merge_sha" \
|
||||
|| fail_closed 'current merge commit does not match the triggering base and head'
|
||||
|
||||
event_merge_tree="$(jq -r '.tree.sha' <<< "$event_merge_json")"
|
||||
live_merge_tree="$(jq -r '.tree.sha' <<< "$live_merge_json")"
|
||||
[[ "$event_merge_tree" == "$live_merge_tree" ]] \
|
||||
|| fail_closed 'current merge tree differs from the triggering merge tree'
|
||||
|
||||
run_json="$(gh api \
|
||||
-H 'Accept: application/vnd.github+json' \
|
||||
-H 'X-GitHub-Api-Version: 2022-11-28' \
|
||||
|
|
|
|||
Loading…
Reference in New Issue