I was reviewing my project’s lockfile this morning when I noticed a version string I didn’t recognize: 0.1.2-2773.beta.0. Fresh off the news about two compromised @joyfill npm packages, I felt that familiar twinge — the same one I get when I hear about a new supply chain attack and realize I might be one careless npm install away from a disaster.

A collection of gadgets on a desk including a smartphone, smartwatch, and tablet, representing the developer workspace targeted by npm supply chain attacks
Image: Shixart1985 via Wikimedia Commons (CC BY 2.0)

Here’s what happened. On July 28, 2026, someone published malicious beta versions of @joyfill/components and @joyfill/layouts to the npm registry. These aren’t obscure packages — Joyfill provides SDKs for embedding forms and documents into web apps, and their packages see about 16,000 weekly downloads each. The compromised versions don’t just sit there quietly either. The moment Node.js loads them, they reach out to the Tron blockchain, resolve a BNB Smart Chain transaction, decrypt a payload, and deploy a full remote access trojan onto your machine. No install hooks, no postinstall scripts. Just require() the package, and the chain starts.

This isn’t an isolated incident either. It’s the latest in a relentless wave of npm supply chain attacks — from the FakeGit campaign that planted 7,600 malicious repos, to the slopsquatting attacks that exploit AI coding assistants. The question isn’t whether your project will encounter a compromised package — it’s whether you’ll catch it before it compromises your entire environment.

What the Joyfill Attack Looks Like Under the Hood

Before I walk through the detection steps, it helps to understand what we’re up against. The joyfill compromise is remarkably sophisticated — Socket Research Team and StepSecurity both published excellent technical breakdowns, and the attack chain reads like a heist movie script.

The implant lives inside the published distribution bundles. It doesn’t rely on npm lifecycle hooks, so npm install --ignore-scripts does nothing to stop it. The code executes the moment your application imports the package — during a unit test, a dev server startup, a build, even server-side rendering in production.

Once loaded, it follows a five-stage chain:

  • Stage 0 — The bootstrap exposes Node.js require and module under innocuous global names (global.r = require) and sets a campaign marker
  • Stage 1 — It queries the Tron blockchain for the latest outbound transaction from a hardcoded address, uses that to find a BNB Smart Chain transaction, then decrypts the transaction input with an XOR key
  • Stage 2 — A 5.8 KB JavaScript loader configures the C2 endpoint and repeats the blockchain resolution for a second-stage payload
  • Stage 3 — A 77 KB Node.js RAT with Socket.IO command channel, file upload, clipboard theft, and the ability to execute arbitrary JavaScript or shell commands
  • Stage 4 — A detached Python infostealer that harvests browser data, Git credentials, GitHub CLI tokens, VS Code storage, Windows Credential Manager, and Linux Secret Service data

And here’s the clever part: because the C2 address is resolved through live blockchain transactions, the attacker can swap payloads any time without publishing a new npm package. Blocking a single IP address does nothing — the blockchain pointer just routes to a new one.

Step 1: Scan Your Lockfiles for Suspicious Versions

Your first line of defense is the lockfile. Whether you use package-lock.json, yarn.lock, or pnpm-lock.yaml, every resolved dependency is listed there with its exact version. Here’s the command to check for the joyfill compromise specifically:

grep -rEn 'joyfill.*2773' package-lock.json yarn.lock pnpm-lock.yaml

Any match means you or your team installed a compromised beta version. The affected versions are:

But you shouldn’t stop at joyfill. Make this a habit — regularly grep your lockfiles for prerelease tags, unusual version patterns, or packages you don’t recognize adding themselves to your dependency tree. The jscrambler supply chain attack earlier this month used a similar approach of injecting malicious code into published packages.

Step 2: Run npm Audit With a Critical Eye

npm audit is the built-in tool for checking known vulnerabilities, but it has a blind spot — it only catches issues in the npm advisory database. Zero-day compromises like the joyfill attack won’t appear there until a CVE is assigned and published. Still, it’s your quickest check:

npm audit --audit-level=critical

If you see any CRITICAL severity findings, investigate immediately. But don’t stop at the audit report — cross-reference the package version against known compromise announcements from Socket, StepSecurity, or OpenSourceMalware.

Step 3: Use Socket CLI for Behavioral Analysis

Socket.dev’s CLI tool (I installed it after the FakeGit scare) catches what npm audit misses — it analyzes package behavior, not just CVEs. It flags packages that use eval(), access the file system, make network calls, or use obfuscated code:

npx socket scan ./package.json

The joyfill packages would have been flagged with a security score of 0 and marked REJECTED. Socket’s detection engine spotted the obfuscated loader, the blockchain API calls, and the child_process spawning — all red flags that a simple version check would miss entirely.

For CI/CD pipelines, Socket offers a GitHub Action that blocks PRs containing flagged packages before they ever merge. I’d argue this is becoming as essential as a linter for any team shipping Node.js code.

Step 4: Audit Your node_modules for Anomalies

The nuclear option — and sometimes the only reliable one — is to dig into your actual node_modules tree. Compromised packages often have telltale signs:

  • Abnormally small file size — The malicious joyfill layouts tarball was ~45 KB versus ~322 KB for clean versions. It only exported PDFRenderer while the real package exports a full component library
  • Suspicious global assignments — Look for patterns like global.r = require or global["!"] = "some-marker"
  • Heavy obfuscation in dist files — Seeded string shuffles, nested Function constructors, and LZ77-style token decompressors are signs someone is hiding something
  • Blockchain API calls in unexpected packages — A UI component library shouldn’t be calling api.trongrid.io or fullnode.mainnet.aptoslabs.com

Here’s a quick one-liner to find suspicious global assignments in your installed packages:

grep -r 'global\[.r.\]\s*=\s*require' node_modules/@joyfill/ 2>/dev/null || echo "Not found — good sign"

For a broader scan, search for any package referencing blockchain APIs:

grep -rl 'trongrid\|aptoslabs\|bsc-dataseed' node_modules/ 2>/dev/null | head -20

Step 5: Check Your CI/CD Pipeline

Here’s where things get really serious. The joyfill implant doesn’t just infect your local machine — it runs in CI runners, build servers, and test environments too. And because it uses a detached Node.js process with stdio: "ignore" and unref(), it can survive past the build step and continue running in the background.

Check your CI logs for unexpected outbound connections. Look for calls to api.trongrid.io, fullnode.mainnet.aptoslabs.com, or any eth_getTransactionByHash JSON-RPC calls. If you use ReKon or a similar monitoring tool, review the outbound traffic logs for your build agents.

The HeimWall approach of least-privilege execution is worth considering here — restrict what your CI runners can access and what outbound destinations are allowed. A runner that can only talk to your private registry and npm doesn’t have the luxury of phoning home to a blockchain node.

Step 6: Respond If You’re Affected

If you find the joyfill malicious versions in your project, don’t panic — but do act quickly:

  1. Pin to a clean version immediately: npm install @joyfill/[email protected] @joyfill/[email protected]
  2. Delete node_modules and regenerate lockfiles — the injected bundles must be purged completely
  3. Rotate every credential that existed on affected machines. This includes npm tokens, GitHub tokens, SSH keys, browser-stored passwords, and any cloud provider keys
  4. Audit your Git history for any unexpected commits or pushes from the affected timeframe — the RAT has file modification capabilities
  5. Check for persistence mechanisms — the implant can modify developer tool files. Look for the campaign marker A9-0135-3 or _V global assignments in your VS Code config, shell profiles, and startup scripts

Long-Term Prevention

What keeps me up at night isn’t any single attack — it’s how many of these we’re seeing. ViteVenom, FakeGit, jscrambler, and now joyfill. Each one is more sophisticated than the last, and they’re increasingly linked to state-sponsored threat actors. The PolinRider cluster behind this joyfill compromise is assessed to be a North Korean operation, same as the Contagious Interview campaigns.

Here’s what I’ve changed in my own workflow after covering these attacks:

  • Pin everything. No more loose semver ranges. Every dependency gets an exact version in the lockfile, and I review diffs before updating
  • Use a package firewall. Tools like Socket or the npm package cooldown feature block new publications from entering your project until they’ve been vetted. GitHub’s new 3-day Dependabot cooldown does this at the dependency update level
  • Audit your own supply chain. Run a full dependency tree analysis weekly. Know which packages use native bindings, make network calls, or execute shell commands
  • Segment your CI/CD. Build agents should have the minimum permissions needed to run builds — nothing more
  • Treat every npm install like a deployment. It’s pulling untrusted code into your environment. Would you run an unsigned binary on your machine without checking it first?

The Bottom Line

The joyfill compromise is a wake-up call, but it shouldn’t be a surprise. We’ve watched supply chain attacks evolve from simple postinstall scripts to multi-blockchain, five-stage malware delivery systems. The attackers are getting better, faster, and more creative with every passing month.

The good news? The detection tools are getting better too. Socket, StepSecurity, and even the built-in npm CLI are evolving alongside these threats. But tools only work if you use them. Run that lockfile scan. Install a package scanner. Review your dependencies like you’d review a pull request — because in a real sense, every npm install is a merge of someone else’s code into your running process.

Stay safe out there, and keep your lockfiles clean.

0 0 votes
Article Rating
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Newest
Oldest Most Voted