security: harden sdk-regression issue_comment workflow — pin actions, scope permissions + token (PER-8610)#2332
Conversation
… (PER-8610) F-012 (CWE-284/CWE-269): the issue_comment-triggered SDK Regression workflow injects the cross-repo WORKFLOW_DISPATCH_ACTIONS_TOKEN PAT into steps that do not need it, including a third-party action. Scope the PAT to only the single step that strictly requires cross-repo dispatch: - Tiryoh/gha-jobid-action (third-party) now uses the built-in GITHUB_TOKEN with a new job-level `actions: read` grant (it only reads this run's job metadata on percy/cli). - Both actions/github-script commit-status steps now use GITHUB_TOKEN, covered by the existing job-level `statuses: write`. - convictional/trigger-workflow-and-wait keeps the PAT — it is the only step that dispatches test.yml in another percy repo, which GITHUB_TOKEN cannot do. All third-party action SHA pins were re-resolved live against the GitHub API and confirmed correct (unchanged). issue_comment trigger, the author-permission (write/admin) guard, and the regression flow are unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ssue-comment-hardening # Conflicts: # .github/workflows/sdk-regression.yml
Claude Code PR ReviewPR: #2332 • Head: e9e1f97 • Reviewers: fallback inline checklist (no in-scope project reviewers; CI-workflow-only change reviewed against a hardening-specific rubric) SummaryHardens Review Table
Hardening-specific checks (task rubric)
FindingsNo Critical/High/Medium findings. Two pre-existing (not introduced by this PR, outside the diff) informational nits:
Verdict: PASS |
Claude Code PR ReviewPR: #2332 • Head: e9e1f97 • Reviewers: orchestrator inline review (security + correctness + quality lens)
SummarySupply-chain / CI hardening (PER-8610) for the Review Table
FindingsNo Critical, High, or Medium findings. Informational (Low, pre-existing — not introduced by this PR, out of diff scope):
Adversarial verification results:
Verdict: PASS |
pranavz28
left a comment
There was a problem hiding this comment.
Automated multi-agent security review. Approve — the least-privilege split is correct and each remaining permission is used: statuses: write covers the commit-status calls, the added actions: read covers the job-id lookup, and the cross-repo dispatch PAT is narrowed to the one step that genuinely needs it. Verified: no checkout of PR-author code anywhere in the job, check-access gates before the PAT step and fails closed, head_ref is regex-validated before its only raw use, and all third-party actions are full-SHA pinned. A couple of optional cleanups inline / below.
| # Third-party action: only reads this run's job metadata on percy/cli, | ||
| # so the built-in GITHUB_TOKEN (actions: read) is sufficient. The | ||
| # cross-repo dispatch PAT is deliberately not exposed to it. | ||
| github_token: ${{ github.token }} |
There was a problem hiding this comment.
Good — moving job-id lookup + commit-status steps to the built-in github.token and adding only actions: read is exactly right.
P2 (residual — track, don't block): the PAT is still handed to the third-party convictional/trigger-workflow-and-wait. Unavoidable for cross-repo dispatch (the built-in token can't dispatch other repos), and SHA-pinning limits the code that receives it — but to shrink blast radius, prefer a fine-grained PAT scoped to just the SDK repos + actions:write, or a short-lived GitHub App installation token minted per run. (Can't verify the PAT's scope from the workflow — flag for secret-config review.)
P3 (pre-existing): the "Output Current Job Log URL" step around line 150 echoes steps.jobs.outputs.html_url, but the step id is job-url — so it always echoes empty. Since this PR is already the workflow-cleanup PR, cheap to fix the reference or drop the step. Also consider bumping actions/github-script off the 2021-era v4.1.0 (SHA-pinned, so not urgent).
There was a problem hiding this comment.
Thanks. P3 fixed in 53c174f — the "Output Current Job Log URL" step now references steps.job-url.outputs.html_url (the actual step id) instead of the empty steps.jobs...; the commit-status target_url already used the correct id. Left the SHA-pinned actions/github-script as-is (not urgent, per your note).
P2 (PAT to the third-party dispatch action): agreed it's a residual we can't remove in-workflow (the built-in token can't dispatch other repos). Flagging for secret-config review to move it to a fine-grained PAT scoped to the SDK repos + actions:write (or a per-run GitHub App installation token) — tracked as a follow-up, not in this workflow-cleanup PR.
pranavz28
left a comment
There was a problem hiding this comment.
Run regression once. I tried with content: read permissions, it doesnt work it seems.
|
RUN_REGRESSION |
The 'Output Current Job Log URL' step echoed steps.jobs.outputs.html_url but the step id is 'job-url' (the status target_url already uses steps.job-url), so it always printed empty. Point it at the right id. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
RUN_REGRESSION |
Claude Code PR ReviewPR: #2332 • Head: 53c174f • Reviewers: orchestrator inline (fresh clone — no in-repo reviewers) SummaryCI/supply-chain hardening of Review Table
FindingsNo Fail-level findings. Adversarial verification of the four hardening claims all passed: the cross-repo PAT is unreachable by an untrusted commenter (permission gate aborts before the trigger step, no
Verdict: PASS |
Summary
Hardens
.github/workflows/sdk-regression.yml(F-012, CWE-284/CWE-269). Thisissue_comment-triggered workflow injects the cross-repo dispatch PATWORKFLOW_DISPATCH_ACTIONS_TOKENinto more steps than strictly need it — including a third-party action — so a step compromise could exfiltrate a token that can dispatch workflows across otherpercy/*repos.The
issue_commenttrigger intent, the author-permission guard (write/admin collaborators only), and the regression-trigger flow are all unchanged.Actions SHA-pinned (verified)
All third-party action
uses:are pinned to full 40-char commit SHAs with a trailing# vX.Y.Zcomment. Each tag was re-resolved live against the GitHub API for this PR and confirmed correct (already pinned by an earlier hardening pass; no SHA changed):actions/github-scriptf891eff65186019cbb3f7190c4590bc0a1b76fbcxt0rted/pull-request-comment-branche8b8daa837e8ea7331c0003c9c316a64c6d8b0b1actions-ecosystem/action-regex-match9e6c4fb3d5e898f505be7a1fb6e7b0a278f6665bTiryoh/gha-jobid-actionbe260d8673c9211a84cdcf37794ebd654ba81eefwinterjung/split7f51d99e7cc1f147f6f99be75acf5e641930af88convictional/trigger-workflow-and-waitf69fa9eedd3c62a599220f4d5745230e237904bePermissions block
Top-level
permissions: contents: read(already present). Theregressionjob keeps its least-privilege grant and addsactions: readso the built-inGITHUB_TOKENcan read this run's job metadata:How the token injection was narrowed
WORKFLOW_DISPATCH_ACTIONS_TOKENis now injected into exactly one step — the only one that strictly requires it:convictional/trigger-workflow-and-wait— keeps the PAT. It dispatchestest.ymlin a differentpercy/*repo, which the built-inGITHUB_TOKENcannot do.Tiryoh/gha-jobid-action(third-party) — switched to${{ github.token }}. It only reads this run's job metadata onpercy/cli; the newactions: readgrant covers it. The cross-repo PAT is no longer handed to a third-party action.actions/github-scriptcommit-status steps — switched to${{ github.token }}. They only write commit statuses onpercy/cli, covered by the existing job-levelstatuses: write.No workflow/job-level
envexposed the token; the over-exposure was per-stepwith:inputs, which is what this PR narrows.🤖 Generated with Claude Code