Skip to content

Back to the catalog

codfish/semantic-release-action GitHub Action has been compromised
stepsecurity.iowriteup

codfish/semantic-release-action GitHub Action has been compromised

Step Security's incident analysis of the compromise of codfish/semantic-release-action, a widely used GitHub Action wrapping semantic-release. An attacker force-pushed a malicious commit and repointed seven version tags to it, converting the action from a Docker runner to a composite action that installs Bun and runs an obfuscated 512KB payload via if: always() steps. The payload steals GitHub OIDC tokens and PATs, uses a novel GitHub commit-message dead-drop for C2, has a Russian-locale killswitch, and locked tags immutable via GitHub Rulesets to block remediation.

TL;DR Attacker hijacked mutable git tags on codfish/semantic-release-action to inject a Bun-based credential-stealing payload using GitHub commit messages as a C2 dead-drop.
Document

What it is

Step Security's forensic analysis of the June 24, 2026 supply-chain compromise of codfish/semantic-release-action. At 15:39:06 UTC the attacker force-pushed commit 6b9501e1889cc45c91726729610cf69c2442b8c5 and repointed seven version tags (including the rolling v5) to it. The action was flipped from a Docker runner to a composite action that installs Bun and runs a ~512 KB obfuscated index.js payload.

How it works

The malicious action.yml keeps the legitimate semantic-release step, then appends two steps both guarded with if: always() so the payload fires even if the release step fails or is skipped:

    - name: Setup Bun
      uses: oven-sh/setup-bun@v2
      if: always()
    - name: Run
      shell: bash
      if: always()
      run: bun run ${{ github.action_path }}/index.js

Bun was chosen deliberately because it lacks the --require hook interception most Node.js security tooling relies on. The payload runs two guards first: a Russian-locale killswitch (checks Intl.DateTimeFormat().resolvedOptions().locale and LANG/LC_ALL/LANGUAGE; halts if locale starts with ru). It then steals OIDC tokens and PATs and encrypts the loot with AES-128-GCM.

The C2 is a GitHub commit-message dead drop: the payload searches the commit API for messages matching RevokeAndItGoesKaboom:<BASE64>, where the base64 is an operator Fine-Grained PAT encrypted with AES-256-CBC under a hardcoded key. It validates candidate tokens and picks the one with the most rate limit remaining — making C2 traffic indistinguishable from normal GitHub API use and immune to domain blocking.

Preconditions / Affected

Any workflow referencing an affected mutable tag (e.g. uses: codfish/semantic-release-action@v2) that ran after 15:39:06 UTC on June 24, 2026. Tag references resolve at runtime, so the hijack executes silently with no notification to the workflow author.

What to do

  • Audit CI logs for runs of this action after the compromise timestamp; treat any OIDC tokens and PATs exposed to those runners as compromised and rotate them.
  • The page names one clean path: the legitimate v5 branch and its GHCR image remain unaffected. Pin to a verified commit SHA rather than a tag.
  • If you maintain the repo: the attacker used GitHub Repository Rulesets to make the hijacked tags immutable, so you cannot force-push clean commits until you locate and disable the attacker-created rulesets first.

Caveats

Analysis was in progress at publication. Full deobfuscation was incomplete and the C2 exfiltration endpoint remained encoded beyond the analysis window. Capabilities described are drawn from static analysis of identifiable patterns and partially decoded strings, not a complete decode; the post was to be updated as work continued.

Organisations named here

  • RevokeAndItGoesKaboom

Technologies named here

  • GitHub Actions
  • Bun
  • Node.js
  • git
  • codfish/semantic-release-action
  • oven-sh/setup-bun
  • GitHub repository rulesets
  • semantic-release