diff --git a/.github/workflows/pr-trusted.yml b/.github/workflows/pr-trusted.yml index 960c415ba6..30fc834655 100644 --- a/.github/workflows/pr-trusted.yml +++ b/.github/workflows/pr-trusted.yml @@ -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' \