April 26, 2026

The Bitwarden Supply Chain Attack Was Preventable (And Yours Might Be Too)

On April 22, 2026, Bitwarden's CLI package @bitwarden/cli@2026.4.0 shipped a malicious payload for 93 minutes. The vector wasn't a zero-day in Bitwarden's code. It was Checkmarx's ast-github-action inside Bitwarden's build pipeline.

The compromised build carried the signature "Shai-Hulud: The Third Coming" and exfiltrated GitHub tokens, npm tokens, SSH keys, .env files, and cloud credentials.

What happened

The attack chain was simple:

  1. An attacker compromised the Checkmarx ast-github-action GitHub Action (likely via a maintainer account takeover or tag hijack)
  2. Bitwarden's CI workflow referenced this action by tag (checkmarx/ast-github-action@v2), not by commit SHA
  3. When the compromised tag was pushed, every workflow using @v2 pulled the malicious version
  4. The malicious action ran with the workflow's full token scope — access to secrets, registries, and environments

93 minutes from publish to detection. In that window, every user who installed or updated the CLI got owned.

Why this was preventable

GitHub Actions referenced by tag (@v2, @main) are mutable. The tag can be force-pushed to point to any commit, including malicious code. This is not a new problem. GitHub documented the risk in their security hardening guide years ago.

The fix is a one-line change per action reference:

# Vulnerable — tag can be changed
- uses: checkmarx/ast-github-action@v2

# Safe — pinned to immutable SHA
- uses: checkmarx/ast-github-action@4d0e1f8c2a7b3e9f5c1d8a4b6e2f0c3d5a7b9e1f

Commit SHAs are immutable. An attacker can't retroactively change what a SHA points to.

The 15-minute CI audit

Run this in any repo with GitHub Actions workflows:

# Find every unpinned action reference
grep -rn 'uses:.*@' .github/workflows/ | \
  grep -v '@[a-f0-9]\{40\}' | \
  grep -v '@master\|@main'

What you're looking for:

What I found when I ran this

I recently scanned 15 GitHub Actions workflows in a YC W23 company's open-source repo (5,000+ stars). Results:

This is a popular, well-maintained project from a funded startup. The Bitwarden attack showed what happens when one of those unpinned actions turns hostile.

Automate the check

Don't trust manual audits. Add this to your CI:

# .github/workflows/action-pin-check.yml
name: Verify action pins
on: [pull_request]
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Check for unpinned actions
        run: |
          UNPINNED=$(grep -rn 'uses:.*@' .github/workflows/ | grep -v '@[a-f0-9]\{40\}' || true)
          if [ -n "$UNPINNED" ]; then
            echo "Unpinned actions found:"
            echo "$UNPINNED"
            exit 1
          fi

This fails any PR that adds an unpinned action reference. Zero cost. Zero maintenance. Catches the exact vector that hit Bitwarden.

The hard truth

Bitwarden did a lot right — fast detection, quick response, transparent disclosure. But the attack vector was a known, documented, solvable problem. Supply chain attacks via CI dependencies are not theoretical. They're happening, and they'll keep happening because most workflows still reference actions by mutable tags.

Pin your actions. It takes 15 minutes. The alternative is hoping your CI's transitive dependencies never get compromised.

← Back to alexreed.srht.site