From e8c338423d33856ad577f808e5313f422fc97bd5 Mon Sep 17 00:00:00 2001 From: Thanh Nguyen Date: Wed, 25 Mar 2026 17:01:34 -0400 Subject: [PATCH 1/4] New Sigscanner API and full PR check --- .github/workflows/sigscanner-check.yml | 233 +++++++++++++++++++------ 1 file changed, 183 insertions(+), 50 deletions(-) diff --git a/.github/workflows/sigscanner-check.yml b/.github/workflows/sigscanner-check.yml index 8903c3b..1ee64fa 100644 --- a/.github/workflows/sigscanner-check.yml +++ b/.github/workflows/sigscanner-check.yml @@ -1,70 +1,203 @@ -name: "SigScanner Check" +name: "Sigscanner Check" +description: "This check ensures all commits in a PR have verified signatures" on: merge_group: pull_request: -permissions: {} +concurrency: + group: ${{ github.workflow }}-pr-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + +permissions: + pull-requests: read jobs: sigscanner-check: runs-on: ubuntu-latest + timeout-minutes: 10 # Skip on merge group events if: ${{ github.event_name == 'pull_request' }} + env: + REPOSITORY: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + VERIFY_MAX_ATTEMPTS: "3" + # If the PR has more than this many commits, abort + VERIFY_MAX_COMMITS: "512" steps: - - name: "SigScanner checking ${{ github.sha }} by ${{ github.actor }}" + - name: "Fetch PR commits" + id: fetch-commits env: - API_TOKEN: ${{ secrets.SIGSCANNER_API_TOKEN }} - API_URL: ${{ secrets.SIGSCANNER_API_URL }} - COMMIT_SHA: ${{ github.sha }} - ACTOR: ${{ github.actor }} - REPOSITORY: ${{ github.repository }} - EVENT_NAME: ${{ github.event_name }} + GH_TOKEN: ${{ github.token }} + run: | + # Clean up + rm -f /tmp/commits_with_committer.csv /tmp/verified_commits.csv /tmp/failed_commits.csv \ + /tmp/pending_commits.csv + + # Fetch all commit hashes and their corresponding committers in this PR + gh api "repos/$REPOSITORY/pulls/$PR_NUMBER/commits" --paginate \ + --jq '.[] | [.sha, (.committer.login // "")] | join(",")' \ + > /tmp/commits_with_committer.csv + commit_count=$(wc -l < /tmp/commits_with_committer.csv | tr -d ' ') + echo "Found $commit_count commits in PR #$PR_NUMBER" + echo "commit-count=$commit_count" >> "$GITHUB_OUTPUT" + + if [[ $commit_count -eq 0 ]]; then + echo "❌ Unexpected: no commits to verify" + exit 1 + fi + + if [[ $commit_count -gt $VERIFY_MAX_COMMITS ]]; then + echo "❌ Too many commits: $commit_count (max $VERIFY_MAX_COMMITS)" + exit 1 + fi + + - name: "Sigscanner check" + id: sigscanner + continue-on-error: true + env: + SIGSCANNER_URL: ${{ secrets.SIGSCANNER_URL }} + SIGSCANNER_API_KEY: ${{ secrets.SIGSCANNER_API_KEY }} + COMMIT_COUNT: ${{ steps.fetch-commits.outputs.commit-count }} run: | - echo "🔎 Checking commit $COMMIT_SHA by $ACTOR in $REPOSITORY - $EVENT_NAME" - - payload=$(printf '{"commit":"%s","repository":"%s","author":"%s"}' \ - "$COMMIT_SHA" "$REPOSITORY" "$ACTOR") - - max_attempts=3 - attempt=1 - - # Retry on 5XXs - while [[ $attempt -le $max_attempts ]]; do - echo "Attempt $attempt/$max_attempts" - - CODE=$(curl \ - --silent \ - --output /dev/null \ - --write-out '%{http_code}' \ - --max-time 20 \ - -X POST \ - -H "Content-Type: application/json" \ - -H "Authorization: $API_TOKEN" \ - --url "$API_URL" \ - --data "$payload") - - echo "Received $CODE" - if [[ "$CODE" == "200" ]]; then - echo "✅ Commit is verified" - exit 0 - elif [[ "$CODE" == "400" ]]; then - echo "❌ Bad request" - exit 1 - elif [[ "$CODE" == "403" ]]; then - echo "❌ Commit is NOT verified" - exit 1 - elif [[ "$CODE" =~ ^5[0-9][0-9]$ ]]; then - if [[ $attempt -lt $max_attempts ]]; then - echo "Retrying in 15s..." - sleep 15 + touch /tmp/verified_commits.csv /tmp/failed_commits.csv + + echo "🔎 Verifying $COMMIT_COUNT commits" + + # Loop through all the commits + # For each commit, query Sigscanner with retry to check if it's verified + # Verified commit hashes with committer username are saved to /tmp/verified_commits.csv + # Failed-to-verify commit hashes with committer username are saved to /tmp/failed_commits.csv + while IFS=, read -r commit_sha committer_username; do + [[ -z "$commit_sha" ]] && continue + + commit_is_verified=false + request_attempt=1 + + while [[ $request_attempt -le $VERIFY_MAX_ATTEMPTS ]]; do + response=$(curl -s --max-time 20 -G \ + -H "X-SIGSCANNER-SECRET: $SIGSCANNER_API_KEY" \ + --data-urlencode "commit=$commit_sha" \ + --data-urlencode "repository=$REPOSITORY" \ + --data-urlencode "author=$committer_username" \ + "$SIGSCANNER_URL") + + res_verified=$(echo "$response" | jq -r '.verified') + res_error=$(echo "$response" | jq -r '.error') + + if [[ "$res_verified" == "true" ]]; then + commit_is_verified=true + break + elif [[ "$res_error" == "null" || "$res_error" == "" ]]; then + # This means the commit is explicitly unverified and shouldn't be retried + break fi + + [[ $request_attempt -lt $VERIFY_MAX_ATTEMPTS ]] && sleep 15 + request_attempt=$((request_attempt + 1)) + done + + if [[ "$commit_is_verified" == "true" ]]; then + echo "✅ $commit_sha" + echo "$commit_sha,$committer_username" >> /tmp/verified_commits.csv else - echo "❌ Unexpected response" - exit 1 + echo "❌ $commit_sha" + echo "$commit_sha,$committer_username" >> /tmp/failed_commits.csv fi + done < /tmp/commits_with_committer.csv + + verified_commit_count=$(wc -l < /tmp/verified_commits.csv | tr -d ' ') + echo "Verified: $verified_commit_count / $COMMIT_COUNT" - attempt=$((attempt + 1)) - done + if [[ $verified_commit_count -eq $COMMIT_COUNT ]]; then + echo "✅ All commits verified" + exit 0 + fi + + echo "❌ Not all commits verified" exit 1 + + - name: "Sigscanner fallback check" + if: ${{ steps.sigscanner.outcome == 'failure' }} + env: + API_TOKEN: ${{ secrets.SIGSCANNER_API_TOKEN }} + API_URL: ${{ secrets.SIGSCANNER_API_URL }} + COMMIT_COUNT: ${{ steps.fetch-commits.outputs.commit-count }} + run: | + touch /tmp/verified_commits.csv + + # Extract commits failed to verify earlier + comm -23 \ + <(sort /tmp/commits_with_committer.csv) \ + <(sort /tmp/verified_commits.csv) \ + > /tmp/pending_commits.csv + pending_commit_count=$(wc -l < /tmp/pending_commits.csv | tr -d ' ') + + if [[ $pending_commit_count -eq 0 ]]; then + echo "✅ All commits verified" + exit 0 + fi + + echo "🔎 Fallback: verifying $pending_commit_count remaining commits" + + # Loop through all the commits again with retry with the fallback API + while IFS=, read -r commit_sha committer_username; do + [[ -z "$commit_sha" ]] && continue + + commit_is_verified=false + request_attempt=1 + + while [[ $request_attempt -le $VERIFY_MAX_ATTEMPTS ]]; do + body=$(jq -n \ + --arg commit "$commit_sha" \ + --arg repository "$REPOSITORY" \ + --arg author "$committer_username" \ + '{commit: $commit, repository: $repository, author: $author}') + + http_status=$(curl --silent --output /dev/null --write-out '%{http_code}' \ + --max-time 20 -X POST \ + -H "Content-Type: application/json" \ + -H "Authorization: $API_TOKEN" \ + --url "$API_URL" \ + --data "$body") + + case $http_status in + 200) + commit_is_verified=true + break + ;; + 400) + echo "❌ $commit_sha - Bad request" + break + ;; + 403) break ;; + 5??) + [[ $request_attempt -lt $VERIFY_MAX_ATTEMPTS ]] && sleep 15 + ;; + *) + echo "❌ $commit_sha - Unexpected: $http_status" + break + ;; + esac + + request_attempt=$((request_attempt + 1)) + done + + if [[ "$commit_is_verified" == "true" ]]; then + echo "✅ $commit_sha" + echo "$commit_sha,$committer_username" >> /tmp/verified_commits.csv + else + echo "❌ $commit_sha" + fi + done < /tmp/pending_commits.csv + + total_verified_count=$(wc -l < /tmp/verified_commits.csv | tr -d ' ') + echo "Verified: $total_verified_count / $COMMIT_COUNT" + + if [[ $total_verified_count -ne $COMMIT_COUNT ]]; then + echo "❌ Not all commits verified by fallback" + exit 1 + fi + + echo "✅ All commits verified" From c2854bce3938ca104b76980b62c7046286f4461d Mon Sep 17 00:00:00 2001 From: Thanh Nguyen Date: Wed, 25 Mar 2026 19:39:02 -0400 Subject: [PATCH 2/4] Remove commit count limit --- .github/workflows/sigscanner-check.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/sigscanner-check.yml b/.github/workflows/sigscanner-check.yml index 1ee64fa..d9a5de1 100644 --- a/.github/workflows/sigscanner-check.yml +++ b/.github/workflows/sigscanner-check.yml @@ -22,8 +22,6 @@ jobs: REPOSITORY: ${{ github.repository }} PR_NUMBER: ${{ github.event.pull_request.number }} VERIFY_MAX_ATTEMPTS: "3" - # If the PR has more than this many commits, abort - VERIFY_MAX_COMMITS: "512" steps: - name: "Fetch PR commits" id: fetch-commits @@ -48,11 +46,6 @@ jobs: exit 1 fi - if [[ $commit_count -gt $VERIFY_MAX_COMMITS ]]; then - echo "❌ Too many commits: $commit_count (max $VERIFY_MAX_COMMITS)" - exit 1 - fi - - name: "Sigscanner check" id: sigscanner continue-on-error: true From 59c3e52560776e34b41b3bc989d3552ba3da47e5 Mon Sep 17 00:00:00 2001 From: Thanh Nguyen Date: Wed, 25 Mar 2026 20:10:57 -0400 Subject: [PATCH 3/4] simplify --- .github/workflows/sigscanner-check.yml | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/.github/workflows/sigscanner-check.yml b/.github/workflows/sigscanner-check.yml index d9a5de1..ab488d7 100644 --- a/.github/workflows/sigscanner-check.yml +++ b/.github/workflows/sigscanner-check.yml @@ -28,10 +28,6 @@ jobs: env: GH_TOKEN: ${{ github.token }} run: | - # Clean up - rm -f /tmp/commits_with_committer.csv /tmp/verified_commits.csv /tmp/failed_commits.csv \ - /tmp/pending_commits.csv - # Fetch all commit hashes and their corresponding committers in this PR gh api "repos/$REPOSITORY/pulls/$PR_NUMBER/commits" --paginate \ --jq '.[] | [.sha, (.committer.login // "")] | join(",")' \ @@ -54,7 +50,7 @@ jobs: SIGSCANNER_API_KEY: ${{ secrets.SIGSCANNER_API_KEY }} COMMIT_COUNT: ${{ steps.fetch-commits.outputs.commit-count }} run: | - touch /tmp/verified_commits.csv /tmp/failed_commits.csv + > /tmp/verified_commits.csv echo "🔎 Verifying $COMMIT_COUNT commits" @@ -96,7 +92,6 @@ jobs: echo "$commit_sha,$committer_username" >> /tmp/verified_commits.csv else echo "❌ $commit_sha" - echo "$commit_sha,$committer_username" >> /tmp/failed_commits.csv fi done < /tmp/commits_with_committer.csv @@ -120,11 +115,11 @@ jobs: run: | touch /tmp/verified_commits.csv - # Extract commits failed to verify earlier - comm -23 \ - <(sort /tmp/commits_with_committer.csv) \ - <(sort /tmp/verified_commits.csv) \ + # Extract commits failed to verify earlier by comparing the verified commits file + # with the full list of commits + grep -vxFf /tmp/verified_commits.csv /tmp/commits_with_committer.csv \ > /tmp/pending_commits.csv + pending_commit_count=$(wc -l < /tmp/pending_commits.csv | tr -d ' ') if [[ $pending_commit_count -eq 0 ]]; then From 64513f5b1324290e2c087506b475815ff49507c1 Mon Sep 17 00:00:00 2001 From: Thanh Nguyen Date: Wed, 25 Mar 2026 20:14:48 -0400 Subject: [PATCH 4/4] comment --- .github/workflows/sigscanner-check.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/sigscanner-check.yml b/.github/workflows/sigscanner-check.yml index ab488d7..3b4caf9 100644 --- a/.github/workflows/sigscanner-check.yml +++ b/.github/workflows/sigscanner-check.yml @@ -57,7 +57,6 @@ jobs: # Loop through all the commits # For each commit, query Sigscanner with retry to check if it's verified # Verified commit hashes with committer username are saved to /tmp/verified_commits.csv - # Failed-to-verify commit hashes with committer username are saved to /tmp/failed_commits.csv while IFS=, read -r commit_sha committer_username; do [[ -z "$commit_sha" ]] && continue