How to Prevent npm Supply Chain Attacks in Your Vibe Coding Project

Updated on: May 27, 2026
,

It was around 11 AM on Monday when one of my devs pinged me on Slack: "Cursor just added @tallyui/connector-shopify to our package.json. Do we use that one?"

I didn’t recognize the name. We were three weeks into automating a mid-sized D2C brand’s online store - working on the Shopify backend, a custom checkout, and code connecting their product system with order fulfillment.

The AI had been writing most of those integrations all morning. That’s pretty much how our teams work now.

Prompt->generate-> install->ship.

The package looked legitimate. It was published under @tallyui, the versioning looked normal, and the npm README was polished and convincing. My developer was just about to merge it.

I asked him to wait 2 min while I checked something.

That 2 min is the only reason I'm writing this post instead of rotating every credential my team has ever touched.

What Crawled Into Our Codebase

On May 11, 2026, a hacking group brought back a malware campaign known as Mini Shai-Hulud. Between 7 and 8 PM UTC, they uploaded 373 malicious versions across 169 npm packages. The security team at Aikido Security found that several popular package scopes were affected, including @tanstack/, @uipath/, @mistralai/, @squawk/, @draftlab/, @beproduct/, and - the one that immediately caught my attention - @tallyui/, including these four packages

  • @tallyui/connector-shopify
  • @tallyui/connector-woocommerce
  • @tallyui/connector-medusa
  • @tallyui/pos

Yes - Shopify, WooCommerce, Medusa, and even a point-of-sale connector were affected. So if you’re building e-commerce automation tools right now, you need to be careful.  This attack was targeting exactly the kind of tech stack you’re likely using.

The malware itself is aggressive. Once installed, it searches for sensitive credentials like GitHub tokens, npm tokens, AWS keys, Kubernetes files, Vault tokens, .env files, and other secrets stored in environment variables. The most alarming part is that it can also use a stolen npm publish token to infect other packages you own, publish compromised updates under your account, and spread the malware further.

I looked at it carefully and found that it's not just an infostealer. It's a worm that turns any release pipeline into the next attack.

Why This Attack Broke Everything I Thought I Knew

I had read about npm supply chain attacks before. I thought I understood the threat model in which a bad actor compromises a maintainer's token, then publishes a poisoned version later malicious postinstall script runs. Which leads to hijacking your code.

I'm well aware about above workflow, but the TanStack wave broke that model. Here's what the compromised packages actually did, according to Aikido's analysis.

The malicious version added a file at the package root called router_init.js. Which seems harmless at first glance

Then it added what looks like a perfectly reasonable optional dependency:

"optionalDependencies": {
  "@tanstack/setup": "github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c"
}

That dependency points to a GitHub commit on the real TanStack repo.

The trick: npm runs lifecycle scripts for Git dependencies during install, and the referenced commit contains a prepare script that does this:

"scripts": {
  "prepare": "bun run tanstack_runner.js && exit 1"
}

The && exit 1 part is the clever trick. Since it’s an optional dependency, npm ignores the failed install. But by then, the malicious code has already run and the credentials have already been stolen.

And this is the part that really shocked me, the malicious packages were actually published through TanStack’s real GitHub Actions release system using trusted OIDC publishing.

So the packages looked fully legitimate. They even had valid SLSA (Supply Chain Levels for Software Artifacts) security verification showing they came from a trusted source.

Technically, that verification was correct. The attacker didn’t steal a publish token - they took control of the release workflow while it was running.

So if you were told to “only install verified packages”  like I told my own team, you would’ve checked the package, seen the green verification badge, and trusted it enough to merge.

Where Vibe Coding Let Me Down (And I Let It)

I went back through Cursor's tool-call log. Between 9 AM and 11 AM, our agent had run npm install 11 times. Eleven. For a feature branch.

Each time, it was responding to a small prompt - "add SKU validation against the Shopify product catalog," "wire the inventory sync to the Medusa endpoint," "set up a webhook handler" - and each time it would propose a couple of packages, add them, and run install. I'd been approving the diffs without reading them carefully, because the code looked right and the tests passed.

This is the vibe coding failure mode, and I had walked straight into it.

When an AI agent installs a package because your prompt said "add a connector," nobody in the loop has reviewed the package.

The AI agent has no idea that the package maintainer’s account may have been hacked just a few hours earlier.

It also can’t tell that the README might have been carefully written to attract AI tools - something security researchers at ReversingLabs say is happening in a campaign called PromptMink. And sometimes the package name itself may not even be real.

A study from Stanford Human-Centered Artificial Intelligence found that AI-generated JavaScript code occasionally invents fake package names. Attackers take advantage of this by registering those fake names on npm and waiting for developers or AI agents to install them - a tactic researchers call “slopsquatting.

I had built a workflow that did the choosing-a-dependency part on autopilot, then told myself I was reviewing.

How We Prevented npm Supply Chain Attacks in Our Vibe Coding Project

After my dev paused and I checked the Aikido feed, here's what we did in this exact sequence.

I'm sharing it because most "incident response" posts skip the order, and the order matters.

1. Did NOT run rm -rf node_modules

This was the first thing I had to actively stop my team from doing. The Mini Shai-Hulud worm persists outside node_modules  it drops a launch agent at ~/Library/LaunchAgents/com.user.gh-token-monitor.plist on macOS and a systemd user service at ~/.config/systemd/user/gh-token-monitor.service on Linux.

It also writes payloads into .claude/ and .vscode/ that survive uninstall. Nuking node_modules would have made me feel productive while leaving the actual persistence mechanism alive. We searched for those files first.

2. Blocked the C2s at our network layer, not just on the dev machine

For this wave: *.getsession.org, the AWS metadata IP 169.254.169.254 (the payload tries to scrape EC2 instance credentials), and the npm tokens endpoint at registry.npmjs.org/-/npm/v1/tokens.

Blocking it at the firewall meant that any other machine on our network that had installed the package could no longer communicate with the attacker’s server while we investigated the issue.

3. Rotated tokens in priority order - not alphabetically

We decided to reset the npm publish tokens first because the malware uses them to spread itself to other packages.

As long as those tokens stay active, the attacker could publish infected updates using our account. After that, we rotated GitHub personal access tokens, then AWS credentials, then Vault tokens, and finally everything else.

The safest approach is to reset credentials in the same order the malware is most likely to abuse them.

4. Hunted for the worm's calling card

We searched our entire GitHub org for repositories with the description "A Mini Shai-Hulud has Appeared". The worm exfiltrates stolen data by creating public GitHub repos on the victim's own account with that exact phrase. We didn't have any. If we had, deleting them would have been step zero of damage control.

5. Audited every package version installed in the last 72 hours

We cross-referenced all our active e-commerce projects against the affected list. 3 repos had pulled @tanstack/react-router versions that weren't compromised - the bad versions were 1.169.5 and 1.169.8 specifically; we'd pulled 1.169.4.

One repo had @mistralai/mistralai in its lockfile, and I had to check three times that we were on 2.2.1 and not 2.2.2, 2.2.3, or 2.2.4.

By 2 PM I knew we'd dodged it. By 3 PM I knew exactly why, and I was angry at myself for it being luck.

What We Actually Fixed, Starting the Next Morning

Luck is not a control. Here's what we shipped that week, in order of how much it would have helped if we hadn't been lucky.

The One-Liner That Would Have Stopped Everything

npm config set min-release-age 3

This is a native npm config that refuses to install any package version published less than 72 hours ago. The Mini Shai-Hulud TanStack versions were detected within hours. The earlier Axios compromise in March was detected within 24 hours. SAP at the end of April: detected fast. PyTorch Lightning April 30: detected fast. That's why we decided to delay it till 3 days have elapsed.

I realised this single setting would have blocked all of these packages during installation. No extra scripts, CI checks, or security scanners needed. Just one line of configuration. Unfortunalty I hadn’t enabled it in any of our projects, and that was my mistake.

We’ve now added it to our default project template, with a 7-day delay for e-commerce projects since we usually don’t need the newest package versions immediately.

Prevent npm Supply Chain Attacks delay package installtion script

Lock Down npm Before It Locks You Out

Here's what lives in our .npmrc now, committed to every project root:

# Project root .npmrc — committed
min-release-age=7
ignore-scripts=true
save-exact=true
audit-level=moderate
fund=false

ignore-scripts=true is the one most teams resist because a handful of legitimate packages (sharp, bcrypt, better-sqlite3) need to compile native bindings on install.

After going through this myself, I think that mindset is completely backwards. Automatically allowing the npm packages code to run during installation is exactly what made attacks like Mini Shai-Hulud.

The axios incident, the SAP attack, and the PyTorch Lightning attack are possible during such settings. Most packages don’t actually need that level of access. And for the few that do - maybe 5 packages a year - we can manually run commands like npm rebuild sharp ourselves, so we know exactly what code is being executed and when.

save-exact=true writes "axios": "1.14.0" instead of "axios": "^1.14.0".

The caret (^) in package versions is what allows a new npm install to automatically pull in a newer patch or minor release - including a malicious one if a package gets compromised.

Drop npm for New Projects. Use pnpm.

pnpm and bun provide better dependency controls, but lifecycle scripts still need explicit hardening. They significantly reduce exposure to common postinstall -based attacks when properly configured. I wish I'd known this two years ago.

We're not rewriting existing projects, that's not free, but every new e-commerce automation we start now is pnpm, full stop.

Tell Your AI Agent What It's Not Allowed to Do

The single most important file in our repo is now .cursorrules (and CLAUDE.md for the projects on Claude Code).

Here's roughly what's in it now:

# Dependency rules — enforced

1. Before adding ANY package to package.json:
   - Run `npm view <package> version time --json`
   - Report the package's latest version, weekly downloads, and publish date
   - If the package has <1,000 weekly downloads OR was last published <7 days ago,
     STOP and ask before adding
   - If the package does not resolve on npm at all, STOP. Do not guess at a similar name.

2. Never use `npx <package>` for a package not already in package-lock.json.

3. Use `pnpm` for all installs. Never `npm install` in CI —
   use `npm ci` or `pnpm install --frozen-lockfile`.

4. When adding e-commerce connectors (Shopify, WooCommerce, Medusa, BigCommerce):
   - Only use the official packages from those vendors' verified npm scopes
   - Flag any @tallyui/*, @squawk/*, @uipath/* package as requiring manual review

5. If you are uncertain whether a package name is real, SAY SO. Do not invent.

Rule 1 catches the cooldown-window attacks.

Rule 5 prevents slopsquatting.

Rule 4 is specifically what would have caught the package my dev almost merged on Monday.

Rememeber It's not a magic shield. Agents drift from their rules under pressure. But it changed the failure mode from "agent silently installs poisoned package" to "agent visibly defies its rules," which AI will notice.

dependency cooldown github protect npm supply chain attack script

A CI ( continuous integration) Gate That Actually Catches This in Pull Requests

# .github/workflows/dependency-cooldown.yml
name: Dependency Cooldown
on:
  pull_request:
    paths: ['package.json', 'package-lock.json', 'pnpm-lock.yaml']

jobs:
  cooldown:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - uses: actions/setup-node@v4
        with: { node-version: 22 }

      - name: Block fresh dependencies
        run: |
          git fetch origin "${{ github.base_ref }}" --depth=1
          added=$(git diff "origin/${{ github.base_ref }}"...HEAD -- package.json \
                  | grep -E '^\+\s+"[^"]+":' \
                  | sed -E 's/^\+\s*"([^"]+)".*/\1/' \
                  | grep -v '^[a-z]*Dependencies$')

          [ -z "$added" ] && { echo "No new deps."; exit 0; }

          fail=0
          for pkg in $added; do
            data=$(npm view "$pkg" version time --json 2>/dev/null) || continue
            v=$(echo "$data" | jq -r '.version')
            t=$(echo "$data" | jq -r ".time[\"$v\"]")
            age=$(( ($(date +%s) - $(date -d "$t" +%s)) / 86400 ))

            if [ "$age" -lt 7 ]; then
              echo "::error file=package.json::$pkg@$v is $age day(s) old. Cooldown is 7 days."
              fail=1
            else
              echo "✓ $pkg@$v ($age days old)"
            fi
          done
          exit $fail

Two things this workflow gets right that most tutorial versions don't: fetch-depth: 0 so the diff actually resolves against the base branch, and it gates only on real new dependencies, not every keystroke in package.json. If you skip either, your team will disable the workflow within a sprint.

Aikido Safe Chain as a Safety Net

I'm not pitching anyone, but I'll be honest, after this incident, I installed Aikido's Safe Chain on every dev machine on my team.

It's open source. It sits in front of npm, npx, yarn, and pnpm and checks packages against their malware intel feed before the install actually happens.

If they had flagged @tallyui/connector-shopify by Monday morning, my dev would have seen the warning before he typed install. Worth setting up.

Perplexity recently open-sourced Bumblebee, a lightweight supply-chain vulnerability scanner that helps developers detect risky packages, IDE/browser extensions, & AI tool configs on developer machines . This tool is especially useful for every vibe coder to quickly audit their macOS or Windows setup for hidden supply-chain risks before they become problems.

GitHub npm Supply Chain Attacks in Your Vibe Coding Project

The Uncomfortable Truth About Letting AI Pick Your Dependencies

Here's the thing I had to sit with on Monday afternoon.

The whole pitch of vibe coding and the reason my agency can take on the volume of e-commerce work that we do - is that I don't have to read every line of code or evaluate every dependency.

The agent does it. That's the productivity multiplier.

But there is no agent in the world right now that can reliably distinguish a legitimate @tallyui/connector-shopify from a worm wearing its skin.

The AI agent will install both packages with the same level of confidence, and it’ll give the same cheerful “everything worked” summary either way.

AI really does make development faster. But it’s a mistake to assume the AI understands risks, context, or trust the same way a human developer does.

The settings we added like cooldown periods, disabled install scripts, pnpm, agent rules, and CI checks - don’t really slow down AI-assisted coding.

They mainly slow down fully unattended AI coding. And that difference matters a lot now.

The real issue isn’t “how do we make the AI smarter.” The better question is: “how do we limit the damage when the AI makes a bad decision or gets tricked".

If You Read Nothing Else, Do This Today

The most expensive thing in a vibe coding workflow isn't the time you spend reviewing a package. It's the assumption that the agent already did.

npm config set min-release-age 3

Set it. Today. Even if you do nothing else this week.

I personally recommend that you consider vibe coded code as your review draft, not the final code to be shipped.  Please do share your experience if you & your team also get scared with such attacks & questioning the coding safety.

Saurabh Mukhekar
Saurabh Mukhekar is a Professional Tech Blogger. World Traveler. He is also thinker, maker, life long learner, hybrid developer, edupreneur, mover & shaker. He's captain planet of BlogSaays and seemingly best described in rhyme. Follow Him On Facebook

Leave a Reply

Your email address will not be published. Required fields are marked *

Are you human? Please solve:Captcha