CyberBytes Daily

Trending cyberattacks, explained simply.

supply chain attacks

How a compromised security scanner let attackers publish a credential-stealing worm under Bitwarden's own name

The package that landed in developers' projects on April 22, 2026 looked exactly like the official Bitwarden command-line tool. It came from the official @bitwarden scope on npm. It was published by Bitwarden's own automated build pipeline. It passed every trust signal a developer or security tool would check. And it was stealing SSH keys, cloud credentials, and AI assistant tokens within seconds of installation, before a single line of Bitwarden's own code had been touched.

The attacker never broke into Bitwarden directly. They broke into a third-party security scanning tool that Bitwarden's build pipeline trusted, and used that foothold to silently rewrap the legitimate Bitwarden CLI with a credential-stealing worm before it was published. The result was a package that was simultaneously genuine and malicious: real Bitwarden code, real Bitwarden signature, real Bitwarden distribution channel, with a hidden layer added by an attacker who never needed to touch Bitwarden's own systems.

The most alarming detail is not the credential theft. It is what happened next. Once the worm found npm tokens on a developer's machine, it automatically unpacked every npm package that developer had publish rights to, injected itself into each one, and republished them to their downstream consumers, without any further action from the attacker. A single developer who ran npm install @bitwarden/cli during the 93-minute exposure window could have become an unwitting distribution node for the same worm, pushing it to every project that depended on their packages.

Narrative · 6 min read

The Context

Bitwarden is one of the most widely used open-source password managers in the world, serving over 10 million users and 50,000 business customers. Its command-line interface (CLI), distributed as @bitwarden/cli on the npm registry, is used by developers and IT teams to automate credential management in scripts and build pipelines. The package had over 78,000 weekly downloads at the time of the attack.

Checkmarx is a cybersecurity company that makes application security testing tools. Many software teams, including Bitwarden, use Checkmarx's GitHub Actions in their build pipelines to automatically scan code for vulnerabilities.

The Attack, Phase by Phase

Phase 1: Upstream Breach of Checkmarx Tooling

The attack began not at Bitwarden but at Checkmarx. In March 2026, a threat actor tracked as TeamPCP compromised Checkmarx's GitHub Actions infrastructure, injecting credential-stealing code into two widely used actions: checkmarx/ast-github-action and checkmarx/kics-github-action. They also trojanized Checkmarx's KICS Docker images. Checkmarx disclosed this publicly on March 23, 2026.

By compromising the scanner, TeamPCP gained a foothold inside the build pipelines of every organization using these tools—including Bitwarden—without ever attacking those organizations directly.

ATTACKER ACTIONS: MARCH 2026🎯1Breach Checkmarx infrastructureGain access to CI/CD credentials💉2Inject infostealers into ActionsPoison ast-github-action and kics-action🐳3Trojanize KICS Docker imagesBackdoor container used in CI runnersCHECKMARX DISCLOSES MARCH 23, 2026⚠️Silent footholdAny org using these tools is now exposedBitwarden's build pipeline referenced checkmarx/ast-github-action, inheriting this exposure.

Phase 2: Pipeline Injection and Malicious Package Publication

Bitwarden's automated release pipeline, defined in publish-ci.yml in the bitwarden/clients repository, referenced the compromised checkmarx/ast-github-action. On April 22, 2026, attackers used harvested credentials to abuse this pipeline step.

They did not rewrite Bitwarden's code. Instead, they added a hidden layer on top of the legitimate CLI 2026.3.0 codebase: a bootstrapper script called bw_setup.js and a 9.7-megabyte obfuscated payload called bw1.js. The result was published to npm as @bitwarden/cli@2026.4.0 under Bitwarden's own trusted scope. A forensic clue confirmed the tampering: the outer version said 2026.4.0, but the version embedded inside the application code still said 2026.3.0, because the malicious layer was added after the build.

The package was live at 5:57 PM ET. Bitwarden contained the incident at 7:30 PM ET. The exposure window was 93 minutes.

ATTACKER ACTIONS: APRIL 22, 2026🔑1Use harvested CI credentialsAbuse publish-ci.yml pipeline step📦2Wrap legitimate CLI 2026.3.0Add bw_setup.js and bw1.js payload🚀3Publish as @bitwarden/cli@2026.4.0Under Bitwarden's trusted npm scopeLIVE 5:57 PM ET, CONTAINED 7:30 PM ET (93 MINUTES)📥78,000 weekly download baseThousands of installs during windowThe outer version said 2026.4.0; the embedded app code still said 2026.3.0, confirming external tampering.

Phase 3: Credential Harvesting and Multi-Channel Exfiltration

When a developer ran npm install @bitwarden/cli, the malicious package.json triggered a preinstall hook that silently downloaded the Bun JavaScript runtime from GitHub's official servers and used it to execute bw1.js. The malware ran both on install and on every subsequent use of the bw command.

The payload swept SSH private keys, AWS and Google Cloud credentials, npm authentication tokens, Claude and Kiro AI assistant configuration files, Git credentials, .env files, and shell history. It also called cloud secret manager APIs directly using whatever credentials it found, pulling secrets from AWS, Azure, and Google Cloud vaults.

Stolen data was encrypted and sent to a domain impersonating Checkmarx: audit.checkmarx.cx. If that channel failed, the malware created a public GitHub repository under the victim's own account using stolen GitHub tokens, committed the encrypted data there with messages like LongLiveTheResistanceAgainstMachines, and named repositories using Dune-universe vocabulary such as fremen-sandworm-441. OX Security confirmed real user data was exfiltrated before containment.

ON VICTIM MACHINE: INSTALL TIME AND RUNTIME⬇️1Preinstall hook firesDownloads Bun runtime from GitHub🗂️2Sweep credential filesSSH, AWS, GCP, npm, Claude, Kiro, .env☁️3Query cloud secret managersAWS SSM, Secrets Manager, Azure KV, GCP📤4Primary exfiltrationPOST to audit.checkmarx.cx/v1/telemetry🐙5Fallback: GitHub dead-dropCommit payload to victim's own accountThe malware did not execute if the Russian locale was configured on the host machine.

Phase 4: Worm Propagation Across the npm Ecosystem

Using stolen npm tokens, the malware enumerated every npm package the victim had rights to publish. For each one, it downloaded the current version, injected a new preinstall dropper, bumped the patch version, and republished using Bun's native publish capability. No further attacker involvement was required. Each infected developer became a new distribution node.

Simultaneously, stolen GitHub tokens injected a malicious workflow named "Formatter" into every repository the token could write to. The workflow extracted the entire CI/CD secrets context, saved it as a downloadable artifact, then deleted the branch and workflow run to erase evidence.

SELF-PROPAGATION: NO ATTACKER INVOLVEMENT REQUIRED🔑1Use stolen npm tokenEnumerate all publishable packages💉2Inject dropper into each packageBump patch version, republish via Bun🐙3Use stolen GitHub tokenInject Formatter workflow into every repo🗑️4Extract secrets, then eraseDownload artifact, delete branch and run🌐Downstream consumers infectedEvery package dependent on victim's🔓CI/CD secrets extractedFrom every repo the token could reachA single infected developer could propagate the worm to thousands of downstream projects without any further attacker action.

What Made This Possible

  1. Mutable third-party actions in the build pipeline. Bitwarden's publish-ci.yml referenced checkmarx/ast-github-action by a tag rather than a fixed commit hash. Tags are mutable: the repository owner can change what a tag points to at any time. When Checkmarx was compromised, the tag pointed to attacker-controlled code, and Bitwarden's pipeline ran it with full publishing credentials.

  2. Elevated credentials available to pipeline steps. The build pipeline had authority to publish under the @bitwarden scope. When an attacker controlled a step in that pipeline, they inherited that authority with no distinction between a legitimate build step and a compromised one.

  3. No independent verification of the published artifact. The package that reached npm was never compared against what Bitwarden's engineers had actually built. No checksum comparison, no reproducible build verification, no out-of-band confirmation. The pipeline's output was trusted by definition.

What Should Have Stopped This

  • Pin third-party actions to a specific commit hash. A pinned hash cannot be silently changed. If checkmarx/ast-github-action had been pinned, the compromised tag would have had no effect on Bitwarden's pipeline.
  • Separate publishing credentials from build credentials. The npm token that could publish under @bitwarden should not have been available to every pipeline step. A dedicated, isolated publishing step with a scoped token limits what an attacker can do even controlling an earlier step.
  • Reproducible builds with independent verification. Comparing the pipeline's artifact against an independently built reference would have caught the version mismatch (2026.4.0 outer, 2026.3.0 inner) automatically.
  • Audit preinstall hooks before installation. Tools that inspect package.json for preinstall scripts before running npm install would have flagged the unexpected bw_setup.js hook in a package that had never previously used one.

The Takeaway

This attack follows the same pattern as the Stryker Intune wipe and the Axios build-time injection: a trusted administrative tool was weaponized against the organization it was built to protect. In the Stryker case, it was a device management platform. In the Axios case, a build-time dependency. Here, a security scanner embedded in the build pipeline. The meta-pattern: systems fail when they grant elevated authority to a boundary the attacker controls.

What makes this incident distinct is propagation. Previous supply chain attacks required the attacker to maintain active infrastructure to spread. This one built a self-replicating distribution network out of the victims themselves, using their own publishing credentials, their own trusted package names, and their own downstream relationships. The attacker's involvement ended the moment the first developer installed the package.

Pattern to remember: A build pipeline's security posture is determined by the least-trusted tool it invokes with elevated credentials, not by the security of the vendor's own code.

What changed: Supply chain attacks can now self-propagate through the npm ecosystem using victims' own publishing identities, requiring no ongoing attacker infrastructure after the initial infection.

Technical Deep Dive · 4 min

The Technical Mechanism

The attack chain begins with a tag-based reference to a compromised GitHub Action. Bitwarden's publish-ci.yml workflow in github.com/bitwarden/clients invoked checkmarx/ast-github-action using a mutable tag. After TeamPCP compromised Checkmarx's infrastructure in March 2026, this tag resolved to attacker-controlled code with access to the workflow's environment, including the NPM_ACCESS_TOKEN secret scoped to publish under @bitwarden.

The injected packaging step wrapped the legitimate Bitwarden CLI 2026.3.0 build artifacts with two new files. bw_setup.js (132 lines, plaintext) served as the bootstrapper: it detected the victim's OS and architecture, downloaded Bun runtime 1.3.13 from github.com/oven-sh/bun/releases if not already present, and executed bw1.js. Using Bun rather than Node.js was a deliberate evasion choice: it moved execution off the expected Node.js code path and gave the attacker access to Bun-specific APIs for decompressing embedded payloads, while keeping bw_setup.js itself clean enough to pass casual review.

bw1.js is a 9.7 MB Bun bundle obfuscated via obfuscator.io with a 43,436-entry string lookup table and a seeded pseudo-random scramble table protecting the most sensitive strings, including the C2 domain. The payload executed six capability blobs:

  1. Local credential harvester: Targeted ~/.ssh/id*, ~/.aws/credentials, ~/.config/gcloud/credentials.db, ~/.npmrc, ~/.claude.json, ~/.claude/mcp.json, ~/.kiro/settings/mcp.json, .git/config, .git-credentials, .env, and shell histories. Also extracted the GitHub CLI's cached authentication token via gh auth token, catching tokens stored in system keychains rather than on disk.

  2. Cloud secret manager collector: Called AWS SSM Parameter Store, AWS Secrets Manager, Azure Key Vault, and GCP Secret Manager APIs directly using ambient credentials found in step 1.

  3. Exfiltration packager: Encrypted all stolen data with AES-256-GCM using a 32-byte random session key, then RSA-2048 OAEP encrypted the session key with the attacker's public key, bundled as tpcp.tar.gz. The same RSA key and archive format appear in prior TeamPCP operations against LiteLLM and telnyx.

  4. Primary C2 channel: HTTP POST to audit.checkmarx.cx/v1/telemetry (resolving to 94.154.172.43), a domain impersonating the legitimate Checkmarx brand.

  5. GitHub dead-drop fallback: If the primary channel failed, the malware used stolen GitHub Personal Access Tokens (PATs) to create a new public repository under the victim's own account, commit tpcp.tar.gz with messages following the pattern LongLiveTheResistanceAgainstMachines, and name the repository using randomized Dune-universe vocabulary (fremen-sandworm-441, harkonnen-melange-7). Repository description: Shai-Hulud: The Third Coming.

  6. npm worm and GitHub Actions injector: Enumerated all packages the victim's npm token could publish to, unpacked each tarball, replaced the preinstall script with a new dropper (node setup.mjs), bumped the patch version, and republished via Bun's native publish API. Simultaneously injected a GitHub Actions workflow named Formatter into every repository the stolen GitHub token could write to, serializing the entire secrets context into a downloadable artifact, then deleting the branch and workflow run.

The malware included a Russian-language execution check: it did not run if the host machine's locale was configured to Russian.

The version mismatch between the outer package.json (2026.4.0) and the embedded build/bw.js metadata (2026.3.0) is the primary forensic indicator confirming the malicious layer was added post-build rather than through the normal vendor pipeline.

TECHNICAL EXECUTION CHAIN🏷️1Mutable tag resolves to malicious actioncheckmarx/ast-github-action poisoned🔑2NPM_ACCESS_TOKEN inheritedPipeline env exposes publish credentials📦3Malicious layer injected post-buildbw_setup.js + bw1.js added to base🟢4Bun runtime bootstrappedDownloaded from github.com/oven-sh/bun🧠5bw1.js executes 6 capability blobsHarvest, collect, encrypt, exfil, wormThe preinstall hook fires at install time and the bw binary entrypoint fires at every subsequent invocation.

CVE and Advisories

A CVE for @bitwarden/cli@2026.4.0 was announced on April 23, 2026. The CVE ID had not been assigned at time of publication; this post will be updated when the canonical record is available at cve.org.

The upstream Checkmarx compromise was disclosed in the Checkmarx Security Update (March 23, 2026), which confirmed that checkmarx/ast-github-action and checkmarx/kics-github-action were affected, and identified checkmarx.zone as the attacker-controlled C2 domain used in the March wave.

MITRE ATT&CK Mapping

Technique IDATT&CK nameHow it appeared
T1195.001Supply Chain Compromise: Compromise Software Dependencies and Development ToolsTeamPCP compromised Checkmarx's GitHub Actions to gain access to downstream build pipelines, including Bitwarden's.
T1059.007Command and Scripting Interpreter: JavaScriptThe payload bw1.js is a heavily obfuscated JavaScript bundle executed via the Bun runtime.
T1552.001Unsecured Credentials: Credentials in FilesThe harvester swept a hardcoded list of credential files including SSH keys, cloud configs, npm tokens, and shell histories.
T1552.004Unsecured Credentials: Private KeysSSH private keys were explicitly targeted by the credential harvester.
T1555Credentials from Password StoresThe malware called AWS SSM Parameter Store, AWS Secrets Manager, Azure Key Vault, and GCP Secret Manager APIs directly.
T1567.001Exfiltration Over Web Service: Exfiltration to Code RepositoryThe fallback C2 channel committed encrypted payloads to public GitHub repositories created under the victim's own account.
T1053Scheduled Task/JobThe preinstall hook and binary entrypoint rewiring ensured the payload fired at both install time and every subsequent invocation.
T1496Resource HijackingStolen npm tokens were used to republish trojanized versions of victim-owned packages to downstream consumers.

Indicators of Compromise

Network Indicators

  • audit.checkmarx.cx resolving to 94.154.172.43 (primary C2, impersonating Checkmarx)
  • checkmarx.zone (C2 domain used in the March 2026 Checkmarx wave, per Checkmarx's advisory)
  • Outbound connections to github.com/oven-sh/bun/releases from CI runners or developer workstations (Bun runtime download)

File System Indicators

  • Presence of bw_setup.js or bw1.js in the npm package cache or project node_modules/@bitwarden/cli/
  • tpcp.tar.gz in temporary directories
  • Unexpected public GitHub repositories named with Dune-universe vocabulary (fremen-*, harkonnen-*, sandworm-*) under victim accounts

npm and GitHub Indicators

  • @bitwarden/cli version 2026.4.0 in package.json or package-lock.json (deprecated; upgrade to 2026.4.1)
  • GitHub Actions workflow named Formatter appearing in repositories without a corresponding commit in the default branch history
  • Commit messages matching the pattern LongLiveTheResistanceAgainstMachines in any repository

Detection Difficulty

The worm's cleanup behavior (deleting the injected branch and workflow run after extracting secrets) means GitHub audit logs are the primary detection surface for the Actions injection component. Organizations should query their GitHub audit log for workflow creation and deletion events on April 22, 2026 and the days following. The Russian-language execution check means the malware produced no artifacts on Russian-locale machines, which may create gaps in forensic timelines for organizations with globally distributed developer populations.

Attribution

Multiple security firms attribute this attack to TeamPCP (also tracked as @pcpcats), a cloud-native criminal operation active since at least September 2025. Palo Alto Unit 42, StepSecurity, Socket, and GitGuardian all name TeamPCP as the responsible actor. Prior confirmed victims include Aqua Security's Trivy scanner, LiteLLM, and telnyx, across npm, PyPI, Docker Hub, GitHub Actions, and VS Code extensions.

Socket researchers note an attribution wrinkle: the Bitwarden payload carries ideological branding not seen in earlier TeamPCP operations, including the Shai-Hulud: The Third Coming repository marker, Butlerian Jihad debug strings, and commit messages proclaiming resistance against machines. Socket assesses this could indicate a splinter group, a different operator sharing infrastructure, or a deliberate shift in campaign posture. OX Security notes the Russian-language execution check as a potential indicator of origin, though this technique is common and not definitive. No nation-state link has been established by any firm.

The Shai-Hulud worm lineage dates to September 2025 (original campaign), November 2025 (Shai-Hulud 2.0, tracked by Datadog, Microsoft, and GitLab), and February 2026 (SANDWORM_MODE variant, tracked by Socket). This April 2026 incident is the third major wave.


Primary Sources