Open the node_modules folder of almost any AI-built app and you will find somewhere between 400 and 1,500 packages. Nobody chose most of them. They arrived because one library needed another, which needed another, and so on, until a simple contact form was quietly standing on top of a tower of other people’s code. That tower is where AI hallucinated packages and other dependency problems hide, and it is one of the least visible risks in a vibe-coded app, because nothing about it looks broken. The app runs. The bug only shows up when someone goes looking, or when an attacker does first.
We have opened enough of these folders to know the pattern. AI coding tools install freely and rarely think twice about what they are pulling in. Below is what we actually find, and what to do about it without turning dependency management into a second job.
What dependencies are and why your app has hundreds
A dependency is code someone else wrote that your app borrows instead of rewriting. You install a package to handle dates, another to send emails, another to validate forms. Sensible enough on its own. The problem is that each of those packages has its own dependencies, and those have dependencies too. A single npm install for “a library to resize images” might drag in sixty other packages you never asked for and will never read.
AI tools make this worse because they default to convenience. If a prompt needs “a way to parse CSV files,” the model will often reach for a popular package rather than fifteen lines of code that would do the same job with zero extra risk. That is not always the wrong call, but it compounds. Across a few dozen prompts, an app accumulates a dependency tree nobody designed on purpose. We have written before about why AI coding tools keep making the same mistakes, and this is one of the quieter ones: the model optimises for “does this run,” not “should this many strangers’ code be running inside a payments flow.”
Outdated packages with known vulnerabilities
Most of the risk in that tree is not exotic. It is old. AI models are trained on snapshots of code from the past, which means they sometimes suggest a package version that was fine when the training data was collected and has since had two or three security advisories filed against it. The model has no way of knowing that, because it is not checking an advisory database when it writes "package": "^2.1.0" into your package.json. It is pattern-matching on what it has seen before.
The result is apps shipped today running libraries with known, documented, publicly searchable vulnerabilities. Sometimes it is a logging library with a remote code execution flaw. Sometimes it is an authentication helper with a token-validation bug that was patched eighteen months ago in a version the app never upgraded to. None of this shows up during a demo. It shows up when someone runs a scanner against the live site, and by then it is a disclosure conversation rather than a quiet patch. If you have not had someone check whether your app is exposed this way, our vibe code security guide is a reasonable place to start before you assume you are fine.
Hallucinated package names and slopsquatting
This is the part that genuinely surprises founders, so it is worth explaining properly. Large language models sometimes invent package names that sound plausible but do not exist. Ask a model to fix an import error and it might suggest pip install data-utils-pro or npm install react-form-validator-plus, names that feel like they should be real because thousands of similarly named packages are. They are not real, until an attacker notices the pattern and registers them.
That is slopsquatting: publishing malicious packages under names that AI tools are statistically likely to hallucinate, then waiting. A developer, or more often an AI coding assistant working unsupervised, installs the fake package without checking, and the attacker’s code runs inside the app from that point on. Researchers have found this happens often enough that it is now a recognised category of supply-chain attack, not a theoretical one. It works precisely because the install step feels routine. Nobody double-checks a package name the way they would double-check a wire transfer, and AI tools do not pause to ask “does this actually exist and is it trustworthy,” they just run the install command. This is also why we keep saying that a human should be reading what the AI produces, not just what it outputs on screen. Our piece on AI code review tools versus human review covers where automated scanning stops being enough.
Free tools that scan for you
You do not need to read five hundred package names to catch most of this. A few free tools do the heavy lifting.
npm audit is built into npm and will flag known vulnerabilities in your installed packages with a severity rating and, usually, a fix command. Run it from your project folder and read the summary even if you do not understand every line. GitHub’s Dependabot does something similar automatically if your code lives on GitHub, opening pull requests when a dependency has a patch available. Socket and Snyk both offer free tiers that go a step further, flagging packages with suspicious behaviour, unusual maintainer changes, or install scripts that reach out to the network, which is exactly the kind of thing a slopsquatted package does. None of these require you to understand the code. They require you to run a command and read a report, which is a fair ask even for a non-technical founder.
The honest caveat: these tools tell you what is wrong, not whether fixing it will break something else. That judgement call is where a lot of founders get stuck, which is fair, because guessing wrong here can take a working app offline.
A sensible update routine that won’t break everything
The instinct after reading all this is often to update everything immediately. Do not. A dependency tree built by an AI tool over several months of prompts is fragile in ways that are hard to predict, and a blanket npm update can break three features to fix one vulnerability.
What actually works is smaller and duller. Run a vulnerability scan monthly, not daily, so you are not chasing noise. Fix critical and high severity issues first and leave low severity ones for the next scheduled pass. Update one package at a time when you can, test the specific feature it touches, and commit before moving to the next. If a package has not been updated by its maintainer in over two years, treat that as a flag worth asking about even if no vulnerability has been filed yet, because abandoned packages are exactly the ones attackers look to take over. And before you add anything new, a five-second check that the package actually exists and has a real download history costs nothing and closes off slopsquatting entirely.
None of this needs to be glamorous. It needs to happen on a schedule, by someone who checks the result rather than trusting the green tick.
Not sure what’s inside your node_modules? We’ll audit it. Free assessment. Get in touch and we’ll tell you exactly what’s outdated, what’s risky, and what’s safe to leave alone.