mirror of https://github.com/garrytan/gstack.git
99 lines
4.2 KiB
YAML
99 lines
4.2 KiB
YAML
name: PR Title Sync
|
|
|
|
# WHY pull_request_target (not pull_request): the default GITHUB_TOKEN is
|
|
# READ-ONLY on fork PRs under `pull_request`, so the title-sync backstop could
|
|
# never `gh pr edit` a fork/agent PR. `pull_request_target` runs in the base-repo
|
|
# context with a write token, which fixes fork coverage.
|
|
#
|
|
# WHY this is SAFE (pull_request_target is the most dangerous trigger):
|
|
# - We check out the BASE repo (no `ref:`), so the only code we execute is
|
|
# trusted base-repo infra (bin/gstack-pr-title-rewrite.sh). We NEVER check
|
|
# out or run PR-head/fork code.
|
|
# - Every attacker-controlled PR field (title, head repo, head sha) arrives via
|
|
# `env:` and is referenced as a shell-quoted "$VAR". We NEVER inline a
|
|
# `${{ github.event.pull_request.* }}` expression inside the run: script
|
|
# (that would execute a crafted title as shell).
|
|
# - The PR-head VERSION is read as DATA via the API (raw media type), from the
|
|
# head repo at the head sha — never by checking out the head.
|
|
# test/pr-title-sync-workflow-safety.test.ts is the static tripwire for all of
|
|
# the above and fails CI if any of it regresses.
|
|
|
|
on:
|
|
pull_request_target:
|
|
types: [opened, synchronize, edited]
|
|
paths:
|
|
- 'VERSION'
|
|
|
|
concurrency:
|
|
group: pr-title-sync-${{ github.event.pull_request.number }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
sync:
|
|
name: Sync PR title to VERSION
|
|
runs-on: ubicloud-standard-8
|
|
permissions:
|
|
contents: read
|
|
pull-requests: write
|
|
if: github.actor != 'github-actions[bot]'
|
|
steps:
|
|
# Base repo only — trusted infra (the rewrite helper). No PR-head checkout.
|
|
- name: Checkout base repo (trusted)
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 1
|
|
|
|
- name: Rewrite PR title to match VERSION
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
PR_NUM: ${{ github.event.pull_request.number }}
|
|
# Attacker-controlled on fork PRs — env-only, never inlined into run:.
|
|
OLD_TITLE: ${{ github.event.pull_request.title }}
|
|
BASE_REPO: ${{ github.repository }}
|
|
HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
|
|
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
run: |
|
|
set -euo pipefail
|
|
chmod +x ./bin/gstack-pr-title-rewrite.sh
|
|
|
|
if [ "$HEAD_REPO" = "$BASE_REPO" ]; then IS_FORK=0; else IS_FORK=1; fi
|
|
|
|
# Read the PR-head VERSION as data (raw bytes), from the head repo at
|
|
# the head sha. Guard the assignment itself: under `set -e` a bare
|
|
# `VERSION=$(...)` would abort the step before any later [ -z ] check.
|
|
if ! VERSION=$(gh api -H "Accept: application/vnd.github.raw" \
|
|
"repos/$HEAD_REPO/contents/VERSION?ref=$HEAD_SHA" 2>/dev/null | tr -d '[:space:]'); then
|
|
VERSION=""
|
|
fi
|
|
|
|
if [ -z "$VERSION" ]; then
|
|
# Same-repo read failure should never happen — fail loudly so we
|
|
# notice. A fork miss (public-contents quirk, private fork) is a
|
|
# convenience gap, not a gate — warn and skip so the check stays green.
|
|
if [ "$IS_FORK" = "0" ]; then
|
|
echo "::error::Could not read VERSION from same-repo PR head ($HEAD_SHA)."
|
|
exit 1
|
|
fi
|
|
echo "::warning::Could not read VERSION from fork $HEAD_REPO ($HEAD_SHA); skipping title sync."
|
|
exit 0
|
|
fi
|
|
|
|
# The helper rejects a malformed VERSION (exit 2). Same policy: loud for
|
|
# same-repo, soft for forks. Never echo the raw (attacker-controlled)
|
|
# title — Actions still parses ::workflow-command:: from stdout.
|
|
if ! NEW_TITLE=$(./bin/gstack-pr-title-rewrite.sh "$VERSION" "$OLD_TITLE"); then
|
|
if [ "$IS_FORK" = "0" ]; then
|
|
echo "::error::Could not compute title for VERSION '$VERSION' on PR #$PR_NUM."
|
|
exit 1
|
|
fi
|
|
echo "::warning::Could not compute title for fork PR #$PR_NUM; skipping."
|
|
exit 0
|
|
fi
|
|
|
|
if [ "$NEW_TITLE" = "$OLD_TITLE" ]; then
|
|
echo "PR #$PR_NUM title already correct; no change."
|
|
exit 0
|
|
fi
|
|
gh pr edit "$PR_NUM" --title "$NEW_TITLE"
|
|
echo "PR #$PR_NUM title synced to VERSION."
|