Skip to content

Arbitrary code execution via pull_request_target fork checkout in pr.yml

High
LoricAndre published GHSA-9g93-rxr5-xhqw Apr 15, 2026

Package

skim (unapplicable)

Affected versions

<bf63404ad51985b00ed304690ba9d477860a5a75

Patched versions

bf63404ad51985b00ed304690ba9d477860a5a75

Description

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

  1. pull_request_target triggers on fork PRs and runs with access to base repo secrets
  2. The checkout uses head.ref + head.repo.full_name - this checks out the attacker's fork code, not the base branch
  3. cargo run compiles and executes this attacker-controlled Rust code
  4. The app installation token (from create-github-app-token) is stored in git credentials on the runner at /home/runner/work/_temp/git-credentials-*
  5. There are no if: conditions, label gates, or approval requirements on this job - any GitHub user can trigger it

Attack scenario

  1. Attacker forks the repo
  2. 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
  3. Attacker opens a PR - the workflow triggers immediately with no approval required
  4. 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:

  1. Push malicious code to a non-master branch
  2. Tag that commit as a new version (e.g. v4.5.2)
  3. release.yml triggers, builds poisoned binaries, then calls publish.yml which runs cargo publish with CRATES_IO_TOKEN
  4. 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

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
Required
Scope
Changed
Confidentiality
None
Integrity
High
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:N/I:H/A:N

CVE ID

CVE-2026-41414

Weaknesses

Improper Control of Generation of Code ('Code Injection')

The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment. Learn more on MITRE.

Credits