Summary
The generate-files job in .github/workflows/pr.yml checks out attacker-controlled fork code and executes it via cargo run, with access to SKIM_RS_BOT_PRIVATE_KEY and GITHUB_TOKEN (contents:write). No gates prevent exploitation - any GitHub user can trigger this by opening a pull request from a fork.
Details
The workflow uses pull_request_target, which runs in the context of the base repository with access to secrets. The generate-files job then explicitly checks out the fork's code and runs cargo run on it:
steps:
- uses: actions/create-github-app-token@v3
id: app-token
with:
app-id: ${{ vars.SKIM_RS_BOT_APP_ID }}
private-key: ${{ secrets.SKIM_RS_BOT_PRIVATE_KEY }}
- name: Checkout Git repo
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
token: ${{ steps.app-token.outputs.token }}
- name: Generate files
run: |
cargo run -- --man > man/man1/sk.1
cargo run -- --shell bash > shell/completion.bash
cargo run -- --shell zsh > shell/completion.zsh
cargo run -- --shell fish > shell/completion.fish
cargo run -- --shell nushell > shell/completion.nu
This is a known dangerous pattern: pull_request_target + fork checkout + code execution. See GitHub's security hardening guide and this blog post on pull_request_target risks.
Why this is vulnerable
pull_request_target triggers on fork PRs and runs with access to base repo secrets
- The checkout uses
head.ref + head.repo.full_name - this checks out the attacker's fork code, not the base branch
cargo run compiles and executes this attacker-controlled Rust code
- The app installation token (from
create-github-app-token) is stored in git credentials on the runner at /home/runner/work/_temp/git-credentials-*
- There are no
if: conditions, label gates, or approval requirements on this job - any GitHub user can trigger it
Attack scenario
- Attacker forks the repo
- Attacker modifies
src/bin/main.rs (or adds a build.rs) to read the git credential file and exfiltrate the app installation token to an attacker-controlled server
- Attacker opens a PR - the workflow triggers immediately with no approval required
cargo run executes the attacker's code, exfiltrating the token
Supply chain impact
The exfiltrated app installation token can push to non-protected branches and create tags. Since release.yml triggers on tag pushes and GitHub App token events do trigger other workflows (unlike GITHUB_TOKEN), an attacker could:
- Push malicious code to a non-master branch
- Tag that commit as a new version (e.g.
v4.5.2)
release.yml triggers, builds poisoned binaries, then calls publish.yml which runs cargo publish with CRATES_IO_TOKEN
- A poisoned crate is published to crates.io, affecting the 124 crates that depend on
skim
Additionally, if the GitHub App is installed on other skim-rs repos, the app private key grants write access to those repos as well.
Proof of concept
I verified this vulnerability by creating a replica repository with the same vulnerable github workflow which demonstrates how an attacker may exfiltrate secrets such as GITHUB_TOKEN. POC video that demonstrates the attack flow over the replica repository is available here
Summary
The
generate-filesjob in.github/workflows/pr.ymlchecks out attacker-controlled fork code and executes it viacargo run, with access toSKIM_RS_BOT_PRIVATE_KEYandGITHUB_TOKEN(contents:write). No gates prevent exploitation - any GitHub user can trigger this by opening a pull request from a fork.Details
The workflow uses
pull_request_target, which runs in the context of the base repository with access to secrets. Thegenerate-filesjob then explicitly checks out the fork's code and runscargo runon it:This is a known dangerous pattern:
pull_request_target+ fork checkout + code execution. See GitHub's security hardening guide and this blog post onpull_request_targetrisks.Why this is vulnerable
pull_request_targettriggers on fork PRs and runs with access to base repo secretshead.ref+head.repo.full_name- this checks out the attacker's fork code, not the base branchcargo runcompiles and executes this attacker-controlled Rust codecreate-github-app-token) is stored in git credentials on the runner at/home/runner/work/_temp/git-credentials-*if:conditions, label gates, or approval requirements on this job - any GitHub user can trigger itAttack scenario
src/bin/main.rs(or adds abuild.rs) to read the git credential file and exfiltrate the app installation token to an attacker-controlled servercargo runexecutes the attacker's code, exfiltrating the tokenSupply chain impact
The exfiltrated app installation token can push to non-protected branches and create tags. Since
release.ymltriggers on tag pushes and GitHub App token events do trigger other workflows (unlikeGITHUB_TOKEN), an attacker could:v4.5.2)release.ymltriggers, builds poisoned binaries, then callspublish.ymlwhich runscargo publishwithCRATES_IO_TOKENskimAdditionally, if the GitHub App is installed on other skim-rs repos, the app private key grants write access to those repos as well.
Proof of concept
I verified this vulnerability by creating a replica repository with the same vulnerable github workflow which demonstrates how an attacker may exfiltrate secrets such as GITHUB_TOKEN. POC video that demonstrates the attack flow over the replica repository is available here