April 26, 2026
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.
The attack chain was simple:
ast-github-action GitHub Action (likely via a maintainer account takeover or tag hijack)checkmarx/ast-github-action@v2), not by commit SHA@v2 pulled the malicious version93 minutes from publish to detection. In that window, every user who installed or updated the CLI got owned.
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.
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:
@v2, @v1.3) — the primary risk. Replace with SHA pins.@main) — mutable and risky, but at least you know who maintains the branch.pull_request_target — allows fork PRs to access secrets. Extremely dangerous if the workflow runs untrusted code.persist-credentials: false on actions/checkout — leaves a GitHub token in .git/config for later steps to exfiltrate.I recently scanned 15 GitHub Actions workflows in a YC W23 company's open-source repo (5,000+ stars). Results:
@v2 or @v3pull_request_target workflows with secrets accessactions/checkout steps missing persist-credentials: falseThis is a popular, well-maintained project from a funded startup. The Bitwarden attack showed what happens when one of those unpinned actions turns hostile.
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.
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.