# SAP CAP npm compromise teardown: Mini Shai-Hulud wave 1

> How four SAP CAP and mbt npm versions were published with a stolen token and a hijacked workflow, what the Bun payload stole, and whether Chainsaw stops it.

Source: https://chain305.com/teardowns/sap-cap-npm-compromise/

---

# Teardown: the SAP CAP and mbt npm compromise

Four SAP npm releases, one preinstall hook, and the first wave of Mini Shai-Hulud.

What happened on 29 April 2026, how the attacker got publish rights to SAP packages, and where Chainsaw would and would not have stopped it.

By [Muhammad Zeeshan Sultan](https://chain305.com/about/#founder), founder of Chain305 · October 2026

Questions and answers

## What happened?

The SAP CAP compromise was a 29 April 2026 attack in which an attacker published four malicious versions of SAP npm packages: `mbt@1.2.48`, the Cloud MTA Build Tool, and `@cap-js/sqlite@2.2.2`, `@cap-js/postgres@2.2.2` and `@cap-js/db-service@2.10.1` from SAP’s Cloud Application Programming Model (CAP). `mbt@1.2.48` went out at 09:55 UTC, and according to Snyk the three `@cap-js` versions followed between about 11:25 and 12:14 UTC. Snyk puts their combined reach at roughly 52,000 (`mbt`), 260,000 (`@cap-js/db-service`), 250,000 (`@cap-js/sqlite`) and 10,000 (`@cap-js/postgres`) weekly downloads.

StepSecurity reports detecting the attack within minutes of the first publish and opened issues on both SAP repositories at about 12:03 UTC. SAP maintainers published a clean `mbt@1.2.49` at 16:15 UTC the same day and say npm confirmed the removal of `1.2.48` on 30 April. For CAP, SAP shipped superseding versions (`@cap-js/sqlite@2.4.0`, `@cap-js/postgres@2.3.0`, `@cap-js/db-service@2.11.0`) and later confirmed the malicious versions were deleted from the registry. The `@cap-js` versions are tracked as CVE-2026-46421 and GitHub advisory GHSA-pvw4-cvr4-97p8; all four are in OSV as MAL-2026-3176 to MAL-2026-3179.

## How did the attacker get publish rights?

The SAP CAP attacker got publish rights through two different routes, and the public reports are not in full agreement on which package used which.

-   **`mbt`: a long-lived npm token.** StepSecurity reports `mbt@1.2.48` went out under a static npm automation token belonging to the project’s CI service account, and the package had never used trusted publishing. How the attacker obtained the token is not visible in public data.
-   **`@cap-js`: a hijacked release workflow and trusted publishing.** The attacker, according to StepSecurity, used a compromised SAP developer’s GitHub account to push a commit making the `release-please` workflow run on a non-main branch, and replaced the workflow’s publish steps with an OIDC token exchange printing the resulting npm token, double base64-encoded, into the workflow log. npm’s trusted-publisher setting for the package trusted any branch that ran the release workflow, so the exchange succeeded.

SAP’s own statement, as quoted by Snyk, says an unauthorized actor pushed commits hijacking the release workflow, and “the workflow had publish permissions without any manual approval gate.” Snyk, however, reports all four versions were published from `cloudmtabot`, the mbt maintainer account, while SAP’s clean releases went out via GitHub Actions trusted publishing, and notes it cannot explain how a hijacked workflow would publish under the `cloudmtabot` identity. Socket does not say how the versions were published. In both routes the result looked like an ordinary SAP release to anyone checking only who published the package.

## What ran on install?

Each SAP package ran a new `preinstall` hook, `node setup.mjs`. `setup.mjs` is a readable loader of about 4.5 KB, byte-identical across all four packages. If the machine has no `bun` binary, the loader downloads Bun 1.3.13 from GitHub Releases and uses Bun to run `execution.js`, a single-line file of about 11.6 MB obfuscated with an obfuscator.io-style string table and a custom cipher. For `mbt`, Snyk reports three new dependencies, `axios`, `tar` and `unzip-stream`, added to support the loader.

According to StepSecurity and Snyk, the payload:

-   **Exits on a Russian locale**, and forks into a detached background process on developer machines while running inline in CI.
-   **Collects credentials:** npm and GitHub tokens, AWS, GCP and Azure credentials and secret-manager contents, Kubernetes credentials, password-manager CLI tokens, `~/.claude.json` and MCP configs, `.env` files and crypto wallets. On Linux GitHub Actions runners it reads the memory of the `Runner.Worker` process to pull out secrets.
-   **Exfiltrates through GitHub.** It encrypts the results (AES-256-GCM with the key wrapped by an embedded RSA-4096 public key) and commits the results to new public repositories on the victim’s own GitHub account, described “A Mini Shai-Hulud has Appeared”. Snyk counted 1,076 such repositories by 15:17 UTC on 29 April 2026.
-   **Persists in repositories.** It commits a `.claude/settings.json` `SessionStart` hook and a `.vscode/tasks.json` task which runs on folder open, both re-running the payload, as “chore: update dependencies” under a `claude@users.noreply.github.com` author. With a `workflow`\-scoped token, it additionally adds a `dependabout/...` branch with a workflow which dumps the repository’s secrets.
-   **Can spread.** It validates stolen npm tokens and publishes infected versions of the victim’s other packages straight to the registry API. Snyk had not observed a fifth package carrying the payload.

## How does it connect to the later wave?

The SAP CAP compromise is the first wave of Mini Shai-Hulud. Twelve days later the same campaign reached TanStack, covered in our [TanStack teardown](https://chain305.com/teardowns/mini-shai-hulud-tanstack/). Tenable describes Mini Shai-Hulud as active since late April 2026 and counts the campaign as the fourth generation of Shai-Hulud, while StepSecurity calls the SAP wave a third wave; the sources number the generations differently.

Between the two waves the attacker moved further from what defenders inspect:

SAP CAP / mbt (29 April)

TanStack (11 May)

Route to publish

Stolen npm token, and a pushed workflow change that exposed a trusted-publishing token

Trusted publishing reached without changing the publish workflow

Install hook

`preinstall` in the published package

Outside the published package’s own scripts

Where data went

Encrypted commits to public repos on the victim’s GitHub account

An end-to-end encrypted file-sharing network

Researchers attribute the campaign to TeamPCP. Socket assesses the SAP compromise as TeamPCP-linked “with medium confidence”, citing a reused cipher and the same Russian-locale guardrail, and according to Tenable, multiple security firms attribute Mini Shai-Hulud to TeamPCP. StepSecurity and Snyk name no actor.

## Would Chainsaw have stopped it?

Yes, on day 0, for SAP package installs through the Chainsaw proxy. The evidence is a re-run of 58 attacks from 2026 against production Chainsaw (v0.22.118) on 19 October 2026:

-   **A default rule refuses them on first download.** Chainsaw’s rule on install scripts that fetch remote code follows the `preinstall` loader to the script it runs, the obfuscated `execution.js`, and refuses `mbt@1.2.48` and the three `@cap-js` versions before any feed lists them. No policy setting has to be turned on.
-   **The malware feed makes the refusal permanent.** The known-malicious feed Chainsaw carries lists all four versions almost at once, so they stay refused.
-   **The day-0 refusal rests on the obfuscation.** The loader downloads Bun through an HTTP client library call, which the rule does not count as a remote fetch. What trips the rule is the heavily obfuscated second stage, so the same loader carrying a readable `execution.js` would not be refused on its bytes; the 48-hour release hold and the feed would stop it.
-   **Installs that bypass the proxy get the malware.** A laptop or CI runner pointed straight at registry.npmjs.org is not protected. Chainsaw therefore also gates CI, pins laptops through MDM and blocks direct registry traffic at the network.

## What should you do?

If `mbt@1.2.48` or one of the three `@cap-js` versions from 29 April 2026 was installed on any machine or CI runner, treat every credential the host could reach as compromised, according to SAP’s advisory. First find the versions (step 1), then rotate npm and GitHub tokens (step 3).

1.  **Find the four versions.** Search lockfiles for `mbt@1.2.48`, `@cap-js/sqlite@2.2.2`, `@cap-js/postgres@2.2.2` and `@cap-js/db-service@2.10.1`. Snyk notes `@cap-js/sqlite@2.2.2` pulls in `@cap-js/db-service@^2.10.0`, so the last one can arrive transitively.
2.  **Upgrade.** Move to `@cap-js/sqlite` 2.4.0 or later, `@cap-js/postgres` 2.3.0 or later and `@cap-js/db-service` 2.11.0 or later. For `mbt`, SAP maintainers say 1.2.49 is clean.
3.  **Rotate credentials** on affected machines and runners: npm and GitHub tokens first, since they let the payload spread, then cloud, Kubernetes and SSH credentials.
4.  **Remove the persistence.** Delete `.claude/settings.json` hooks and `.vscode/tasks.json` tasks that run `setup.mjs`, the `.claude/` and `.vscode/` copies of the payload, any `dependabout/...` branch and `format-check.yml` workflow, and repositories described “A Mini Shai-Hulud has Appeared”. Check history for commits by `claude@users.noreply.github.com`.
5.  **Run CI installs with `--ignore-scripts`** and allow lifecycle scripts only where a build needs them. Snyk notes this blocks the `preinstall` delivery.
6.  **Tighten publishing.** Restrict npm trusted publishing to a specific branch and workflow rather than a whole repository, and put a manual approval gate on release workflows.

## Sources

-   StepSecurity, [A Mini Shai-Hulud has Appeared](https://www.stepsecurity.io/blog/a-mini-shai-hulud-has-appeared), 29 April 2026.
-   Snyk, [Bun-based stealer hits SAP @cap-js and mbt npm packages](https://snyk.io/blog/bun-based-stealer-hits-sap-cap-js-mbt-npm-packages/).
-   Socket, [TeamPCP-linked supply chain attack hits SAP CAP and Cloud MTA npm packages](https://socket.dev/blog/sap-cap-npm-packages-supply-chain-attack), 29 April 2026.
-   SAP, [GHSA-pvw4-cvr4-97p8 / CVE-2026-46421](https://github.com/cap-js/cds-dbs/security/advisories/GHSA-pvw4-cvr4-97p8), cap-js/cds-dbs security advisory.
-   SAP maintainers, [cap-js/cds-dbs issue #1588](https://github.com/cap-js/cds-dbs/issues/1588) and [SAP/cloud-mta-build-tool issue #1224](https://github.com/SAP/cloud-mta-build-tool/issues/1224).
-   OSV, [MAL-2026-3179 (mbt)](https://osv.dev/vulnerability/MAL-2026-3179); MAL-2026-3176 to MAL-2026-3178 cover the `@cap-js` versions.
-   Tenable, [Mini Shai-Hulud: frequently asked questions](https://www.tenable.com/blog/mini-shai-hulud-frequently-asked-questions).

One teardown per notable attack

## Get the next teardown by email

What happened, the install-path angle most coverage misses, and the rule that refuses it.

[Subscribe to teardowns](https://chain305.com/teardowns/) [Read what a package firewall catches →](https://chain305.com/what-is-a-package-firewall/)
