Skip to content

Arbitrary Code Execution via pull_request_target Fork Checkout in CI Workflow

Critical
thebentern published GHSA-mjx5-98jq-q736 Apr 20, 2026

Package

No package listed

Affected versions

N/A

Patched versions

None

Description

Summary

The main_matrix.yml workflow is triggered by pull_request_target (line 19) and multiple jobs check out the attacker's fork code and execute it with access to repository secrets and elevated GITHUB_TOKEN permissions.

No approval gate exists - I verified that PRs from external users with author_association: "NONE" triggered the CI workflow automatically.

This is not a command injection vulnerability - the workflow directly executes attacker-controlled files from the fork checkout.
Note: I reproduced the vulnerability in a replica environment. I did not attempt to exploit this on the meshtastic/firmware repository. POC video available here.

Vulnerable Flows

Flow 1 - build-debian-src job (via build_debian_src.yml lines 27-42) - Supply Chain (GPG Key)
Called with secrets: inherit, which passes all repository secrets. Checks out the fork and runs mk-build-deps against attacker-controlled debian/control, with PPA_GPG_PRIVATE_KEY available in the environment.

Impact: An attacker can exfiltrate PPA_GPG_PRIVATE_KEY - the GPG key used to sign Debian packages for the Meshtastic PPA - and sign and distribute malicious firmware packages to all users who install via apt.

Flow 2 - check job (main_matrix.yml lines 87-98) - Self-Hosted Runner
Checks out the fork and runs meshtastic/gh-action-firmware@main, a Docker action that executes /workspace/bin/check-all.sh from the checked-out code. Runs on self-hosted arctastic runners.

Impact: Attacker code executes on persistent self-hosted runners. Unlike ephemeral GitHub-hosted runners, backdoors can survive between workflow runs, access cached credentials, or pivot to internal network resources.

Flow 3 - build job (via build_firmware.yml lines 26-34) - Self-Hosted Runner
Same fork checkout pattern, also on self-hosted arctastic runners. Executes meshtastic/gh-action-firmware@main which runs attacker-controlled build scripts.

Flow 4 - gather-artifacts job (main_matrix.yml lines 174-178)
Checks out the fork with explicit permissions: contents: write, pull-requests: write. This grants the attacker a GITHUB_TOKEN that can push commits directly to any branch (including master) and modify releases, enabling repository takeover.

PoC

  1. Attacker forks meshtastic/firmware
  2. Replaces bin/check-all.sh (or any build script) with a malicious payload
  3. Opens a pull request to meshtastic/firmware
  4. The pull_request_target workflow triggers automatically - no approval required
  5. The workflow checks out the attacker's fork and executes the malicious code with access to all repository secrets

Impact

Supply Chain Compromise: PPA_GPG_PRIVATE_KEY - the GPG key used to sign Debian packages for the Meshtastic PPA. An attacker could sign and distribute malicious firmware packages to all users who install Meshtastic via apt. DIST_PAGES_DEPLOY_KEY - deploy key for meshtastic/meshtastic.github.io, used to publish firmware binaries (see publish-firmware job, line 482). An attacker could replace legitimate firmware downloads with backdoored versions.

Self-Hosted Runner Compromise: The check and build jobs run on self-hosted arctastic runners. These are persistent machines - an attacker could install backdoors that survive between workflow runs, access cached credentials, or pivot to internal network resources. GitHub explicitly warns against using self-hosted runners with public repositories for this reason.

Repository Takeover: The gather-artifacts job grants a GITHUB_TOKEN with contents: write permissions, allowing an attacker to push commits to any branch (including master), create or modify releases, and take full control of the repository.

Note that a previous advisory addressed a command injection via github.head_ref in the setup job of the same workflow. That fix correctly moved to environment variables. However, the more critical fork checkout vulnerability across the check, build, and build-debian-src jobs was not addressed.

Severity

Critical

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
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:N/S:C/C:H/I:H/A:N

CVE ID

CVE-2026-44359

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.

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