You wrote maybe 2,000 lines of code. Your node_modules has 80,000 files from 1,400 packages you never read. Most of them are fine. One of them is not, and the fix landed three months ago in a minor version you never pulled.
Vulnerable and outdated components means you are running code with known bugs in it. Not bugs you wrote — bugs in the libraries you imported, or the libraries those libraries imported. The fix usually exists. It just hasn't reached your package.json yet. Modern apps pull in thousands of transitive dependencies, so the surface area is enormous.
What your AI actually built
You asked for a markdown editor, a PDF renderer, a date picker — something real. The model reached for the obvious npm package, wired it up, and everything worked. Nice. Shipped.
What it didn't do was pin the version, check the changelog, or notice that the package was last updated in 2021 and has three open CVEs in its transitive deps. Models tend to suggest whatever was popular in the training corpus. Popularity and freshness are not the same thing.
The second trap is copy-paste from old Stack Overflow answers. A snippet that says 'install lodash@4.17.11' still runs perfectly. It also still has the prototype-pollution CVE that was patched in 4.17.12.
How it gets exploited
The attacker runs a scanner against your deployed app and reads the response headers.
- 1Fingerprint the stackThe X-Powered-By header, a JS bundle path, and a favicon hash are enough to guess Next.js 13.2 and a specific image library.
- 2Cross-reference CVEsThey paste the versions into the public CVE database. Two hits. One is critical with a working proof-of-concept on GitHub.
- 3Run the PoCThey clone the exploit repo, change the URL to yours, and run the script. It works on the first try because the PoC was written against the exact version you shipped.
- 4Land and pivotThe exploit gives them a shell in your Node process. From there, environment variables, database URL, and session secrets are all one env read away.
The attacker compromised your server using a public exploit against a library you never knowingly installed. The fix had existed for months.
Vulnerable vs Fixed
// package.json
{
"dependencies": {
"next": "13.2.0",
"sharp": "^0.30.0",
"lodash": "4.17.11",
"marked": "^2.0.0"
}
}
// No lockfile committed. No audit step.
// No Renovate, no Dependabot, no CI check.// package.json
{
"dependencies": {
"next": "14.2.15",
"sharp": "0.33.5",
"lodash": "4.17.21",
"marked": "14.1.3"
},
"scripts": {
"preinstall": "npx only-allow yarn",
"audit": "yarn npm audit --severity high"
}
}
// Lockfile committed. Renovate runs weekly.
// CI fails the build on high-severity advisories.The fix is not a code change — it's a process change. Pin versions, commit the lockfile, and let a bot open PRs when something ships a CVE. The hard part is deciding to care before a scanner tells you to.
A real case
Log4Shell turned one log line into remote code execution
In December 2021 a single CVE in log4j gave attackers RCE on millions of servers worldwide. Most victims had no idea log4j was even in their dependency tree.
Related reading
References
Find the stale dependency before the scanner bot does.
Flowpatrol fingerprints every library your app ships and flags the ones with known CVEs. Five minutes. One URL.
Try it free