
Why I would not start a SaaS on npm in 2026
JavaScript has an npm problem. 36 months of supply chain receipts: chalk, debug, Solana, Polyfill, Next.js auth bypass, TanStack. An honest, sourced look at the damage and why Go's architecture is structurally safer.
I am not anti-JavaScript. I have shipped products in TypeScript. Most of my developer friends work on React codebases I genuinely admire.
What I am, sitting here in May 2026 looking at the last week of news, is properly worried about the npm ecosystem. The last 36 months have been the worst sustained run of software supply chain incidents I can remember in any language. The blast radius keeps getting bigger, the attackers more sophisticated, and the structural choices that made npm what it is (tiny packages, deep transitive trees, automatic updates everywhere) are the same choices that make it almost impossible to defend.
If you are running a SaaS on a JS stack right now, you should know exactly how bad the last three years have been. This is the receipts post.
The incidents
These are not hypotheticals. Every one of these is a public, documented compromise of a real npm package in widespread production use. Sources at each entry; the Sources section at the end has the full list.
October 2024: @lottiefiles/lottie-player
Versions 2.0.5 through 2.0.7 of the lottie-player package were compromised via a stolen npm token. The malicious payload was a cryptocurrency wallet drainer targeting Web3 sites. Losses exceeded $723,000 in cryptocurrency before the malicious versions were pulled.
June 2024: Polyfill.io
A Chinese CDN company called Funnull bought the Polyfill.io domain (the legitimate operator had let it lapse). They injected malware into the polyfill script that served from cdn.polyfill.io. The script was loaded by roughly 100,000+ websites, including high-profile properties. Cloudflare, Fastly, and Google had to ship CDN-level blocks. The polyfill JavaScript library itself was abandoned by its author Andrew Betts months earlier with a warning that nobody listened to.
December 2024: @solana/web3.js
Versions 1.95.6 and 1.95.7 of the Solana JavaScript SDK were compromised via a phishing attack on the maintainer. The attackers injected a function called addToQueue that exfiltrated private keys for any wallet that interacted with the library. $160,000 in crypto assets were stolen. Solana web3.js is a core SDK; thousands of dapps depend on it.
March 2025: tj-actions/changed-files (CVE-2025-30066)
A GitHub Action used in CI workflows was compromised. The attackers retroactively modified version tags to point at a malicious commit. The malicious code extracted secrets from the GitHub Actions runner memory and printed them in workflow logs. Affected: over 23,000 repositories. Leaked secrets included AWS keys, GitHub Personal Access Tokens, npm tokens, and private RSA keys. The attack was originally aimed at Coinbase, then expanded broadly. CISA issued an emergency advisory.
March 2025: Next.js CVE-2025-29927
This is not a supply chain attack, but a critical authorization bypass vulnerability in Next.js middleware itself. The vulnerability allows attackers to bypass middleware-based authentication by setting an internal header called x-middleware-subrequest. CVSS score: 9.1 (Critical). Affected versions span Next.js 11.x through 15.x. Vercel-hosted deployments were patched automatically. Self-hosted apps had to scramble.
The implication: anyone who put auth logic in Next.js middleware (a common pattern in many starter kits) had a critical auth bypass in production for as long as they were on an unpatched version. The pattern itself was the trap.
July 2025: eslint-config-prettier maintainer phished (CVE-2025-54313)
A maintainer of eslint-config-prettier was phished via a fake email from npnjs.com (a typosquatted domain mimicking npmjs.com, swapping the m for an n). The attacker published malicious versions 8.10.1, 9.1.1, 10.1.6, and 10.1.7. The malicious code ran on npm install via an install.js postinstall script that launched node-gyp.dll malware on Windows machines. Weekly downloads of the package: 31 million.
The same wave also compromised several other packages maintained by the same person.
September 2025: The chalk/debug attack (“Qix” compromise)
In what may be the largest-blast-radius npm supply chain incident in history, the npm maintainer “Qix” was phished on September 8, 2025. Attackers impersonated npm support and convinced him to enter credentials and a TOTP code on a fake site (npmjs.help). They then published malicious versions of 18 trusted JavaScript packages, including chalk, debug, ansi-styles, supports-color, and others, in a burst that lasted about 16 minutes.
Combined weekly downloads of the affected packages: over 2 billion.
The malicious payload was crypto wallet drainer code that hooked into browser fetch(), XMLHttpRequest, and window.ethereum to redirect transactions. The malicious versions were live for approximately 2 hours before takedown.
This was not a niche package. chalk is downloaded 300+ million times per week. debug is downloaded 400+ million times per week. They are dependencies of dependencies of dependencies. If you ran npm install during that 2-hour window, you got it.
September 2025: “Shai-Hulud” self-replicating npm worm
Two weeks after the Qix attack, a new attack pattern emerged. A self-propagating worm dubbed “Shai-Hulud” started compromising npm packages by harvesting credentials from infected developers’ machines and using those credentials to publish malicious versions of other packages those developers maintained.
It is the first npm worm in the wild. Hundreds of npm packages were infected during the initial wave. CISA issued another emergency advisory. The malware targeted Linux and macOS developer machines, stealing GitHub tokens, npm tokens, AWS keys, and GCP keys, then weaponizing them.
The conceptual leap: previous attacks compromised one package. Shai-Hulud spreads from package to package using the credentials it steals along the way. There is no analog to this in any other major package ecosystem.
November 2025: Shai-Hulud 2.0
A renewed wave of the same worm hit in late November 2025, with a more aggressive payload. The new version injects two files (setup_bun.js and bun_environment.js) into infected packages and uses a preinstall script for execution. Microsoft, Datadog, JFrog, Elastic, and Palo Alto all published incident response guides.
May 2026: @tanstack/react-router and 41 other TanStack packages
Eleven days ago, on May 11, 2026, 42 @tanstack/ packages were compromised*, with 84 malicious versions total. @tanstack/react-router alone has 12.7 million weekly downloads.
The attack vector was novel. The TanStack maintainers themselves did not have their credentials phished. Instead, the attackers exploited a pull_request_target workflow in the TanStack/router GitHub repository, used GitHub Actions cache poisoning across trust boundaries, and extracted an OIDC token from runtime memory of the Actions runner to bypass npm’s publish approval flow.
The malicious versions were live for approximately 20-26 minutes before an external researcher detected them. TanStack ran a public, transparent postmortem (linked in sources, worth reading). The takedown was fast. The architectural lesson is not.
This is the most recent major incident, eleven days old at time of writing. By the time you read this, there will likely be another.
The pattern
Step back. Look at the trajectory.
- 2021: ua-parser-js compromised. Wake-up call. Largely ignored.
- 2022: node-ipc protestware. Largely dismissed as a one-off.
- 2024: Polyfill.io. 100k+ sites hit. Industry-wide alarm.
- 2024: Lottie, Solana web3.js. Targeted, financial damage in the hundreds of thousands.
- 2025: tj-actions, Next.js CVE, eslint-config-prettier, Qix, Shai-Hulud worm. The attacks become structural.
- 2026: Mini Shai-Hulud variants automate the attack. TanStack hit at scale.
The trend line is bad and accelerating. Attackers got better, the supply chain got bigger, and the blast radius keeps growing along with both.
If you are deploying a Next.js or Nuxt or SvelteKit app to production in 2026 without taking aggressive supply chain hardening steps, you are running on luck.
Why npm specifically
This is the part that matters for the architectural conversation. Why does the JavaScript ecosystem keep getting hit, when other ecosystems do not at this scale?
Three structural reasons.
1. Deep transitive dependency trees
A typical Next.js app has 800-2000 transitive dependencies. A npm install pulls code from hundreds of different maintainers, most of whom you have never heard of.
goilerplate has 25 direct dependencies and 49 indirect, 74 modules in total (you can cat go.mod and count). All from a small set of well-known orgs and the Go standard library. Still a real attack surface, just an order of magnitude smaller and from a tighter set of trusted publishers.
When you depend on 2000 packages, you trust 2000 maintainers. Compromise any one of them and you are compromised. The attack surface is the product of dependencies times maintainers times attack vectors. JavaScript wins on every multiplier.
2. Postinstall scripts
npm runs arbitrary code on npm install. A package’s postinstall script can do anything: read your filesystem, exfiltrate environment variables, modify other packages on disk. This is by design.
go mod download runs no scripts. It downloads source code. The first thing that runs your code is go build, and even then it only runs your package’s main(), not arbitrary code from a transitive dependency.
This is a fundamental architectural choice. npm chose flexibility. Go chose safety.
3. The single-namespace flat registry
npm is a global namespace. Anyone can publish a package called anything (within the rules). Typosquatting is trivial. Maintainer changes are not visible to downstream consumers. Package hijacks are invisible until someone notices.
Go modules use Git-style import paths (github.com/owner/package). The namespace is a GitHub or GitLab repository. Compromising the package means compromising the source repo, which is logged, visible, and harder to do without leaving traces.
Both registries can be attacked. The npm registry’s structure makes attacks easier and faster.
Is Go actually safer
I want to be honest here, because this article has a strong opinion and you deserve a strong honest counterpoint.
Go is not immune. There have been Go supply chain incidents:
- February 2025: A malicious typosquat of BoltDB (
github.com/boltdb-go/bolt) was discovered. It had been live since November 2021. The Go module proxy cached it for over three years. - February 2025: Researchers documented other typosquatted Go modules cached in the module mirror.
- May 2025: Three malicious Go modules (
prototransform,go-mcp,tlsproxy) were published with disk-wiping payloads targeting Linux developer machines. - June 2025: GitLab caught a
qiniiu/qmgotyposquat of the legitimate MongoDB driver.
All real. All bad. None remotely on the scale of the npm Shai-Hulud worm or the chalk/debug Qix attack.
The key structural difference: every Go incident I know about is a typosquat. An attacker publishes boltdb-go hoping you typo’d boltdb. The blast radius is limited to developers who fall for the typo.
Almost every major npm incident is a hijack of a legitimate, widely-used package. The blast radius is every project that ever depended on that package, directly or transitively, in any version range matching the malicious release.
The difference between “we typosquat and hope” and “we own chalk and publish to 300 million weekly downloads” is multiple orders of magnitude.
Go has had some bad days. npm has had bad years.
“But everyone uses npm”
Most software vulnerabilities are exploited because the ecosystem normalized risky defaults. If “everyone uses npm” is the argument, “everyone gets hit by npm incidents” is the counter-argument.
You can pick a less risky ecosystem. You will be more boring at parties. You will also not lose sleep on a Tuesday because the maintainer of is-arrayish got phished.
What to do if you stay on JavaScript
If you cannot move (most teams cannot, on short notice), here is the minimum hardening:
- Pin exact versions. Use a lockfile. Never
npm installwithout--frozen-lockfilein CI. - Disable postinstall scripts. Run
npm install --ignore-scriptswhenever possible. Audit the few packages that genuinely need scripts. - Audit your dependency tree. Run
npm auditandsocket.devscans regularly. Treat warnings as critical. - Use a private registry mirror. Verdaccio or Nexus. Cache approved versions, block direct npmjs.com access in CI.
- Subscribe to advisories. GitHub Security Advisories, OSV, npm security alerts. Catch incidents in the first hour, not the first day.
- Rotate secrets liberally. Anything that touches CI is one compromised package away from being public. Rotate npm tokens, GitHub PATs, cloud keys on a schedule.
- Use scoped, internal-only packages where possible. Reduce your exposure to public-internet maintainers.
This is hours of work per week, ongoing. It is the cost of staying on the JavaScript ecosystem in 2026.
What it looks like on Go
A goilerplate-style Go SaaS has:
- 25 direct dependencies from well-known orgs (the templ author, AWS SDK, pgx, polar-go, Stripe SDK, Resend, Sentry, modernc/sqlite, golang.org/x, JWT, Goose, Goldmark, etc.) and 49 indirect, 74 total in
go.mod. go.sumprovides cryptographic integrity for every dependency version.- No
postinstallequivalent. Code runs only when you compile and run your code. - A single binary at deploy time. No surprise downloads of dependencies in production.
- The Go module proxy provides a stable cache. Tampering with a published version invalidates the hash.
govulncheckcan be wired into CI in 10 minutes to flag known CVEs.
The total time I spend on supply chain hardening for goilerplate: maybe 30 minutes a quarter. The total time I spent on supply chain hardening when running similar Node apps: 2-4 hours per week.
This is not because Go is magic. It is because the Go ecosystem made architectural choices that limit the blast radius of any single compromise.
The architectural bet
If you are starting a new SaaS in 2026, the architectural bet is this: pick an ecosystem where the worst plausible supply chain incident does not end your business.
For npm in 2026, the worst plausible incident is “a transitive dependency of your auth library gets hijacked, your customer database leaks, your business is over.” This has nearly happened multiple times in the last 12 months.
For Go in 2026, the worst plausible incident is “a typosquat fools a careless developer, the affected developer’s laptop gets wiped, the project recovers from git.” Bad day. Not business-ending.
The risk asymmetry is real. The blast radius asymmetry is real. The maintenance burden asymmetry is real.
You can still pick npm. People do. They just do it with their eyes open, with hardening hours budgeted, and with the knowledge that another major incident is coming in the next 90 days, with near certainty.
In one sentence
The npm ecosystem in 2026 is the most attacked software supply chain in history, the attacks are accelerating in frequency and sophistication, and the structural choices that made the ecosystem possible (transitive dependencies, postinstall scripts, flat namespace, global maintainer trust) are exactly what makes it hard to defend.
Pick your stack with that in mind.
In goilerplate
goilerplate is the Go boilerplate I built so this whole problem stops being yours.
A couple dozen direct dependencies, all pinned, all from trusted orgs. No npm install in the production pipeline. No node_modules to audit at 2am after the next npm phishing wave. Tailwind CLI runs at build time only, from a single standalone binary.
The next Shai-Hulud cannot reach a Go binary that never pulls from npm. That is the entire pitch. And when something Go-side does need attention (the rare typosquat, a new CVE in a dependency), fixes land in the repo fast because I run this for my own products.
$99 launch price (regular $199), once, vs 2 to 4 hours per week on npm supply chain hygiene for the life of the product, plus the small but real chance that one Tuesday morning ends your business. Read the docs, or compare it head to head with supastarter and ShipFast.
Sources
Real incidents, sourced:
- TanStack postmortem (May 2026): Postmortem: TanStack npm supply-chain compromise
- TanStack analysis: Mini Shai-Hulud Strikes Again: TanStack + more npm Packages Compromised, by Wiz
- Polyfill.io: Sansec original disclosure, Cloudflare’s automatic mitigation
- Solana web3.js: Solana npm Attack analysis on Mend, Help Net Security coverage
- tj-actions/changed-files: CISA advisory CVE-2025-30066, Wiz incident report, Palo Alto Unit 42 deep dive
- Next.js CVE-2025-29927: NVD detail, Datadog Security Labs analysis, JFrog deep dive
- eslint-config-prettier: Snyk incident report, Socket.dev analysis, Safedep deep dive
- chalk/debug Qix attack: Wiz analysis, Palo Alto breakdown, Hacker News coverage
- Shai-Hulud worm: CISA emergency advisory, Unit 42 deep technical, Sysdig analysis
- Shai-Hulud 2.0: Microsoft Security blog, Datadog 2.0 analysis, JFrog research
- Go module incidents: Three-year BoltDB backdoor on The Register, Socket coverage of Go module proxy abuse, Disk-wiping Go modules writeup
- Cross-ecosystem retrospective: A Retrospective Survey of 2024/2025 Open Source Supply Chain Compromises by Filippo Valsorda
If you are running on JavaScript in 2026, read all of the above before you push to production again.