On April 22, 2026, a malicious version of @bitwarden/cli was published to npm. The package stole CI secrets, SSH keys, cloud credentials, and GitHub tokens from every machine that installed it during a 93-minute exposure window. Most of the public coverage treats this as the Bitwarden incident. It isn't. The Bitwarden npm package was the last link in a chain that started a month earlier, in Checkmarx's own GitHub Actions.
If your CI pipeline uses Checkmarx/ast-github-action, Checkmarx/kics-github-action, or the checkmarx/kics Docker image, you are in the direct blast radius. And if your workflows depend on Bitwarden CLI through npm, you are in the downstream blast radius.
What happened
This was a two-wave supply chain attack by TeamPCP, the same threat actor behind the Trivy compromise five days earlier.
Wave 1: March 23, 2026, 12:58 to 16:50 UTC
Attackers used a compromised Checkmarx service account (cx-plugins-releases, GitHub user ID 225848595) to force-push malicious payloads to all 35 tags of Checkmarx/kics-github-action and at least one tag of Checkmarx/ast-github-action. The payload, injected into the action's setup.sh, was an infostealer that harvested CI secrets, SSH keys, cloud provider credentials, Kubernetes service account tokens, and GitHub tokens. On Kubernetes hosts with sufficient permissions, it also deployed a privileged pod in the kube-system namespace and installed a systemd-persistent Python backdoor.
Exfiltration went to checkmarx[.]zone (83.142.209.11) directly, with a fallback that created a public repository named docs-tpcp under the victim's own GitHub account and uploaded the stolen tarball as a release asset. If you see a repo by that name in your organization, you have confirmed exposure.
Checkmarx took the repositories down, rotated credentials, and published safe versions: kics-github-action v2.1.20 and ast-github-action v2.3.33.
Wave 2: April 22, 2026
The attackers still had access. One month later, Checkmarx disclosed a second wave that hit three places in the same day:
checkmarx/kicsDocker images were trojanized between 12:31 and 12:59 UTC. Affected tags includedv2.1.20,v2.1.20-debian,v2.1.21,v2.1.21-debian,alpine,debian, andlatest.Checkmarx/ast-github-actiontag2.3.35was re-compromised between 14:17 and 15:41 UTC. Safe version is nowv2.3.36.- Credentials harvested from these compromises were used to modify
.github/workflows/publish-cli.ymlinbitwarden/clientsand publish a malicious@bitwarden/cli 2026.4.0npm package.
The second-wave infrastructure is different: checkmarx[.]cx (91.195.240.123) and audit.checkmarx[.]cx (94.154.172.43), rather than checkmarx[.]zone. Payload style shifted from setup.sh to a Bun-based JavaScript loader (bw1.js) that memory-scrapes the GitHub Actions Runner.Worker process.
Who is affected
You are potentially affected if any of these appear in your pipelines:
Checkmarx/kics-github-actionat any tag prior tov2.1.20Checkmarx/ast-github-actionat any tag prior tov2.3.33, or specifically at tag2.3.35checkmarx/kicsDocker image pulled between April 22 2026 12:31 and 12:59 UTC@bitwarden/cliversion2026.4.0pulled from npm between April 22 2026 21:57 and 23:30 UTC- Any SHA pin whose commit was reachable from the tags above during either exposure window
The pipeline dependencies matter as much as the direct references. If a composite action or reusable workflow internally calls one of the compromised Checkmarx actions, your workflow files will not mention Checkmarx at all, but your runner still executes the malicious code.
How to check your pipelines
Grep is step one. It catches direct references:
grep -rE "Checkmarx/(kics|ast)-github-action|checkmarx/kics" .github/workflows/
But grep does not see through transitive action dependencies. A workflow that uses some-org/ci-toolkit@v2 gives no textual clue that ci-toolkit internally calls Checkmarx/kics-github-action. This is the gap we built abom to close.
# Install
go install github.com/julietsecurity/abom@latest
# Scan your repo and check against known-compromised actions
abom scan . --check
We shipped ABOM-2026-003 on April 24, 2026 covering both waves. If abom scan --check flags Checkmarx/ast-github-action or Checkmarx/kics-github-action anywhere in your dependency tree, direct or transitive, you will see the advisory ID, the affected window, and the remediation steps inline.
You can also run it as a CI gate to block PRs that introduce compromised actions:
- name: Check Actions supply chain
run: abom scan . --check --fail-on-warnings
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
For remote repositories you do not need to clone:
abom scan github.com/your-org/your-repo --check
What to do right now
Immediate:
- Update to safe versions.
Checkmarx/kics-github-action@v2.1.20,Checkmarx/ast-github-action@v2.3.36. Do not trust older tags: the 2.3.33 tag Checkmarx published in March has since been deleted, and the current safe version on the repository is 2.3.36. - Re-pin SHA references. A SHA pin that points at a malicious commit force-pushed to a tag during either window continues to execute attacker-controlled code even after Checkmarx rotated the tags. Replace old SHA pins with ones reachable from the current upstream refs.
- Rotate every secret accessible to affected CI pipelines. Cloud credentials (AWS, Azure, GCP), GitHub tokens, npm tokens, SSH keys, Kubernetes service account tokens, database credentials. Do not guess which secrets were in memory during the compromised run. Assume all of them.
- Check for exfiltration artifacts. Look for a public repository named
docs-tpcpin your GitHub organization. Audit Kubernetes clusters for unexpected privileged pods inkube-systemand forsysmon.servicesystemd units under/root/.config/systemd/user/. - Uninstall
@bitwarden/cli 2026.4.0from any developer workstation or CI runner. Downgrade to2026.3.0or move to Bitwarden's signed binary distribution.
Longer-term:
- Pin GitHub Actions to commit SHAs, not tags. Tags are mutable. Both waves of this attack rely on force-pushing tags to malicious commits. SHA pins prevent that specific class of compromise.
- Use
abom --verify-shasto catch SHA pins that no longer reach any ref in the upstream repository. A fork-only or force-pushed-away SHA still executes, but it points at code nobody is auditing. - Generate an ABOM on every PR and fail the build if a compromised action appears. The same way you block a critical CVE in an application dependency.
- Shift CI to short-lived credentials. OIDC federation for cloud providers, short-lived package registry tokens. Stop putting long-lived secrets in workflows.
The bigger picture
A security scanner was used to attack its own users. Then the stolen credentials were used to attack a password manager. The pattern is not going to stop. Security tools run with broad access to exactly the credentials an attacker most wants, and they are distributed as dependencies the way any library is: through GitHub Actions, Docker Hub, npm, PyPI.
The blind spot is not whether a given action is compromised. It is whether you can enumerate the actions your pipeline actually depends on, including the ones no workflow file mentions by name. That is what an ABOM is for.
abom shows what is in your pipeline. Juliet maps what is in your clusters: service accounts, secrets, workloads, network paths. Together they bracket the blast radius of a compromised CI action, from the line of YAML that pulls it all the way to the Kubernetes namespace it could reach.
Questions about this post or need help assessing your exposure? Reach us at contact@juliet.sh.
Get notified when we publish
No spam, no cadence — just an email when we have something worth reading.
Or subscribe via RSS