Merge pull request #1966 from modelcontextprotocol/v2/ci/harden-claude-workflow
ci: gate claude.yml on same-repo PRs and pin actions to commit SHAs
This commit is contained in:
commit
eee482ea87
1 changed files with 45 additions and 8 deletions
55
.github/workflows/claude.yml
vendored
55
.github/workflows/claude.yml
vendored
|
|
@ -35,13 +35,18 @@ jobs:
|
|||
id-token: write
|
||||
actions: read
|
||||
steps:
|
||||
# Actions are pinned to full commit SHAs rather than mutable major tags:
|
||||
# this job holds ANTHROPIC_API_KEY and grants the agent Bash, so a
|
||||
# force-moved tag would be an unreviewed code change inside a
|
||||
# secret-holding job. The trailing `# vX.Y.Z` comment is the form
|
||||
# Dependabot reads, so pinning costs us no upgrade automation. (#1882)
|
||||
- name: Get PR details
|
||||
if: |
|
||||
(github.event_name == 'issue_comment' && github.event.issue.pull_request) ||
|
||||
github.event_name == 'pull_request_review_comment' ||
|
||||
github.event_name == 'pull_request_review'
|
||||
id: pr
|
||||
uses: actions/github-script@v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
let prNumber;
|
||||
|
|
@ -57,26 +62,58 @@ jobs:
|
|||
pull_number: prNumber
|
||||
});
|
||||
|
||||
core.setOutput('sha', pr.data.head.sha);
|
||||
core.setOutput('repo', pr.data.head.repo.full_name);
|
||||
// A fork PR's head lives in a different repository — or in none at
|
||||
// all, if the fork was deleted after the PR was opened (`head.repo`
|
||||
// is then null). Neither is ours to check out, so both count as a
|
||||
// fork here and the steps below decline rather than guess.
|
||||
const headRepo = pr.data.head.repo?.full_name ?? null;
|
||||
const isFork = headRepo !== `${context.repo.owner}/${context.repo.repo}`;
|
||||
|
||||
core.setOutput('sha', pr.data.head.sha);
|
||||
core.setOutput('is_fork', String(isFork));
|
||||
|
||||
# A fork PR's head is untrusted code, and checking it out would put it in
|
||||
# reach of a tool-enabled agent run holding ANTHROPIC_API_KEY. Reviewing
|
||||
# the base tree instead would only trade that for a confident review of
|
||||
# the wrong tree, so decline visibly and leave the reason in the run.
|
||||
- name: Decline fork PR
|
||||
if: steps.pr.outcome == 'success' && steps.pr.outputs.is_fork == 'true'
|
||||
run: |
|
||||
{
|
||||
echo "### Claude Code declined this pull request"
|
||||
echo
|
||||
echo "The head branch lives in a fork, so its code is not checked out and Claude is not run."
|
||||
echo "Push the branch to this repository and re-trigger if a review is needed."
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
# No `repository:` — a same-repo head is all that reaches this step, and
|
||||
# checkout defaults to `github.repository`, which no PR can influence.
|
||||
- name: Checkout PR branch
|
||||
if: steps.pr.outcome == 'success'
|
||||
uses: actions/checkout@v6
|
||||
if: steps.pr.outcome == 'success' && steps.pr.outputs.is_fork == 'false'
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ steps.pr.outputs.sha }}
|
||||
repository: ${{ steps.pr.outputs.repo }}
|
||||
fetch-depth: 0
|
||||
|
||||
# `skipped` means the trigger was an issue or a non-PR comment, so there
|
||||
# is no head to check out and the base tree is the right one. The lookup
|
||||
# having *failed* is deliberately not included: this condition carries no
|
||||
# status-check function, so GitHub applies an implicit `success()` and
|
||||
# skips the step after a failed prior step anyway. Spelling out `skipped`
|
||||
# keeps that from having to be re-derived by the next reader.
|
||||
- name: Checkout repository
|
||||
if: steps.pr.outcome != 'success'
|
||||
uses: actions/checkout@v6
|
||||
if: steps.pr.outcome == 'skipped'
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Run Claude Code
|
||||
# Runs only against a tree this workflow actually checked out: the base
|
||||
# repo (non-PR trigger) or a same-repo PR head. A fork PR reaches here
|
||||
# with `is_fork == 'true'` and both disjuncts false, so it is skipped.
|
||||
if: steps.pr.outcome == 'skipped' || steps.pr.outputs.is_fork == 'false'
|
||||
id: claude
|
||||
uses: anthropics/claude-code-action@v1
|
||||
uses: anthropics/claude-code-action@5ef2e550a465a721f4f45e4a7d3c340c873e1dcc # v1.0.190
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue