Skip to content

build: require pinned PR head for benchmark comparisons#69841

Open
itsonlylock wants to merge 1 commit into
angular:mainfrom
itsonlylock:fix-benchmark-compare-head-sha-live-rebased
Open

build: require pinned PR head for benchmark comparisons#69841
itsonlylock wants to merge 1 commit into
angular:mainfrom
itsonlylock:fix-benchmark-compare-head-sha-live-rebased

Conversation

@itsonlylock

@itsonlylock itsonlylock commented Jul 18, 2026

Copy link
Copy Markdown

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • angular.dev application / infrastructure changes
  • Other... Please describe:

What is the current behavior?

Issue Number: N/A

This fixes an approval-to-checkout TOCTOU where an external fork author can cause unreviewed fork-controlled code to execute in Angular's privileged issue_comment benchmark workflow after a team member's /benchmark-compare comment. The workflow is currently triggered by a trusted commenter, but it resolves the PR head SHA at runtime rather than pinning the authorization to the commit the commenter saw.

Because the job installs the checked-out fork tree with pnpm install --frozen-lockfile and does not disable lifecycle scripts, a stale approval can allow attacker-controlled preinstall code to execute in the privileged workflow and capture repository secrets delivered to later steps, including the workflow token used by the final comment-update step.

The current vulnerable sequence is:

  1. A fork PR is at benign head commit X.
  2. An Angular team member comments /benchmark-compare ..., intending to run the benchmark for X.
  3. Before the workflow resolves/checks out the PR head, the fork author pushes commit Y.
  4. The workflow resolves the current PR head at runtime and checks out/runs Y in the privileged benchmark workflow.

What is the new behavior?

This PR makes benchmark comparison approval commit-bound.

The workflow now requires /benchmark-compare comments to include the current PR head SHA:

/benchmark-compare <target> sha=<pr-head-sha>

If the command is missing sha=..., the workflow stops before checkout/install/benchmark execution and posts a ready-to-use confirmation command containing the current PR head SHA.

If the command includes a SHA that no longer matches the current PR head, the workflow stops before checkout/install/benchmark execution and posts an updated confirmation command for the new head SHA.

Only when the requested SHA matches the current PR head does the workflow continue to checkout and benchmark execution. This prevents a post-approval PR head update from authorizing an unintended commit.

Does this PR introduce a breaking change?

  • Yes
  • No

This changes the benchmark chatops confirmation flow, but it does not affect Angular package APIs or application behavior.

Other information

Expected reviewer-visible behavior:

  • First unpinned /benchmark-compare ... comment: workflow posts the exact pinned command to rerun.
  • Matching pinned command: workflow proceeds.
  • Stale pinned command after a force-push: workflow refuses to run the old approval and posts a fresh pinned command for the current head.

Threat model

  • Attacker: external fork author who can push commits to their fork after a trusted team member posts /benchmark-compare.
  • Impact: attacker-controlled lifecycle scripts can execute in the privileged issue_comment job.
  • Confirmed: the workflow installs the fork workspace with lifecycle scripts enabled.

CVSS v4.0: CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:A/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/AU:A = 8.9 (High)
CVSS v3.1: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N = 9.3 (Critical)

(Updated the CVSS v4.0 vector to correct the User Interaction metric. I had forgotten to set UI, so the calculator defaulted to UI:N.)

Validation

  • Reproduced locally and in a hosted replica using sentinel-only secrets, including capture of fake sentinel tokens in the privileged workflow.
  • Confirmed the workflow token capture chain is reachable through attacker-controlled lifecycle execution.
  • Ran actionlint .github/workflows/benchmark-compare.yml
  • Ran git diff --check
  • Tested the confirmation flow locally with Git Bash for unpinned, matching pinned, stale pinned, uppercase SHA, and multiline comment cases
  • Verified the benchmark command parser still extracts the compare ref and target from the pinned command format

Mitigation rationale

This PR pins /benchmark-compare approvals to an explicit commit SHA to close the TOCTOU window.

Additional reproduction artifacts, and validation evidence are available upon request.

@pullapprove
pullapprove Bot requested a review from josephperrott July 18, 2026 15:53
@angular-robot angular-robot Bot added the area: build & ci Related the build and CI infrastructure of the project label Jul 18, 2026
@ngbot ngbot Bot added this to the Backlog milestone Jul 18, 2026
Require benchmark comparison comments to include the current PR head SHA before checking out and running PR code.
@itsonlylock
itsonlylock force-pushed the fix-benchmark-compare-head-sha-live-rebased branch from 375c038 to 68ee50a Compare July 20, 2026 12:54
@itsonlylock

Copy link
Copy Markdown
Author

Hi @josephperrott

Just checking in to see if there’s anything I can clarify for the review. I also updated the CVSS v4.0 vector in the PR description (I had forgotten to set the UI metric, so it defaulted to UI:N). For context, this fix comes from a report submitted through Google’s OSS VRP intake, per this repo’s SECURITY.md.

Thanks again for taking a look when you have time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: build & ci Related the build and CI infrastructure of the project

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant