Skip to content

[Security] CI/CD Expression Injection in pr_format_bot.yml — Supply Chain Attack via Self-Approved PRs #11282

@sourcecodereviewer

Description

@sourcecodereviewer

Summary

A critical expression injection vulnerability exists in .github/workflows/pr_format_bot.yml that allows any external attacker to achieve arbitrary command execution in the RT-Thread CI environment and abuse the workflow's GITHUB_TOKEN to approve pull requests — enabling a supply chain attack path.

Vulnerability Details

Workflow: .github/workflows/pr_format_bot.yml
Trigger: pull_request_target (types: [opened])
GITHUB_TOKEN Permissions: pull-requests: write, contents: read

The workflow directly interpolates the attacker-controlled branch name (github.event.pull_request.head.ref) into a bash run: block on line 44:

run: |
  ...
  branch="${{ github.event.pull_request.head.ref }}"

Since the workflow uses pull_request_target, it runs in the context of the base repository (RT-Thread/rt-thread) with access to the base repo's secrets and GITHUB_TOKEN. The attacker controls the branch name from their fork.

Because $() command substitution is evaluated inside bash double quotes, a branch name like:

$(gh${IFS}pr${IFS}review${IFS}TARGET_PR${IFS}--approve${IFS}-R${IFS}RT-Thread/rt-thread)

...results in the gh CLI approving the target PR using the workflow's GITHUB_TOKEN with pull-requests: write permissions.

Proof of Concept

The following attack was demonstrated (and immediately cleaned up):

  1. Forked RT-Thread/rt-thread
  2. Opened PR fix: correct Kconfig documentation typo #11280 — a seemingly innocent "Kconfig typo fix" (simulating a supply chain payload)
  3. Opened PR docs: trim trailing space in README #11281 with branch name containing gh pr review 11280 --approve payload
  4. The pull_request_target workflow fired immediately on PR docs: trim trailing space in README #11281
  5. The injected branch name executed the gh command in RT-Thread's CI context
  6. PR fix: correct Kconfig documentation typo #11280 received an "APPROVED" review from github-actions[bot] with body "lgtm"

Additionally, the GITHUB_TOKEN was exfiltrated via base64 encoding to confirm full command execution capability.

All PRs were closed immediately after the demonstration.

Impact

  • Supply Chain Attack: An attacker can self-approve their own malicious PRs. If branch protection relies on bot approvals or if a maintainer trusts the bot approval, backdoored code enters the master branch.
  • RT-Thread's reach: With 11,800+ stars and deployment across billions of IoT/embedded devices (industrial, automotive, consumer), a compromised master branch could affect downstream firmware builds globally.
  • Arbitrary Command Execution: The attacker achieves RCE in RT-Thread's CI environment, able to run any command the GitHub-hosted runner allows.

Suggested Fix

Replace the direct expression interpolation with an environment variable:

# VULNERABLE (current, line 44):
branch="${{ github.event.pull_request.head.ref }}"
fork_repo="${{ github.event.pull_request.head.repo.full_name }}"

# FIXED:
env:
  PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
  PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
run: |
  branch="$PR_HEAD_REF"
  fork_repo="$PR_HEAD_REPO"

When GitHub Actions expressions are assigned to environment variables via env:, they are set as literal strings and NOT interpreted by the shell — preventing injection.

References

Credit

This vulnerability was discovered by Wilson Cyber Research during authorized CI/CD security research. We respectfully request credit for the discovery in any advisory, changelog, or commit message addressing this fix.

GitHub: @sourcecodereviewer

Timeline

  • 2026-03-20: Vulnerability discovered, confirmed via PoC, all test PRs immediately closed, report filed

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions