TeamPCP-Linked Supply Chain Attack Hits SAP CAP and Cloud MTA npm Packages

8 min read Original article ↗

Sidebar CTA Background

Secure your dependencies with us

Socket proactively blocks malicious open source packages in your code.

Install

Socket is investigating a suspected supply chain attack affecting multiple npm packages associated with SAP’s JavaScript and cloud application development ecosystem.

At the time of publication, Socket has identified the following affected package versions:

Socket’s analysis indicates that the affected versions introduced new installation-time behavior that was not previously part of these packages’ expected functionality. The compromised releases added a preinstall script that acts as a runtime bootstrapper, downloading a platform-specific Bun ZIP from GitHub Releases, extracting it, and immediately executing the extracted Bun binary.

These packages did not previously require a Bun installer to function, and the sudden addition of a binary-downloading preinstall script created a high-impact execution path during package installation. The implementation also follows HTTP redirects without validating the destination and uses PowerShell with -ExecutionPolicy Bypass on Windows, increasing the risk for affected developer and CI/CD environments.

The affected packages are notable because they are connected to SAP’s Cloud Application Programming Model, or CAP, and SAP cloud deployment workflows. The mbt package is the npm-distributed Cloud MTA Build Tool, which is used to build deployment-ready Multi-Target Application archives for SAP cloud applications. The @cap-js/* packages are database service packages for CAP applications, including SQLite and PostgreSQL integrations.

Importantly, @cap-js/sqlite is not the generic SQLite library. It is SAP CAP’s SQLite database service package. CAP commonly uses SQLite for local development and testing, including in-memory database workflows.

Based on current npm download estimates available to Socket, the affected packages have meaningful reach across the SAP developer ecosystem, with approximate weekly downloads of:

  • mbt: 52,000
  • @cap-js/postgres: 10,000
  • @cap-js/db-service: 260,000
  • @cap-js/sqlite: 250,000

Socket recommends that developers and security teams immediately review dependency trees and lockfiles for the affected versions. Teams using SAP CAP, SAP Business Technology Platform workflows, or MTA-based deployment pipelines should verify whether these packages were installed during the suspected exposure window.

Until more technical details are confirmed, teams should avoid installing the affected versions, rotate any credentials or tokens that may have been exposed in build or developer environments, and review CI/CD logs for unexpected network activity or binary execution.

Early timeline information suggests the suspicious versions were published within a short window on April 29, 2026:

One affected version, @cap-js/sqlite@2.2.2, appears to have already been unpublished, based on early review. Because npm download counts can lag or be aggregated, current package download numbers may not be exact.

This is a developing story. Socket’s threat research team is continuing to analyze the affected packages and will publish technical details as more information becomes available.

We’re tracking this TeamPCP-linked SAP CAP supply chain campaign on a dedicated page with affected package artifacts, publish times, detection times, and related coverage:

https://socket.dev/supply-chain-attacks/mini-shai-hulud

Technical Analysis#

How the Attack Works

Each tarball contains the expected SAP source tree alongside three injected files: a modified package.json, a new setup.mjs, and a new execution.js. The forensics here are clean, original SAP files carry npm's standard Oct 26, 1985 modification timestamp, while the three injected files are timestamped between 15:25 and 17:43 UTC on April 29, 2026, indicating the tarballs were post-processed after being pulled from a legitimate source.

The modified package.json adds a single lifecycle hook:

"preinstall" : "node setup.mjs"

This fires automatically on every npm install, giving the attacker execution before any application code runs.

The Loader (setup.mjs)

The loader is byte-identical across all four packages (MD5: 35baf8316645372eea40b91d48acb067) despite the packages belonging to two separate namespaces, a strong signal of a coordinated, automated injection campaign.

Its job is to bootstrap a Bun runtime on the victim machine:

  1. Probes the host via ldd --version and /etc/os-release to detect glibc vs. musl
  2. Downloads the appropriate Bun binary from github.com/oven-sh/bun/releases (bun-v1.3.13)
  3. Extracts and sets the binary executable (chmod 755)
  4. Executes execution.js under Bun via execFileSync
  5. Deletes the temporary Bun directory in a finally block to clean up traces

If Bun is already present on PATH, the download step is skipped entirely. The full chain runs with whatever privileges invoked npm install , including CI/CD pipelines and developer machines.

The Payload (execution.js)

execution.js is an ~11.7 MB single-line file obfuscated with javascript-obfuscator. The @cap-js/sqlite variant alone encodes 48,683 string array entries across 865,576 characters, with all meaningful identifiers — URLs, environment variable names, file paths, credential keywords — stored as encoded entries not recoverable via strings or grep. A second cipher layer wraps the most sensitive strings: a function named __decodeScrambled() uses PBKDF2 with 200,000 SHA-256 iterations and salt "ctf-scramble-v2" to derive a 32-byte key, applies a Fisher-Yates shuffle to the resulting keystream, and XORs each ciphertext byte with the shuffled stream. The function name, algorithm, salt, and iteration count are identical to those in the Checkmarx and Bitwarden payloads, indicating shared tooling. Each of the three distinct SAP payloads carries a unique PBKDF2 master key generated per deployment.

The payload's execution proceeds as follows.

Kill Switch on Systems with RU Locale: The payload reads LANG, LANGUAGE, LC_ALL, and LC_MESSAGES. If any value starts with "ru", execution halts immediately. It then checks 25 CI platform environment variables — GITHUB_ACTIONS, CIRCLECI, TRAVIS, BUILDKITE, JENKINS_URL, GITLAB_CI, CODEBUILD_BUILD_ID, and others — and branches into one of two code paths depending on the environment.

On developer machines, the payload reads 80+ credential file patterns from the filesystem:

  • SSH private keys across all key types
  • Cloud credentials for AWS (~/.aws/credentials), Azure (~/.azure/accessTokens.json), GCP (~/.config/gcloud/application_default_credentials.json), and Kubernetes (~/.kube/config)
  • Developer tooling credentials including ~/.npmrc, ~/.gitconfig, ~/.git-credentials, and ~/.docker/config.json
  • Environment files (**/.env, **/.env.local, **/.env.production);
  • AI tool configuration files (~/.claude.json, ~/.claude/mcp.json, ~/.kiro/settings/mcp.json);
  • Cryptocurrency wallets across eleven platforms; messaging application data for Signal, Slack, Telegram, and Discord; and shell history files.

It also executes gh auth token to steal any cached GitHub CLI token. Cloud metadata endpoints are probed directly:

On CI runners, the payload executes an embedded Python script that reads /proc/<pid>/maps and /proc/<pid>/mem for the Runner.Worker process to extract every secret matching "key":{"value":"...","isSecret":true} directly from runner memory, bypassing all log masking applied by the CI platform. This memory scanner for secrets is structurally identical to the one documented in the Bitwarden and Checkmarx incidents.

Exfiltration. All harvested data is encrypted with RSA-OAEP-4096 using a hardcoded attacker public key and wrapped with AES-256-GCM. The payload creates a GitHub repository under the victim’s own account, using a repository naming pattern observed in prior TeamPCP-linked supply chain activity: <word>-<word>-<3 digits>; for example, prescient-lasgun-242. The repository description uses the observed string A Mini Shai-Hulud has Appeared and uploads the encrypted archive there. C2 retrieval uses the GitHub commit search API with the dead-drop key string OhNoWhatsGoingOnWithGitHub. Prior TeamPCP-linked variants also posted to audit.checkmarx[.]cx/v1/telemetry — a threat-actor-controlled domain (.cx is the Christmas Island TLD, not a legitimate Checkmarx endpoint) — as a primary channel with GitHub as fallback. The SAP payloads use GitHub as the confirmed primary channel.

Self-propagation. Using the stolen npm token, the payload enumerates every package the victim maintains, injects execution.js, and publishes under the commit message "chore: update dependencies". Unlike the broader enumeration logic observed in prior TeamPCP-linked variants, the db-service and postgres payloads hardcode @cap-js/cds-typer, @cap-js/db-service, and @cap-js/postgres as explicit next-hop targets; the sqlite payload hardcodes @cap-js/sqlite. This specificity indicates the attacker mapped the SAP CAP.js dependency graph before injection.

Persistence. Two backdoors are written to disk targeting developer tooling directly:

  1. .claude/settings.json registers a SessionStart hook executing node .vscode/setup.mjs on every Claude Code session open.
  2. .vscode/tasks.json registers a runOn:folderOpen task executing node .claude/setup.mjs on every VSCode project open. Both files reference each other and bootstrap a fresh Bun download and re-execution of execution.js. Prior TeamPCP-linked variants persisted via ~/.bashrc and ~/.zshrc injection, while this variant shifts persistence entirely to IDE and AI coding assistant surfaces.

Payload Variants and Campaign Evolution

Three distinct execution.js payloads exist across the four packages. The mbt payload is the earliest: fewer credential targets, one embedded RSA public key, no hardcoded propagation targets. The @cap-js/db-service and @cap-js/postgres packages share a byte-identical payload with two RSA public keys and hardcoded propagation targets. The @cap-js/sqlite payload (SHA256: 6f933d00b7d05678eb43c90963a80b8947c4ae6830182f89df31da9f568fea95) adds OIDC token theft via registry.npmjs.org/-/npm/v1/oidc/token/exchange/package/ which is absent from the others. The incremental capability additions across the three variants, each deployed on the same day, are consistent with the active tooling development pattern observed across prior TeamPCP-linked supply chain waves.

Attribution: TeamPCP-Linked Supply Chain Activity#

We assess with medium confidence that the SAP CAP npm compromise is linked to the threat actors behind recent TeamPCP supply chain campaigns. This assessment is based on specific technical overlap, shared operational patterns, and consistent targeting of developer and CI/CD environments.

The strongest attribution signal is the reuse of distinctive implementation details previously observed in TeamPCP-linked operations. Wiz independently reported that the SAP CAP payload used the same __decodeScrambled cipher to encode secrets before exfiltration and the same Russian locale guardrail seen in prior TeamPCP campaigns. These are specific engineering choices, and they increase the analytic weight of the overlap.

The SAP CAP campaign also followed the operational model seen in prior TeamPCP-linked activity affecting Aqua Security Trivy, LiteLLM, Checkmarx KICS/AST, Telnyx, and Bitwarden CLI. The payload harvested GitHub, npm, cloud, Kubernetes, and CI/CD credentials, then used stolen access to support additional repository and package compromise.

GitHub abuse is another key attribution signal. Like the Namastex.ai, Checkmarx, and Bitwarden-linked activity, the SAP CAP campaign used GitHub repositories as exfiltration and staging infrastructure rather than relying only on conventional command and control (C2) servers. This reflects a consistent operational preference for abusing trusted developer platforms as data transport, staging infrastructure, and propagation infrastructure.

Indicators of Compromise#

Files

setup.mjs
Hash: 4066781fa830224c8bbcc3aa005a396657f9c8f9016f9a64ad44a9d7f5f45e34

execution.js
Hashes:
- eb6eb4154b03ec73218727dc643d26f4e14dfda2438112926bb5daf37ae8bcdb
- 80a3d2877813968ef847ae73b5eeeb70b9435254e74d7f07d8cf4057f0a710ac
- 6f933d00b7d05678eb43c90963a80b8947c4ae6830182f89df31da9f568fea95

tmp.987654321.lock
Hash: N/A

Detection Opportunities#

Monitor for suspicious access to cloud metadata services from package-installation or JavaScript runtime processes, especially npm, node, bun, setup.mjs, or execution.js:

  • http://169.254.169.254
  • http://169.254.170.2
  • http://127.0.0.1:40342
  • http://metadata.google.internal

Github-Related Indicators

  • GitHub commit-search dead-drop query: https://api.github.com/search/commits?q=OhNoWhatsGoingOnWithGitHub&sort=author-date&order=desc&per_page=50
  • Commit Message: OhNoWhatsGoingOnWithGitHub:<Base64=>
  • Commit Message: chore: update dependencies
  • Repository Description: A Mini Shai-Hulud has Appeared