Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/sdk-80-labeler-workflow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
75 changes: 75 additions & 0 deletions .github/workflows/labeler-apply.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: Labeler (apply)

# Privileged half of the labeler. Triggered by completion of the "Labeler" workflow,
# so it always runs the base-branch copy of this file (a PR cannot modify it) with
# write access. It reads the PR number from the trigger run's artifact, validates it,
# checks out only the trusted base .github/labeler.yml, and applies labels via
# actions/labeler driven by pr-number. Replaces the previous pull_request_target
# trigger while preserving fork-PR labeling (SDK-80).

on:
workflow_run:
workflows: [Labeler]
types:
- completed

permissions: {}

jobs:
apply:
name: Apply labels
if: >-
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
timeout-minutes: ${{ vars.TIMEOUT_MINUTES_SHORT && fromJSON(vars.TIMEOUT_MINUTES_SHORT) || 3 }}
runs-on: ${{ vars.RUNNER_NORMAL || 'ubuntu-latest' }}
permissions:
actions: read # download the artifact from the triggering run
contents: read # checkout the trusted base labeler config
pull-requests: write # apply labels
steps:
- name: Download PR number
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: pr-number
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Resolve and verify PR number
id: pr
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
script: |
const fs = require('fs');
const raw = fs.readFileSync('number', 'utf8').trim();
if (!/^[0-9]+$/.test(raw)) {
return core.setFailed(`Invalid PR number from artifact: '${raw}'`);
}
// The artifact comes from the untrusted pull_request leg, so its number
// could point at any PR. Bind it to the run that actually triggered this
// workflow_run before handing it to the privileged labeler: the claimed
// PR's head commit and head repository must match the trigger run.
const run = context.payload.workflow_run;
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: Number(raw),
});
if (pr.head.sha !== run.head_sha) {
return core.setFailed(`PR #${raw} head ${pr.head.sha} does not match triggering run head ${run.head_sha}`);
}
const prHeadRepo = pr.head.repo ? pr.head.repo.full_name : null;
const runHeadRepo = run.head_repository ? run.head_repository.full_name : null;
if (prHeadRepo !== runHeadRepo) {
return core.setFailed(`PR #${raw} head repo ${prHeadRepo} does not match triggering run head repo ${runHeadRepo}`);
}
core.setOutput('number', raw);
- name: Check out trusted labeler config
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
sparse-checkout: .github/labeler.yml
sparse-checkout-cone-mode: false
persist-credentials: false
- uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6
with:
pr-number: ${{ steps.pr.outputs.number }}
configuration-path: .github/labeler.yml
27 changes: 21 additions & 6 deletions .github/workflows/labeler.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
name: Labeler

# Untrusted trigger half of the labeler. Runs in the PR (incl. fork) context with a
# read-only token and no secrets. It only records the PR number to an artifact; the
# privileged labeling happens in labeler-apply.yml on workflow_run. This avoids
# pull_request_target (see SDK-80 / Monorepo Supply-Chain Hardening).

on:
- pull_request_target
- pull_request

permissions: {}

jobs:
triage:
collect:
name: Collect PR metadata
timeout-minutes: ${{ vars.TIMEOUT_MINUTES_SHORT && fromJSON(vars.TIMEOUT_MINUTES_SHORT) || 3 }}
permissions:
contents: read
pull-requests: write
runs-on: ${{ vars.RUNNER_NORMAL || 'ubuntu-latest' }}
steps:
- uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6
- name: Save PR number
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
mkdir -p ./pr
echo "$PR_NUMBER" > ./pr/number
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: pr-number
path: ./pr/
retention-days: 1
Loading