Mastra npm Supply Chain Attack: Poisoned Packages via Maintainer Takeover

On June 17, 2026, Microsoft Threat Intelligence reported that attackers compromised the npm maintainer account “ehindero” and used it to publish poisoned versions of more than 140 packages across the Mastra npm ecosystem. The attack did not wait for vulnerable code to be imported, compiled, or executed by an application. It fired during installation, at the moment many developers and build systems are least inclined to treat a package as active code. That is why this incident matters beyond Mastra: it is a reminder that the modern JavaScript supply chain is not merely a dependency graph, but an execution environment.
The Mastra compromise is the kind of attack that looks almost boring until it works. A trusted maintainer account, a plausible-looking dependency, a version range that resolves forward, and a postinstall hook are not exotic ingredients. They are ordinary parts of npm’s daily machinery. The attackers appear to have understood that the quietest way into a developer workstation or CI/CD runner is not to beat the application at runtime, but to join the build before the application exists.

Cybersecurity diagram showing an NPM supply-chain compromise, dependency graph, and malicious postinstall payload on CI systems.The Attack Began Where Trust Is Usually Outsourced​

The first failure was not in Mastra’s application logic. It was in the identity and publishing layer around the packages. Microsoft says the attacker gained control of the ehindero npm maintainer account, which had publish rights across the mastra and @mastra scopes, then used that access to publish new versions of more than 140 packages.
That distinction matters. In a traditional vulnerability story, defenders ask whether a function is reachable, whether a route is exposed, or whether a vulnerable package is actually imported. Here, those questions arrive too late. If a developer or automated build system resolved and installed the compromised version, the malicious lifecycle script could execute before the project ever called a Mastra API.
The publishing pattern was also a tell. Microsoft observed that prior versions of the main mastra package, through version 1.13.0, were published through GitHub Actions OpenID Connect as part of a legitimate CI/CD pipeline. Version 1.13.1 was manually published by the compromised account using a Tutamail address. That break from the established release path is the sort of anomaly that only matters if someone is watching metadata, not just code diffs.
The actual code delta was brutally simple. Microsoft says the only change between [email][email protected][/email] and [email][email protected][/email] was the addition of easy-day-js@^1.11.21 as a dependency. No corresponding source change appeared in the Mastra GitHub repository. In other words, the registry artifact had become the attack surface, not the repo.

A Typosquat Did the Work a Trojan Usually Does​

The malicious package, easy-day-js, was built to look like the popular dayjs library. That is not a subtle trick, but it does not need to be. Typosquatting succeeds because package ecosystems reward speed, familiarity, and trust by resemblance.
The attackers reportedly copied key signals from the legitimate project, including the claimed author and repository reference. The package name was close enough to seem familiar while distinct enough to exist as a separate npm package. Microsoft characterized it as a deliberate impersonation of dayjs, a library with tens of millions of weekly downloads.
The more interesting move was timing. The attackers first published [email][email protected][/email] as a clean bait release on June 16 at 07:05 UTC. That version contained legitimate Day.js code and no malicious postinstall hook. The weaponized version, [email][email protected][/email], followed on June 17 at 01:01 UTC, adding setup.cjs and the lifecycle script that would run automatically during installation.
That sequencing points to an operator who understood both package reputation and dependency resolution. The compromised Mastra packages were injected with easy-day-js@^1.11.21, a semantic version range that would resolve to the later 1.11.22 release once it existed. The clean package made the dependency appear less suspicious at birth; the weaponized patch release turned the range into a delivery mechanism.
This is why “we reviewed the package when it first appeared” is not enough. In ecosystems built around ranges and automatic resolution, trust is not a one-time event. A package can be harmless when first published and malicious when the next patch version lands.

The Postinstall Hook Turned Installation Into Execution​

The center of gravity in this attack was npm’s lifecycle scripting. The malicious package’s postinstall hook ran setup.cjs automatically when the package was installed. That meant the payload executed during npm install or npm update, including inside CI/CD systems that may never run the application itself.
This is the uncomfortable part for defenders. Developers often think about dependencies in terms of imported modules. Build systems think in terms of lockfiles, caches, and reproducibility. Attackers think in terms of what code runs with access to credentials, environment variables, source repositories, signing keys, package tokens, cloud metadata, and deployment permissions.
A developer workstation is valuable because it is messy. It may have browser sessions, SSH keys, npm tokens, cloud CLIs, password managers, wallet extensions, local .env files, and access to internal systems. A CI/CD runner is valuable because it is privileged by design. It may have short-lived tokens, deployment authority, package publishing rights, and the ability to turn source code into production artifacts.
The Mastra incident exploited exactly that boundary. The malicious package did not need a production server to import it. It needed a machine that trusted npm enough to install it.

Obfuscation Was the Opening Move, Not the Payload​

Microsoft describes the first-stage setup.cjs file as a 4,572-byte obfuscated dropper. It used a rotated array of Base64-encoded strings and a decoder function to hide meaningful strings from static inspection. That is not advanced malware engineering by itself, but it is enough to defeat quick greps, casual inspection, and some automated triage.
Once deobfuscated, the dropper’s purpose was clear. It disabled TLS certificate verification by setting NODE_TLS_REJECT_UNAUTHORIZED to 0, wrote marker files in the OS temp directory, contacted attacker-controlled infrastructure, downloaded a second-stage payload, spawned that payload as a detached hidden Node.js process, and then deleted itself.
The TLS bypass is worth lingering on. Disabling certificate validation is a common malware convenience because it allows command-and-control communications without the operational burden of valid certificates. In a developer context, it is also a loud behavioral signal. Legitimate packages have few good reasons to silently weaken TLS verification during installation.
The marker files are equally telling. Microsoft says the dropper wrote .pkg_history, containing the install path, and .pkg_logs, containing an XOR-encoded reference to easy-day-js, into the temp directory. These artifacts are not merely forensic crumbs; they suggest the operator wanted state, tracking, or idempotence across installations.
The self-deletion step completes the pattern. By removing setup.cjs after execution, the dropper attempted to erase the most obvious evidence from the installed package directory. That will not defeat mature endpoint telemetry, but it can frustrate after-the-fact inspection by a developer who looks inside node_modules and finds the package apparently less incriminating than expected.

The Second Stage Made This More Than a Credential Grab​

The second-stage payload is where the incident stops looking like a smash-and-grab and starts looking like a foothold. Microsoft describes it as an approximately 41 KB cross-platform Node.js tasking client. Rather than simply stealing a fixed set of secrets and exiting, it installed persistence, sent a start beacon, and entered a repeated check loop for operator commands.
That design gives the attacker options. A tasking client can run shell commands, execute Node code, update configuration, exit, and potentially adapt to the victim. In a CI/CD environment, that flexibility is dangerous because the attacker may not know in advance which runners hold useful tokens or which build jobs are worth tampering with.
On Windows, Microsoft says the payload went further. It performed host reconnaissance and used PowerShell to download a .NET DLL, load it directly into memory through reflection, and invoke a method associated with process injection into cmd.exe. That fileless chain is meant to avoid simple disk-based detection and shift the battleground toward behavior, memory, and process telemetry.
The reconnaissance was practical rather than theatrical. The payload enumerated installed applications through Start Menu entries, registry uninstall keys, and UWP packages. That can reveal security tools, developer utilities, cloud clients, browsers, and high-value software. Attackers do not need a perfect asset inventory when the victim will provide one.
This is one reason supply chain incidents are so hard to scope. If the payload merely wrote a known file and exited, responders could search for the file. A tasking client with arbitrary follow-on execution means exposure must be treated as a period of possible operator access, not a single static event.

Windows Was Not the Only Target, but It Got Special Treatment​

The implant was cross-platform, with persistence mechanisms for Windows, macOS, and Linux. That makes sense for a JavaScript supply chain attack. npm packages move across developer laptops, Linux build agents, macOS workstations, and Windows environments without caring much about the organization chart.
The persistence scheme was tailored to each platform. On Windows, the payload used a user-level Registry Run key and a drop path under C:\ProgramData\NodePackages\. On macOS, it used a LaunchAgent under the user’s Library path. On Linux, it used a systemd user unit under the user’s config directory.
The naming theme was deliberately bland. Artifacts such as protocal.cjs, NvmProtocal, com.nvm.protocal, and nvmconf.service appear designed to blend into Node or NVM-adjacent clutter. The misspelling of “protocol” is not proof of sophistication, but the broader masquerade is familiar: pick a name administrators are tired of seeing, then hide in their fatigue.
Windows received additional attention through the PowerShell and reflective .NET execution chain. That is not surprising. Windows developer machines often combine browser-based identity, enterprise management, cloud tooling, and local administrative convenience. For an attacker, a Windows workstation can be both an identity beachhead and a staging point.
For Windows defenders, the lesson is not that npm is a Linux problem that wandered into Redmond territory. The lesson is that developer tooling is now a first-class Windows attack surface. Node, npm, PowerShell, Git, package managers, cloud CLIs, and browsers form a blended execution environment, and attackers are increasingly comfortable moving through all of it.

CI/CD Turned a Developer Attack Into a Production Risk​

The most consequential line in Microsoft’s report is not about the obfuscator or the C2 infrastructure. It is the observation that any developer workstation or CI/CD pipeline that ran npm install or npm update after the compromised versions were published was potentially exposed, regardless of whether the package was imported in application code.
That sentence should make build engineers uncomfortable. CI/CD systems are often treated as sterile, repeatable, and disposable. In reality, they are frequently privileged, internet-connected machines that execute code fetched moments earlier from public registries. If a lifecycle script runs inside that environment, it may inherit access to secrets that developers never see directly.
The downstream integrity risk is more subtle than credential theft. A compromised build runner can potentially alter artifacts, tamper with dependencies, publish poisoned packages, or inject backdoors into software that will later be signed and distributed. Even if none of that happened here, the access pattern makes it plausible enough that responders must consider it.
Lockfiles help, but they are not magic. A project with a committed lockfile that did not resolve the compromised version may have avoided execution. A pipeline that regenerated locks, allowed updates, used floating ranges, or installed affected packages during the exposure window may not have. The practical question is not “Do we use Mastra?” but “Did any environment we control install the poisoned graph?”
That is why incident response must include build logs, dependency caches, package-lock files, outbound network telemetry, and token rotation decisions. The blast radius is not bounded by application source imports. It is bounded by installation events.

SemVer Did Exactly What It Was Told​

There is a temptation after every npm compromise to blame semantic versioning, as if the caret range itself had malicious intent. That is too easy. SemVer did what developers asked it to do: accept compatible updates within a defined range. The problem is that compatibility in package management has historically meant API compatibility, not publisher integrity.
The injected dependency easy-day-js@^1.11.21 could resolve to 1.11.22, the weaponized release. From npm’s perspective, that is normal. From a security perspective, it is an unreviewed code substitution at install time. Those two realities are now in conflict.
Enterprises have spent years telling developers to patch quickly. Automation tools open dependency update pull requests, vulnerability scanners nag about stale packages, and build systems are optimized to fetch the newest acceptable version. Attackers have learned to surf that current. In this case, the malicious patch version arrived just before the poisoned Mastra packages were published at scale.
The defensive answer is not to freeze the world. It is to distinguish between dependency freshness and dependency execution. Allowing a library patch into a review queue is one thing. Allowing a newly published package to run an installation script inside a privileged CI runner is another.

Package Registries Are Becoming Identity Systems​

The Mastra compromise underscores a structural shift in open source security: package registries are no longer just file distribution systems. They are identity, authorization, provenance, and execution platforms. When a maintainer account with broad publish rights falls, the registry becomes a release pipeline for the attacker.
Microsoft’s account of the publication anomaly is especially important here. Previous Mastra releases used GitHub Actions OIDC; the compromised release was manually published. That kind of provenance break should be treated as a high-value signal. A package that suddenly stops coming from its normal CI path deserves more suspicion than a package that merely changes a few lines of source.
The industry has been moving toward stronger provenance, signed attestations, trusted publishing, and two-factor authentication for maintainers. Those controls are necessary, but they remain unevenly adopted and unevenly enforced. The attacker only needs one account with enough rights and one ecosystem where consumers accept the resulting packages automatically.
The hard part is that open source maintainership is not corporate identity management. Maintainers change jobs, lose interest, reuse credentials, delegate publishing, and inherit old scopes. A package may have millions of downstream users while its release authority rests on a handful of accounts. That mismatch is the permanent vulnerability.
For organizations consuming open source at scale, this means registry metadata deserves the same attention as source code. Who published the package? Was it published through the usual workflow? Did a new maintainer appear? Did a dependency appear without a corresponding repository change? Did the package add a lifecycle script? These are not academic questions anymore.

Defender Coverage Helps, but It Does Not Replace Hygiene​

Microsoft’s report naturally emphasizes Defender coverage, including detections for suspicious Node.js execution, malicious npm behavior, reflective code loading, persistence, and command-and-control communication. For Microsoft shops, that coverage is useful, especially where Defender for Endpoint telemetry spans developer workstations and build infrastructure.
But endpoint detection is the backstop, not the policy. The better control is to reduce the number of places where package lifecycle scripts can execute with meaningful privileges. The next best control is to make unexpected lifecycle execution visible. The worst control is to assume that because npm installation is routine, it is safe.
Running npm install --ignore-scripts is an obvious mitigation, but it is not universally painless. Some legitimate packages use install scripts to compile native modules, download platform-specific binaries, or perform setup. That reality is precisely why attackers like lifecycle hooks: banning them outright can break builds, while allowing them silently creates a malware lane.
A more mature approach is contextual. Developer laptops, ephemeral CI jobs, release pipelines, and production container builds do not all need the same policy. High-trust release environments should be far stricter than local experiments. New packages and newly changed lifecycle scripts should face more scrutiny than long-standing, pinned dependencies.
The same goes for tokens. If installing dependencies exposes long-lived credentials, the environment is already too permissive. CI/CD systems should prefer short-lived, scoped credentials; package publishing rights should be separated from routine build jobs; and secrets should not be broadly available to every install step simply because a pipeline might need them later.

The Response Starts With Installation Evidence​

Organizations investigating this incident should resist the urge to begin and end with source code search. The malicious behavior depended on installation, so the key evidence lives in dependency locks, package caches, CI logs, endpoint telemetry, and network records. If affected package versions were never resolved or installed, risk is lower. If they were installed on a privileged host, assume exposure until proven otherwise.
Microsoft’s recommended checks are concrete: look for easy-day-js in node_modules and lockfiles, review dependency trees for affected Mastra versions, inspect temp directories for .pkg_history and .pkg_logs, and hunt for unexpected JavaScript files in user home or temp paths. Network telemetry should be searched for connections to the reported C2 addresses. On Windows, defenders should also inspect user Run keys and suspicious PowerShell-to-Node execution chains.
Credential rotation is the painful but rational step for exposed environments. If a developer workstation or CI runner installed the compromised package during the relevant window, tokens available to that environment may have been accessible. That includes npm tokens, cloud credentials, GitHub or Azure DevOps tokens, deployment secrets, signing material, and application secrets exposed during builds.
The timeline is tight but meaningful. The clean bait package appeared on June 16 at 07:05 UTC. The weaponized [email][email protected][/email] appeared on June 17 at 01:01 UTC. The compromised Mastra packages followed around 01:20 UTC. Microsoft observed the malicious package at 01:07 UTC and [email][email protected][/email] at 01:28 UTC. Those timestamps give defenders a window for log review, but not an excuse to assume the damage window was harmlessly short.

The Mastra Compromise Leaves a Checklist Written in Npm’s Own Grammar​

The practical lesson is not that developers should stop using npm, Mastra, or modern JavaScript tooling. The lesson is that install-time execution has to be treated as a privileged act, especially when it happens inside environments that hold credentials or produce deployable artifacts.
  • Organizations that installed compromised Mastra versions should search for easy-day-js in lockfiles, package caches, node_modules directories, and CI job logs.
  • Systems that ran installation during the exposure window should be checked for temp artifacts such as .pkg_history, .pkg_logs, unexpected random .js files, and persistence entries masquerading as Node or NVM components.
  • CI/CD environments should rotate exposed credentials if the compromised dependency was installed, because the payload did not need application runtime execution to access the build context.
  • Build pipelines should treat new or changed npm lifecycle scripts as security-relevant events rather than routine package metadata.
  • Teams should prefer pinned known-good versions, trusted publishing paths, and restricted script execution policies where practical, especially in release and deployment workflows.
  • Endpoint and network detections should be paired with registry provenance monitoring, because the earliest signal may be a strange publisher rather than a strange process.
The Mastra attack is not the end of npm supply chain compromises, and it is unlikely to be the most technically elaborate one we will see. Its importance is that it shows how little novelty is required when the attacker can combine account takeover, typosquatting, SemVer resolution, and install scripts into a single delivery chain. The future of defending developer environments will depend less on treating open source packages as inert ingredients and more on treating every install as a moment of execution, identity, and trust.

References​

  1. Primary source: Microsoft
    Published: 2026-06-18T03:50:07.938238
 

Back
Top