Supply Chain Security

142 Packages in 88 Minutes: The Mastra npm Scope Hijack and the easy-day-js Infostealer

Dark cyberpunk illustration of a glowing amber shipping crate on a conveyor belt with a cracked cyan seam leaking light into a near-black industrial scene.

For 88 minutes early on June 17, every fresh install of Mastra, the popular open-source framework teams use to build AI agents, reached past the framework's own code and pulled down a date library that had nothing to do with dates. Between 01:12 and 02:39 UTC, an attacker republished 142 packages across the @mastra npm scope - including the top-level mastra and create-mastra packages - each carrying one new dependency: easy-day-js, a typosquat of the widely used dayjs library. The package manifests looked normal. Their dependency lists did not.

Mastra's packages pull more than 1.1 million downloads a week, with @mastra/core alone near 918,000. Any machine that ran npm install on an affected project during that window - a laptop, a CI runner, a container build - executed an obfuscated postinstall dropper that disabled TLS verification, beaconed home, and pulled down a cross-platform infostealer. Then it deleted itself. This is the third widely felt npm scope takeover in a month, and the first to ride an AI-agent framework straight into developers' build pipelines.

Who pulled this, and who can stop reading

Here is the honest scope. You are exposed if any developer, CI job, or Docker build installed mastra, create-mastra, or an @mastra/* package - directly or as a transitive dependency - on or after June 16, and the resolver was free to pick a new minor version of easy-day-js. Because the malicious dependency was pinned as ^1.11.21, a clean install or an npm update in that window resolved to the weaponized 1.11.22 without changing any Mastra version you had written down.

If you do not use Mastra, never installed those packages, and a tree scan comes back clean, this specific compromise is not yours, and you can close the tab. The delivery mechanism still deserves your attention, because the next one will look identical with a different package name. The check further down tells you which group you are in.

An 88-minute window built on one stale credential

The setup was patient. On June 16 at 07:05 UTC the attacker published a clean, functional easy-day-js@1.11.21 - a credibility build that did nothing malicious and drew no attention. Roughly 18 hours later, at 01:01 UTC on June 17, they pushed easy-day-js@1.11.22 with the payload. Eleven minutes after that, the automated republish of the @mastra scope began.

The access did not come from cracking npm. According to Snyk and Orca, the attacker controlled a maintainer account, ehindero, that still held publish rights to the @mastra scope; Mastra later said the account belonged to an active employee whose machine was compromised through social engineering. Either path leads to the same lesson: a single credential with standing write access to an entire package scope is enough. With it, the attacker scripted 142 clean-looking republishes in under 90 minutes, faster than any human review could keep up, and let npm's own semver rules carry the payload the rest of the way.

The 60-second check before anything else

Before you rotate a single credential, find out whether the dependency actually entered your environment. Run this from each repository root, and against your CI cache and any long-lived build images:

# Is the typosquat anywhere in this project's resolved tree?
npm ls easy-day-js 2>/dev/null

# Sweep lockfiles, the npm cache, and installed modules across a fleet
grep -rIl "easy-day-js" . ~/.npm/_cacache 2>/dev/null

# If you keep build logs, look for the dropper and its beacon
grep -REn "setup\.cjs|NODE_TLS_REJECT_UNAUTHORIZED=0|23\.254\.164\.(92|123)" \
  ~/.npm/_logs /var/log 2>/dev/null

The known indicators are narrow enough to grep for:

  • Malicious dependency: easy-day-js@1.11.22 (the clean bait was 1.11.21).
  • Dropper: a postinstall hook running node setup.cjs --no-warnings (about 4.5 KB, obfuscated).
  • C2: 23.254.164.92:8000 (stage one) and 23.254.164.123:443 (stage two), hosted on Hostwinds, AS54290.
  • Beacon markers: .pkg_history and .pkg_logs in the system temp directory.

A clean npm ls on every tree, with no matching log lines, means you dodged it. A match means you treat the host as compromised and keep reading.

What the dropper actually did

The injected code never touched the Mastra source. It lived entirely in the postinstall script of easy-day-js, which is the quiet part of every npm install: arbitrary code that runs with the installing user's privileges before any application code executes. Per the StepSecurity and Orca analyses, the setup.cjs dropper did five things in order:

  • Set NODE_TLS_REJECT_UNAUTHORIZED=0 to disable certificate checks so its C2 calls would not fail on a self-signed cert.
  • Wrote the install path to a beacon file, .pkg_history, in the temp directory.
  • Fetched a second-stage payload from https://23.254.164.92:8000/update/<id>.
  • Spawned it as a detached background process with output ignored, so it outlived the terminal that launched it.
  • Deleted itself with fs.rmSync(__filename) to pull the evidence off disk.

The second stage was an infostealer with an obvious financial motive. It harvested saved data from Chrome, Edge, and Brave, then walked through 166 cryptocurrency wallet browser extensions - MetaMask, Phantom, Coinbase, Binance, and the long tail behind them - pulling stored credentials and seed material. It ran host reconnaissance and set up persistence in OS-specific spots: a registry run key on Windows, a LaunchAgent on macOS, a systemd user service on Linux. On a developer machine the blast radius is everything that machine can reach: npm and GitHub tokens, cloud keys, SSH keys, database credentials, and any LLM API keys sitting in a local .env.

There is a reason this one landed on an AI framework. Tools like Mastra are install-and-go: a developer runs npx create-mastra, accepts a tree of dependencies they will never read, and wires the result straight into systems holding LLM keys and customer data. Socket, which helped surface the campaign alongside JFrog and SafeDep, has tracked the same playbook across npm all spring: publish a clean version for trust, swap in a malicious point release, and let caret ranges handle distribution. The framework is new; the supply-chain technique is well worn. What an AI-agent project adds is a richer set of secrets on the machine that runs the install, which is precisely what a credential stealer is built to monetize.

How to keep the next easy-day-js out of your build

The defenses that would have blunted this are unglamorous and mostly free. None of them depend on spotting the malicious package by name, which is the point: next time the name will be different.

1. Stop running install scripts you did not ask for

The entire payload executed through a lifecycle hook. In CI, where installs are unattended and credentials are richest, turn them off and re-enable only for the handful of packages that genuinely need them:

# Refuse lifecycle scripts on install (set in CI and on dev machines)
npm config set ignore-scripts true

# Reproducible, lockfile-exact installs in CI - no surprise minor bumps
npm ci --ignore-scripts

# With pnpm, block by default and allow-list explicitly
pnpm config set enable-pre-post-scripts false

2. Take away semver's permission to surprise you

A caret range, ^1.11.21, is what let a brand-new 1.11.22 land without a code review. Commit your lockfile, install with npm ci instead of npm install in automation, and consider a registry cooldown that refuses versions published in the last 24 to 48 hours. The bait-and-swap here lived entirely inside that window.

3. Make a compromised token worth less

Mastra's own remediation is the template: it removed the rogue owner, force-published clean versions of all 142 packages, and disabled token-based publishing in favor of mandatory MFA. If you publish packages, scope tokens narrowly, give them short lifetimes, require provenance, and pull access from anyone who has left the project. The account here was a contributor that should have lost its keys long ago.

4. Plan for rotation, not cleanup

If a host ran the dropper, you cannot scrub it back to trusted. Rotate every credential that machine could see, move crypto wallets onto a clean device, and remove the persistence entry for that OS. Reimaging is faster than chasing artifacts.

Roll the versions back, then assume the install already ran

If easy-day-js showed up in your tree, the order of operations is fixed: pin Mastra to the clean republished versions, delete node_modules and the typosquat's lockfile entry, reinstall with scripts disabled, and then treat every developer and CI host that touched it as a credential-exposure incident rather than a patch. The registry is back to clean; the tokens that sat on those machines stay valid until you rotate them. The hour you spend rotating now costs far less than explaining a drained wallet or a pivot from a leaked cloud key later. If you want a second set of eyes on how your build pipeline pulls and trusts dependencies, that is exactly the kind of review we run.

Worried about what your build actually pulls in?

We review software supply chains end to end - lockfile and install-script policy, dependency provenance, scoped publish tokens, and the credential hygiene that limits the blast radius when a package goes bad. Book a session to harden your build pipeline before the next typosquat lands.