Inside the Mastra npm supply chain attack

AI dev frameworks are becoming a key target for software supply chain attackers. The logic is rather simple: if you can compromise the framework itself, you have the ability to compromise highly sensitive infrastructure today. That includes LLM API keys, cloud provider secrets, the kind of credentials that would allow an attacker to move laterally from there.

On June 17, a sophisticated software supply chain incident, specifically targeted Mastra, through a simple typosquatted easy-day-js package name. Mastra is a popular open-source TypeScript framework used for building AI agents and RAG pipelines.

By hijacking a former contributor’s creds, attackers were able to inject a malicious dependency across 144 packages in the Mastra ecosystem. With core components like @mastra/core pulling nearly a million weekly downloads, the blast radius of an attack like this is massive. In this blog we’ll look at how the attack unfolded, how the malware evades detection, and how developers can secure their environment today.

How the attack unfolded

The attackers executed a multi-stage campaign that relied on a blend of account takeover, social engineering (via package mirroring), and a dynamic second-stage runtime payload designed to bypass traditional static analysis.

The groundwork was laid a day prior to the primary breach. An npm user named sergey2016 published version 1.11.21 of a package called easy-day-js. At this point, the package appeared entirely benign, indicating it was a fully functional clone of the highly popular dayjs date library.

By copying the original library’s version numbering, MIT license, and author metadata (iamkun), the attackers ensured that any casual visual or automated sanity check of the package structure would look clean - one of the many ways the attacker is evading detection.

In the next stage, the threat actor updated the easy-day-js package to version 1.11.22. Again, this version looked identical to its predecessor but introduced a relatively small, obfuscated file named setup.cjs, along with an execution trigger in the package.json. Following a wave of community concern, npm recently announced it would disable postinstall scripts by default in its next major version. This is why.

"scripts": {
  "postinstall": "node setup.cjs --no-warnings"
}

The --no-warnings flag was explicitly used to suppress the Node.js runtime environment warnings, which ensured devs remained unaware during the installation.

A few minutes later, the attackers struck the Mastra ecosystem. They hijacked the npm account belonging to ehindero, a legitimate former Mastra contributor whose organisational scope access had never been revoked. From there, they used an automated script that rapidly populated the @mastra/* org and mass-published updates to 144 packages in just over an hour. Each package was updated to include the malicious dependency:

"dependencies": {
  "easy-day-js": "^1.11.21"
}

Because of the way that npm resolves semantic versioning (^), any fresh install or build runner pulling the clean pinned version 1.11.21 was automatically forced to resolve and download the malicious 1.11.22 patch. From an execution perspective, when a dev or automated CI/CD pipeline runs npm install, the postinstall hook fires immediately, which causes the setup.cjs script to initiate a highly-evasive first-stage dropper into the environment.

How the malware evades detection

The underlying mechanics of the first-stage execution reveal a strong emphasis on evasion and anti-forensics. It has four clear evasion tactics:

  1. TLS cert bypassing: The NODE_TLS_REJECT_UNAUTHORIZED = '0' allows the script to safely reach out to an attacker’s C2 infrastructure even if the interception tools or self-signed proxies attempt to inspect or block the traffic.
  2. Process detachment: By leveraging the detached: true and .unref(), the second-stage payload is then able to spin out into its own OS process. When the core npm install routine finishes or is terminated, the malware continues running silently in the background.
  3. Self-deletion: Finally, the script finishes by calling fs.rmSync(__filename). By removing the setup.cjs immediately after execution, the installer leaves behind a clean directory tree, baffling standard post-incident file scanners.
  4. Missing attestations: Mastra utilises npm's trusted publisher flow inside their CI pipeline, generating robust Sigstore-backed SLSA provenance attestations for authentic releases. However, while Mastra generated provenance, their npm org policy did not enforce it.

The tragedy of this fourth evasion tactic is that it was entirely preventable. Because a strict verification policy wasn't required on the registry side, npm willingly accepted the attacker’s mass updates pushed directly from a stolen PAT, even though they dropped the cryptographic provenance signature. Any client configuring a strict signature-verifying installation policy would have immediately blocked this entire campaign.

How developers can secure their environments today

By using open infrastructure tooling like osv-scanner, organisations can detect the malicious easy-day-js package as well as the various malicious packages published to the compromised Mastra org on June 17.

However, security shouldn’t just be reactive. Developers can’t just wait for security advisories. While npm has stepped in to pull any compromised versions from the registry, if you had run an install during the exposure window, before these advisories were published, you should treat the environment as compromised.

Securing your software supply chain requires a proactive architecture. If you're managing external dependencies using Cloudsmith, here’s how you could mitigate attacks like easy-day-js:

  1. Enforce upstream cooldown policies: You’ll be hearing this from us a lot, but cooldown policies really are one of the most effective solutions to this recent wave of open-source supply chain attacks. Malicious packages are often caught and reported within hours of publication. By establishing an upstream proxy with a cooldown window (let’s say, holding newly-published packages for one or two days before they become eligible for download), you create a natural buffer that keeps automated pipeline scripts from pulling zero-day dependencies.
  2. Lock down your semantic versioning: As highlighted earlier, you should never rely purely on range-based specifiers (either ^ or ~) for high-value upstream dependencies. Where possible, ensure a strict package-lock.json or a yarn.lock is enforced across all local and CI/CD builds. This guarantees that your builds anchor exclusively to known, cryptographically hashed versions.