StepSecurity Details Miasma Worm's binding.gyp Attack Across 57 npm Packages

Summary
StepSecurity reports that Miasma compromised 57 npm packages across 286+ versions using binding.gyp to execute during installs and steal CI/CD credentials. Controlled analysis observed runner-memory scraping and GitHub-based exfiltration.
Key points
- StepSecurity reports a June 3 campaign affecting 57 npm packages across 286+ malicious versions in under two hours, including @vapi-ai/server-sdk and ai-sdk-ollama.
- A 157-byte binding.gyp file triggers node-gyp and gyp command substitution to run a root-level payload during npm install, without package.json lifecycle scripts.
- In a controlled GitHub Actions run, process and network telemetry showed a Bun download, gh auth token access, sudo-assisted Runner.Worker memory reads, and authenticated GitHub API uploads.
- The payload targets CI/CD and cloud credentials; StepSecurity traced encrypted credential uploads to repositories created under the GitHub account liuende501.
- StepSecurity's decoded-payload analysis indicates the malware can inject setup and configuration files for Claude Code, Cursor, Gemini, and VS Code into repositories accessible with stolen tokens.
- The decoded worm logic indicates propagation across npm, RubyGems, and GitHub, with capabilities to forge Sigstore signing and SLSA provenance for republished packages.
- The article recommends checking dependencies, CI logs, repositories, and credentials; StepSecurity says Harden-Runner terminated a test workflow after detecting anomalous behavior.
Article Details
- Attack Vectors
- Compromised npm packages use a 157-byte binding.gyp file and gyp command substitution to execute node index.js during npm install without declared preinstall or postinstall scripts.
- The payload uses ROT-encoded JavaScript, AES-128-GCM-encrypted stages, and obfuscation before downloading Bun and launching the main payload.
- The malware steals CI/CD credentials, including GitHub tokens, and reads GitHub Actions secrets from Runner.Worker process memory after invoking sudo python3.
- Stolen credentials are used to publish infected packages across npm and RubyGems and to commit AI-assistant configuration backdoors to accessible repositories.
- The malware exfiltrates encrypted credential data to attacker-created GitHub repositories and uses GitHub commit searches as a C2 beacon and token-validation mechanism.
- Defensive Notes
- If an affected package version was installed, treat the system as compromised; revoke and rotate accessible credentials, including npm, GitHub, cloud, Vault, RubyGems, SSH, and CI/CD tokens.
- Inspect accessible repositories for unexpected AI-assistant configuration files and commits, including .claude/setup.mjs, .claude/settings.json, .cursor/rules/setup.mdc, .gemini/settings.json, .vscode/tasks.json, .vscode/setup.mjs, and .github/setup.js.
- Review CI logs for unexpected node-gyp rebuilds, Bun downloads, curl or unzip child processes, runner-memory reads, new repositories, and unauthorized package publishes.
- Block automatic node-gyp builds where possible, use npm install --ignore-scripts for lifecycle scripts, and pin packages with integrity hashes.
- Use a package-registry cooldown period, tightly scope CI permissions, and flag oversized root-level index.js files that are not the declared package entry point.
Indicators of compromise
| Type | Indicator | Context |
|---|---|---|
| SHA256 | 288f26c2eadcb1a7923fe376d16f5404216cce15d9fc162a4a78574dc7df399a | SHA-256 of the compromised [email protected] package tarball. |
| SHA256 | 5926b86b642e00672252953eb30d8f75cfb7797fe3118bd6fa2cfbee92905d61 | SHA-256 of the obfuscated malicious root index.js from [email protected]. |
| SHA256 | 82d83274680df928fdda296a348e01802f595e412308c399565c320df444052a | SHA-256 of the malicious index.js in @vapi-ai/[email protected]. |
| SHA256 | ceff7c51d70832c3ec8dd2744b606a23b3c924ef664ae23439b9b742ea154108 | SHA-256 of the decrypted Bun loader used by the malware. |
| SHA256 | da39146ef451d1b174a24d00b1e2a45cd38d54e849737f8f35333dcb22175707 | SHA-256 of the decrypted Miasma main payload. |
| SHA256 | e3dbe63aded45278f49c4746ab938ed9472b36def79b43e2dd2d7eff014481d1 | SHA-256 of the malicious index.js in @vapi-ai/[email protected]. |
| SHA256 | ef641e956f91d501b748085996303c96a64d67f63bfeef0dda175e5aa19cca90 | SHA-256 of the malicious binding.gyp install-hook file found in compromised packages. |
| URL | hxxps[:]//api[.]github[.]com/repos/liuende501/nemean-hydra-34343/contents/results/results-1780551069887-0.json | Specific GitHub API resource used to upload an encrypted file of stolen credentials to an attacker-created repository. |
| URL | hxxps[:]//api[.]github[.]com/search/commits?q=IfYouInvalidateThisTokenItWillNukeTheComputerOfTheOwner | GitHub commit-search URL used by the malware to validate its stolen token. |
| URL | hxxps[:]//api[.]github[.]com/search/commits?q=thebeautifulmarchoftime | GitHub commit-search URL used by the malware as a C2 beacon. |
MITRE ATT&CK
T1003.007 · Proc FilesystemThe malware reads /proc/2771/mem to extract GitHub Actions secrets from Runner.Worker memory.T1027 · Obfuscated Files or InformationThe payload uses ROT-encoded character data, encrypted stages, and obfuscator.io-obfuscated JavaScript.T1059.004 · Unix ShellThe gyp command substitution executes a shell command during installation; the payload also invokes curl and unzip.T1059.006 · PythonThe malware invokes sudo python3 to read the Runner.Worker process memory.T1059.007 · JavaScriptThe install trigger runs node index.js, and the downloaded Bun runtime executes the JavaScript payload.T1102.002 · Bidirectional CommunicationThe malware uses GitHub commit-search requests for C2 beaconing and token validation.T1105 · Ingress Tool TransferThe loader downloads and extracts the Bun runtime before launching the main payload.T1140 · Deobfuscate/Decode Files or InformationThe malware decodes the ROT-transformed content and decrypts AES-128-GCM blobs to recover its loader and main payload.T1195.002 · Compromise Software Supply ChainThe attacker published compromised npm package versions containing a binding.gyp trigger and malicious payload.T1528 · Steal Application Access TokenThe malware calls gh auth token to steal the GitHub CLI token and uses stolen tokens for API access.T1548.003 · Sudo and Sudo CachingThe malware invokes sudo python3 before reading Runner.Worker memory.T1552.005 · Cloud Instance Metadata APIThe malware attempts to retrieve credentials from AWS and Azure instance metadata endpoints.T1567.001 · Exfiltration to Code RepositoryThe malware uploads encrypted stolen credentials to results files in attacker-created GitHub repositories.
Malware
Vendors
Amazon Web Servicesautotel-aws 0.13.10Anthropic.claude/setup.mjs - Anthropic Claude Code - SessionStart hook: runs on every new Claude Code sessionGCPGCP: GOOGLE_APPLICATION_CREDENTIALS, private_key_id, https://www.googleapis.com/auth/cloud-platform, secretmanagerGitHubIn our analysis, we traced the exfiltration path to the GitHub account liuende501, which hosts 236 repositories used as credential dead-drops. The malware creates a new repo on the fly (e.g., nemean-hydra-34343), thenGoogle.gemini/settings.json - Google Gemini - Settings injectionHashiCorpHashiCorp Vault tokens and Kubernetes service account tokensMicrosoft AzureAzure: https://login.microsoftonline.com/, https://graph.microsoft.com/v1.0/me, keyvault, Managed identity token requestStepSecurityhttps://app.stepsecurity.io/github/actions-security-demo/comp-packages/actions/runs/26932681873
Products
Bun# T+3.9s - Bun runtime downloaded and extracted in under 1 secondClaude Code.claude/setup.mjs - Anthropic Claude Code - SessionStart hook: runs on every new Claude Code sessionCursor.cursor/rules/setup.mdc - Cursor AI - Custom rules file: loaded on project openGitHubIn our analysis, we traced the exfiltration path to the GitHub account liuende501, which hosts 236 repositories used as credential dead-drops. The malware creates a new repo on the fly (e.g., nemean-hydra-34343), thenGitHub ActionsTo analyze the full behavior of this malware, we temporarily disabled this protection and ran @vapi-ai/[email protected] in a controlled GitHub Actions environment with Harden-Runner in audit mode.Google Gemini.gemini/settings.json - Google Gemini - Settings injectionnpmAn attacker compromised 57 npm packages across 286+ malicious versions in a rolling campaign lasting under two hours. The largest victim is @vapi-ai/server-sdk, the official Vapi.ai voice AI server SDK with 408,000+RubyGemsa four-stage obfuscated payload, and a fully automated propagation engine that spreads across npm, RubyGems, and GitHub repositories. Below is a technical breakdown of each component.VaultVault: /var/run/secrets/vault/token, /home/runner/.vault-token, /etc/vault/token, VAULT_ADDR, /v1/auth/kubernetes/login, /v1/auth/aws/loginVSCode.vscode/tasks.json - Visual Studio Code - runOn: folderOpen auto-execute
