# PR metadata reaching privileged CI secrets

Untrusted PR title, branch name, or head ref flowing into privileged workflow shell or checkout

_Area: Software supply chain and CI/CD. Seen in 3 disclosed reports; one confirmed bounty: $7,500; latest 2025-08-27. Updated 2026-10-23._

## How it works
A workflow runs with secrets or a write token because of a privileged trigger (`pull_request_target`, or a cloud build attached to an IAM role). That privilege is only safe if nothing attacker-controlled reaches the privileged step. The assumption that breaks: that PR metadata (title, branch name, head ref, base ref) is inert data. In practice these strings are interpolated into shell commands or used to resolve a checkout target, so the attacker controls part of the command line or the code that gets executed. The trust boundary between "untrusted PR author" and "privileged job" is crossed without any sanitization.

## Where it shows up
- `pull_request_target` or other privileged triggers combined with `${{ github.event.pull_request.title }}`, branch names, or head refs inside `run:` blocks.
- Checkout steps that resolve a ref or base branch supplied by the PR rather than a pinned SHA.
- Cloud build configs (`buildspec.yml`, `cloudbuild.yml`, `azure-pipelines.yml`) triggered directly by fork PRs with an attached service role.
- Build containers able to reach instance metadata endpoints (169.254.169.254 / 169.254.170.2).
- Post-install scripts in build tooling that run with the job's token.

## How to review
Trace every privileged job and ask: can any value in a `run:` step or checkout target be influenced by the PR author? The invariant: untrusted PR fields must never be interpolated into shell or used to select code; pass them via environment variables and quote them, or avoid them entirely. Checkout must pin an immutable SHA, not a mutable ref or a name that could resolve to a hash. For cloud builds, confirm fork PRs require approval and that the build role is least-privilege and IMDS is blocked. Confirm the token is read-only where possible.

## Variants seen
- Deleted base branch name resolving to a commit shorthash, letting a closed PR reopen against an attacker commit and run `pull_request_target` with secrets [E1](https://bugbountylibrary.com/r/blog%3Ablog-teddykatz-com-2022-02-23-ghosts-of-branches-past-html.md).
- Unsanitized PR title injected into a `pull_request_target` run step, yielding a write token used to publish malicious npm versions [E2](https://bugbountylibrary.com/r/blog%3Awiz-io-blog-s1ngularity-supply-chain-attack.md).
- Fork PRs auto-triggering cloud builds whose role credentials are reachable via IMDS [E3](https://bugbountylibrary.com/r/blog%3Adivyanshu-mehta-gitbook-io-researchs-hijacking-cloud-ci-cd-systems-for-fun-and-profit.md).

## Impact and severity
High: repo secrets and write tokens leaked, malicious packages published, 5500+ private repos exposed [E2](https://bugbountylibrary.com/r/blog%3Awiz-io-blog-s1ngularity-supply-chain-attack.md), and full cloud account compromise via build-role STS tokens [E3](https://bugbountylibrary.com/r/blog%3Adivyanshu-mehta-gitbook-io-researchs-hijacking-cloud-ci-cd-systems-for-fun-and-profit.md). Severity rises with token scope, secret reach, and auto-triggering without approval.

## Evidence
- E1: https://bugbountylibrary.com/r/blog%3Ablog-teddykatz-com-2022-02-23-ghosts-of-branches-past-html.md
- E2: https://bugbountylibrary.com/r/blog%3Awiz-io-blog-s1ngularity-supply-chain-attack.md
- E3: https://bugbountylibrary.com/r/blog%3Adivyanshu-mehta-gitbook-io-researchs-hijacking-cloud-ci-cd-systems-for-fun-and-profit.md
