Web Development

The Supply Chain Attack Nobody Tells You About: How I Learned npm ci Isn't Actually Clean

A

Adil Sher

Author

Aug 6, 2026
4 min read
7 views
The Supply Chain Attack Nobody Tells You About: How I Learned npm ci Isn't Actually Clean

I was debugging a production incident last year when a colleague casually mentioned, "Yeah, we're running npm ci --ignore-scripts now. Should be safe." I nodded along, but something in that statement bothered me. We were treating a flag like it was a silver bullet, a get-out-of-jail-free card for dependency security. Turns out, I was wrong to be skeptical in the way I was. The real problem was much weirder.

A few months later, I came across the Shai-Hulud attack breakdown, and it completely rewired how I think about incident response in a Node.js project. This wasn't just another supply chain attack. This was something that exposed a blind spot in how we all approach security cleanup, a gap between what we think we're protecting and what we're actually protecting.

When ESLint Became a Trojan Horse

In August 2026 (or whenever this happened in the timeline), attackers compromised the npm account of a maintainer working on caching libraries. We're talking keyv, flat-cache, file-entry-cache, packages with 150+ million weekly downloads. The cascade was unavoidable: ESLint depends on file-entry-cache, which means if you were linting code, you were infected.

This is the kind of dependency graph that keeps me awake at night. You didn't install these packages directly. They came in through your linter. The malware, called Math_Symbol.js, used Bun (a trusted runtime) to steal credentials, npm tokens, AWS keys, GitHub PATs, Claude and Cursor IDE credentials. Then it self-replicated across 868 packages in hours.

The clever part? Sigstore signatures were valid. npm audit showed nothing. The traditional safety nets we've built failed completely.

The Persistence Layer Nobody Checks

Here's what actually disturbed me: the worm didn't just live in npm lifecycle scripts. It planted hooks directly into VS Code and Claude Code configuration files.

When you run npm ci --ignore-scripts, you're telling npm not to execute preinstall or postinstall hooks. It's a reasonable precaution. But VS Code doesn't care about that flag. It reads .vscode/tasks.json and executes any task with runOn: folderOpen the moment you open the folder. The worm knew this.

The same goes for Claude Code: it reads .claude/settings.json and fires SessionStart hooks whenever you start a session. If you're using Claude Code to investigate the attack, you've just handed the worm another execution window.

This is genuinely clever threat modeling. The attack surface isn't just npm, it's every tool that reads config from your repository.

My Take: We're Thinking About This Wrong

I've been in enough incident responses to know that the immediate cleanup workflow is: remove infected packages, delete node_modules, run npm ci --ignore-scripts, breathe. But Shai-Hulud forces a harder question: are we treating our IDEs as part of the application attack surface?

I think most of us aren't. We think of IDE config as harmless, it's just settings, right? Wrong. If a config file can be version-controlled, committed, and executed by an IDE, it's code. It should be audited like code.

The problem with automation here is real too. The worm used commit messages like "chore: update config" specifically to blend into noise. No automated tool will catch that. You need to manually inspect these files:

# Check VS Code tasks for anything with folderOpen
cat .vscode/tasks.json | python3 -m json.tool | grep -B2 -A5 "folderOpen"

# Check Claude Code hooks
cat .claude/settings.json | python3 -m json.tool | grep -A10 "SessionStart"

# Hunt through git history for config changes
git log --all --oneline -- "**/.vscode/tasks.json" "**/.claude/**"

This is tedious. It's the kind of thing we usually automate away. But that's exactly why attackers target it.

What I'd Do Differently

I'm adding IDE config inspection to our security checklist now. Not just for incident response, but as part of our normal dependency update process. Before we merge a PR that modifies dependencies, someone's looking at the .vscode and .claude directories too.

I'm also less comfortable with the assumption that --ignore-scripts is sufficient. It's still a good practice, I'm not abandoning it, but it's one line of defense, not the perimeter.

Your Move

If you're managing a team's development environments, the question isn't whether to trust your dependencies. It's whether you're trusting the entire configuration surface that those dependencies can touch.

Have you ever audited your .vscode/tasks.json in production? I'm guessing most people haven't.


Source: This post was inspired by "⚠️ Shai-Hulud opens your IDE: the npm worm that beats --ignore-scripts" by Dev.to. Read the original article

Written by Adil Sher

Full stack developer building high-traffic platforms, AI services, and custom web applications. Explore my portfolio, learn about my background, or get in touch.

Related Articles

I Wasted 3 Months Learning AI the Wrong Way, Here's What Actually Worked
Web Development Oct 1

I Wasted 3 Months Learning AI the Wrong Way, Here's What Actually Worked

I remember the exact moment I decided to "become an AI engineer." It was late 2023, everyone was talking about LLMs, and I had a successful web development background. How hard could it be? I spent the next three months jumping between Andrew Ng's machine learning course, LangCha...

I'm Done Betting On "Enterprise" Secrets Managers For Small Teams
Web Development Sep 30

I'm Done Betting On "Enterprise" Secrets Managers For Small Teams

Six months ago, I spent three days setting up HashiCorp Vault for a four-person team. We spent another week writing deployment scripts, managing encryption keys, and debugging authentication flows. By month two, nobody could remember how to rotate a key without breaking staging....