Skip to content

Security: CI command injection via github.ref_name in release workflow #325

Description

@coderabbitai

Summary

The release workflow embeds ${{ github.ref_name }} directly inside a run: Bash block. GitHub Actions expression expansion occurs before the shell parses the script, so a syntactically valid tag name containing shell metacharacters — e.g. v1.2.$(id) — causes command substitution to execute on the runner. The vulnerable step runs with contents: write and exposes GITHUB_TOKEN/GH_TOKEN, so any user able to push a matching tag could execute arbitrary commands in the release job, exfiltrate the token during its lifetime, or tamper with release publication.

Affected commit: 45e3bde
Reported via: PR #323

Evidence

  • .github/workflows/release.yml lines 4–7: workflow triggers on tag pushes matching v*.*.*; this glob pattern matches tag names that include $() shell metacharacters.
  • Lines 118–121: the release job is granted contents: write.
  • Lines 129–142: ${{ github.ref_name }} is interpolated directly into a Bash run: script while GITHUB_TOKEN and GH_TOKEN are set as environment variables.

Validation confirmed:

  • git check-ref-format refs/tags/v1.2.$(printenv>leaked_env) returns success — the crafted tag is accepted by Git ref syntax.
  • A local emulation of the vulnerable step executed the command substitution and produced a leaked_env file containing the fake token values; the safe env-var variant did not.

Attack Path

  1. Attacker (or compromised trusted account) creates/pushes a tag matching v*.*.* whose name includes shell metacharacters.
  2. The release job triggers; GitHub Actions expands ${{ github.ref_name }} into the Bash script before the shell parses it.
  3. Command substitution executes on the GitHub Actions runner in a step that holds GITHUB_TOKEN/GH_TOKEN and contents: write.
  4. Attacker can exfiltrate the ephemeral token, create or tamper with releases, or inject malicious artifacts into the release pipeline.

Severity

High — exploitation requires repository tag-push capability or a compromised trusted account; the token appears scoped to this repository rather than broader organisation credentials. Impact is nonetheless high: arbitrary runner command execution, release integrity compromise, and potential supply-chain risk for consumers of published release artefacts.

Recommended Fix

Pass the tag name through an environment variable and reference it as "$TAG_NAME" in the shell script rather than interpolating the Actions expression directly. Optionally, add strict validation (e.g. a regex guard) before any shell use of the tag name.

Controls Already Present

  • Repository authentication is required to push tags.
  • Top-level workflow permissions are contents: read; the release job explicitly elevates.
  • Shared actions are pinned to commit SHAs.
  • Pull-request dry-run workflow sets contents: read and passes dry-run: true.

Blindspots

  • Live GitHub tag protection/ruleset configuration was not confirmed via static review.
  • Downstream adoption of published release artefacts determines the full supply-chain blast radius.

Reported by @leynos.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions