JustAppSec
Back to news

Malicious preinstall hooks turn SAP CAP npm install into credential theft

2 min readPublished 29 Apr 2026Source: StepSecurity Blog

TL;DR - Compromised SAP CAP ecosystem packages added "preinstall": "node setup.mjs" to their package.json. On install, setup.mjs fetches Bun 1.3.13 from GitHub Releases, then uses Bun to execute an ~11MB obfuscated credential stealer. In CI, the payload reads /proc/{pid}/mem to extract plaintext secrets directly from the GitHub Actions Runner.Worker process.

What happened

SAP CAP is SAP's open-source application programming model for Node.js - widely used in SAP BTP projects. Several of its npm dependencies were published with malicious install-time behaviour.

StepSecurity captured the full execution chain:

  • npm install triggers the preinstall lifecycle hook: node setup.mjs.
  • setup.mjs downloads the Bun runtime (1.3.13) directly from GitHub Releases.
  • Bun executes execution.js, an obfuscated payload around 11MB in size.

In a controlled GitHub Actions environment, the payload went further. It spawned a Python child process, scanned /proc for the Runner.Worker process, then read memory via /proc/{pid}/mem to extract plaintext secrets - tokens, credentials, whatever the runner held.

Install scripts are already arbitrary code execution. This campaign treats that as a starting point, not an end goal. Developer machines and CI runners are the target surface. Credentials come first; the propagation path through .vscode/tasks.json and repo commits comes after.

Who is impacted

  • Any environment that installed the compromised versions, including developer workstations and CI runners.
  • Highest risk: GitHub Actions jobs where npm install runs with access to repository tokens and CI secrets.
PackageCompromised version
mbt1.2.48
@cap-js/sqlite2.2.2
@cap-js/postgres2.2.2
@cap-js/db-service2.10.1

What to do now

  • Check whether you installed the compromised versions:
    • npm list mbt 2>/dev/null | grep "1\.2\.48"
    • npm list @cap-js/sqlite 2>/dev/null | grep "2\.2\.2"
    • npm list @cap-js/postgres 2>/dev/null | grep "2\.2\.2"
    • npm list @cap-js/db-service 2>/dev/null | grep "2\.10\.1"
  • Look for the malicious file directly in node_modules:
    • ls node_modules/mbt/setup.mjs 2>/dev/null && echo "COMPROMISED"
    • ls node_modules/@cap-js/sqlite/setup.mjs 2>/dev/null && echo "COMPROMISED"
  • If you find a compromised version: uninstall it and downgrade to a clean release.
  • Rotate all credentials on every machine and CI/CD pipeline where a compromised package was installed. This is not optional and not scoped - assume full exposure.

    "Rotate ALL credentials on every machine and CI/CD pipeline where a compromised package was installed"

  • Hunt for persistence artifacts in your repositories - specifically look for .vscode/tasks.json files that reference setup.mjs. The payload attempts to propagate via committed IDE configuration.

Related


Content is AI-assisted and reviewed by our team, but issues may be missed and best practices evolve rapidly, send corrections to [email protected]. Always consult official documentation and validate key implementation decisions before making design or security choices.

Need help?Get in touch.