Account takeover led to the spreading multi-stage dropper containing a crypto-stealer logic. 141 packages affected, with an estimated over ~8M weekly downloads
Breaking News: The @mastra organization in npm was hit with a supply chain attack, 141 packages affected, with an estimated over ~8M weekly downloads, and 29M monthly downloads
Overview
On June 17, 2026, the npm account “ehindero” was hijacked to deliver malware targeting the @mastra organization on npm. The malicious code wasn’t injected directly into the affected packages — instead, the attacker used a subtler approach: they swapped one of the dependencies, “dayjs,” for an attacker-controlled package called “easy-day-js.” The first version of “easy-day-js” was clean, but a subsequent version introduced crypto-stealing logic inside a post-install script.
The attack was coordinated by the threat actor stealing the “ehindero” account, and another account – “sergey2016” – uploading the malicious npm package, and weaponizing it.
The use of install script is soon to be deprecated in npm, which might surge an increase in the amount of pre & post install script malware until the estimated release of npm v12 in July.
Who is affected
Anyone who installed a @mastra organization package during the malware uptime in June 17 2026.
Impact
Total accumulated monthly downloads – ~29,230,561
Total affected packages – 141 (see table chart below)
Total accumulated weekly downloads – ~8,031,590
Recommended Actions
Immediate Actions:
- Harden access to your crypto-wallets and check for unknown transactions
- Downgrade the affected packages to a safe version
Technical Analysis
We will start our analysis with the first stage-1 dropper, which is delivered via a malicious npm package named `easy-day-js`.
This is a typesquat of the legitimate and widely-used `day.js` date library – which has over 40M+ weekly downloads on npm.
The `package.json` file is cloned almost entirely from the real `day.js` package (same author, description, homepage, repository URL) with two changes we will mention:
The `name` field changed from `dayjs` to `easy-day-js`.
A `postinstall` lifecycle hook added: `”postinstall”: “node setup.cjs –no-warnings”`
The threat actor changed the “dayjs” dependency in the @mastra packages to “easy-day-js”, which contained the malicious dropper.
This causes `setup.cjs` to execute automatically and silently on every `npm install`.
First of all, the malware makes the TLS verification disabled:
This disables SSL/TLS certificate validation for the entire process, allowing communication with attacker servers using self-signed certificates.
Next, we found the hardcoded C2 addresses:
Two distinct attacker IPs with specific roles:
`23.254[.]164.92:8000` – the payload delivery server.
`23.254[.]164.123:443` – C2 server, passed as argument to stage 2.
In the next step, the package name is encoded as: `ASCII value + 128` and then decoded into: `easy-day-js` – the package name itself, obfuscated and embedded in the dropper.
Then, we move on to Stage 2 of payload fetch and execution:
This snippet of code contains several steps:
Fetch – downloads stage-2 JS payload from the C2 as plain text.
Random filename – `crypto.randomBytes(12).toString(“hex”) + “.js”` generates a 24-character random hex filename (e.g. `a3f9c12d4b8e7f2a1c9d3e4f.js`). Static filename detection won’t work against this.
Write to temp – payload lands in the OS temp directory, not the package directory making it harder to correlate with the npm install.
C2 address as argument – `23.254.164.123:443` is passed as `process.argv[2]` to stage 2 which uses it to communicate with the C2 server.
At the final part, there is a self-deletion step which works as anti-forensics:
This removes the primary forensic artifact. Post-incident, there is no dropper on disk –
only the randomly-named stage-2 script in the temp directory (itself likely self-deleting).
Conclusions
npm’s upcoming deprecation of pre/post install scripts in v12 is a meaningful step — but threat actors are already adapting. As this attack shows, malicious logic can be embedded in alternative hooks like binding.gyp, sidestepping the restriction entirely.
Beyond install-time controls, the deeper gap remains: npm has no meaningful review process for packages uploaded with malicious intent. That leaves import-time attacks, typosquatting, and dependency hijacking largely unaddressed — regardless of what v12 ships.
Hardened 2FA and install script restrictions raise the cost of attack. They don’t close the window..
Affected Packages
| Package name | Affected versions |
| @mastra/acp | 0.2.2 |
| @mastra/agent-browser | 0.3.2 |
| @mastra/agent-builder | 1.0.42 |
| @mastra/agentcore | 0.2.2 |
| @mastra/agentfs | 0.1.1 |
| @mastra/ai-sdk | 1.4.6 |
| @mastra/arize | 1.2.3 |
| @mastra/arthur | 0.3.3 |
| @mastra/astra | 1.0.2 |
| @mastra/auth | 1.0.3 |
| @mastra/auth-auth0 | 1.0.2 |
| @mastra/auth-better-auth | 1.0.4 |
| @mastra/auth-clerk | 1.0.3 |
| @mastra/auth-cloud | 1.1.4 |
| @mastra/auth-firebase | 1.0.1 |
| @mastra/auth-okta | 0.0.5 |
| @mastra/auth-studio | 1.2.4 |
| @mastra/auth-supabase | 1.0.2 |
| @mastra/auth-workos | 1.5.3 |
| @mastra/azure | 0.2.3 |
| @mastra/blaxel | 0.4.2 |
| @mastra/braintrust | 1.1.4 |
| @mastra/brightdata | 0.2.2 |
| @mastra/browser-firecrawl | 0.1.1 |
| @mastra/browser-viewer | 0.1.3 |
| @mastra/chroma | 1.0.2 |
| @mastra/clickhouse | 1.10.1 |
| @mastra/claude | 1.0.3 |
| @mastra/client-js | 1.24.1 |
| @mastra/cloud | 0.1.24 |
| @mastra/cloudflare | 1.4.2 |
| @mastra/cloudflare-d1 | 1.0.7 |
| @mastra/codemod | 1.0.4 |
| @mastra/convex | 1.2.2 |
| @mastra/core | 1.42.1 |
| @mastra/couchbase | 1.0.4 |
| @mastra/cursor | 0.2.1 |
| @mastra/dane | 1.0.2 |
| @mastra/datadog | 1.2.5 |
| @mastra/daytona | 0.4.2 |
| @mastra/deployer | 1.42.1 |
| @mastra/deployer-cloud | 1.42.1 |
| @mastra/deployer-cloudflare | 1.1.44 |
| @mastra/deployer-netlify | 1.1.20 |
| @mastra/deployer-vercel | 1.1.38 |
| @mastra/docker | 0.3.1 |
| @mastra/dsql | 1.0.3 |
| @mastra/duckdb | 1.4.3 |
| @mastra/dynamodb | 1.0.9 |
| @mastra/e2b | 0.3.4 |
| @mastra/editor | 0.11.3 |
| @mastra/elasticsearch | 1.2.1 |
| @mastra/engine | 0.1.1 |
| @mastra/evals | 1.3.1 |
| @mastra/express | 1.3.31 |
| @mastra/fastembed | 1.1.3 |
| @mastra/fastify | 1.3.31 |
| @mastra/files-sdk | 0.2.1 |
| @mastra/gcs | 0.2.3 |
| @mastra/github-signals | 0.1.2 |
| @mastra/google-cloud-pubsub | 1.0.6 |
| @mastra/google-drive | 0.1.1 |
| @mastra/hono | 1.4.26 |
| @mastra/inngest | 1.5.2 |
| @mastra/koa | 1.5.14 |
| @mastra/laminar | 1.2.3 |
| @mastra/lance | 1.0.7 |
| @mastra/langfuse | 1.3.6 |
| @mastra/langsmith | 1.2.4 |
| @mastra/libsql | 1.13.1 |
| @mastra/loggers | 1.1.3 |
| @mastra/longmemeval | 1.0.50 |
| @mastra/mcp | 1.10.1 |
| @mastra/mcp-docs-server | 1.1.47 |
| @mastra/mcp-registry-registry | 1.0.2 |
| @mastra/mem0 | 0.1.14 |
| @mastra/memory | 1.20.4 |
| @mastra/modal | 0.2.2 |
| @mastra/mongodb | 1.9.3 |
| @mastra/mssql | 1.3.2 |
| @mastra/mysql | 0.1.1 |
| @mastra/nestjs | 0.1.15 |
| @mastra/node-audio | 0.1.8 |
| @mastra/observability | 1.14.2 |
| @mastra/openai | 1.0.2 |
| @mastra/opencode | 0.0.47 |
| @mastra/opensearch | 1.0.3 |
| @mastra/otel-bridge | 1.2.3 |
| @mastra/otel-exporter | 1.2.3 |
| @mastra/perplexity | 0.1.1 |
| @mastra/pg | 1.13.1 |
| @mastra/pinecone | 1.0.2 |
| @mastra/playground-ui | 33.0.1 |
| @mastra/posthog | 1.0.29 |
| @mastra/qdrant | 1.0.3 |
| @mastra/rag | 2.2.2 |
| @mastra/railway | 0.1.1 |
| @mastra/react | 1.0.1 |
| @mastra/redis | 1.1.3 |
| @mastra/redis-streams | 0.0.4 |
| @mastra/s3 | 0.5.3 |
| @mastra/schema-compat | 1.2.12 |
| @mastra/sentry | 1.1.4 |
| @mastra/server | 2.1.1 |
| @mastra/slack | 1.3.1 |
| @mastra/spanner | 1.1.2 |
| @mastra/speech-azure | 0.2.1 |
| @mastra/speech-elevenlabs | 0.2.1 |
| @mastra/speech-google | 0.2.1 |
| @mastra/speech-ibm | 0.2.1 |
| @mastra/speech-murf | 0.2.1 |
| @mastra/speech-openai | 0.2.1 |
| @mastra/speech-replicate | 0.2.1 |
| @mastra/speech-speechify | 0.2.1 |
| @mastra/stagehand | 0.2.5 |
| @mastra/tavily | 1.0.3 |
| @mastra/temporal | 0.1.14 |
| @mastra/turbopuffer | 1.0.3 |
| @mastra/twilio | 1.0.2 |
| @mastra/upstash | 1.1.3 |
| @mastra/vectorize | 1.0.3 |
| @mastra/vercel | 1.0.1 |
| @mastra/voice-aws-nova-sonic | 0.1.4 |
| @mastra/voice-azure | 0.11.2 |
| @mastra/voice-cloudflare | 0.12.3 |
| @mastra/voice-deepgram | 0.12.2 |
| @mastra/voice-elevenlabs | 0.12.2 |
| @mastra/voice-gladia | 0.12.2 |
| @mastra/voice-google | 0.12.3 |
| @mastra/voice-google-gemini-live | 0.12.2 |
| @mastra/voice-inworld | 0.3.1 |
| @mastra/voice-modelslab | 0.1.2 |
| @mastra/voice-murf | 0.12.3 |
| @mastra/voice-openai | 0.12.3 |
| @mastra/voice-openai-realtime | 0.12.6 |
| @mastra/voice-playai | 0.12.2 |
| @mastra/voice-sarvam | 1.0.2 |
| @mastra/voice-speechify | 0.12.2 |
| @mastra/voice-xai-realtime | 0.1.2 |
| create-mastra | 1.13.1 |
| easy-day-js | 1.11.22 |
| mastra | 1.13.1 |
How Can OX Help?
If you’re an OX customer, inside the OX platform, go to the Active Issues view, under the ‘Severity’ category, you can filter by ‘Malicious Dependency’ to instantly find threats across both your repositories and containerized environments.
If you don’t have an account, you can sign up for a demo to see how OX detects these threats in your environment.