Malicious preinstall hooks turn SAP CAP npm install into credential theft
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 installtriggers thepreinstalllifecycle hook:node setup.mjs.setup.mjsdownloads 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 installruns with access to repository tokens and CI secrets.
| Package | Compromised version |
|---|---|
mbt | 1.2.48 |
@cap-js/sqlite | 2.2.2 |
@cap-js/postgres | 2.2.2 |
@cap-js/db-service | 2.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.jsonfiles that referencesetup.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.
