OWASP Top 10:2025 — A03 Software Supply Chain Failures for AI-Generated Code

Written by the Rafter Team

Last month a founder shipped a "scrape and summarize" feature their CLI agent built in twenty minutes. The agent picked a package whose name was one letter off from a popular HTML parser, installed it without asking, wrote five lines of code around it, and moved on. The package shipped an info-stealer in its post-install script. The founder noticed when their AWS bill turned a color it should not turn. This is A03. The category renamed itself from "Vulnerable and Outdated Components" to Software Supply Chain Failures in the 2025 list precisely because the failure mode is no longer just "you forgot to upgrade Log4j" — it's "your AI assistant trusted a name it had never seen, in an ecosystem where names are squatted weekly."
What A03 actually is
A03 is every way a third-party piece of code ends up in your app without anyone verifying that the code is what it claims to be. Typosquatted npm packages (reqeusts, cross-env.js, chalk-next), abandoned libraries the original maintainer handed off to a stranger, malicious updates pushed after a maintainer's account got phished, install scripts that exfiltrate ~/.aws/credentials the first time you npm install. Concrete example: in late 2024 the lottie-player npm package was hijacked and a single minor-version bump dropped a wallet-drainer into every site that pinned to ^ or ~. Anyone who ran npm install between the push and the unpublish got compromised. They didn't write a vulnerable line of code. They installed one.
Why AI-generated code trips on it
Three patterns we see constantly in scans of vibe-coded apps:
The agent invents package names. LLMs hallucinate imports. They'll happily write import { parseJWT } from "jsonwebtoken-utils" because it sounds like it should exist. If a squatter has registered that exact name (and they do, automatically, by scraping LLM outputs), the install succeeds and you ship malware.
The agent picks the first thing that compiles. Asked to add image processing, the model reaches for whatever package was popular in its training data, which is often two years old, often abandoned, and often has known CVEs the model has never heard of. Sharp, jimp, gm — the agent doesn't know which one is currently maintained, and it won't tell you it doesn't know.
Lockfiles get regenerated. Every time you say "fix the build" and the agent runs rm -rf node_modules package-lock.json && npm install, you've thrown away pinning and re-resolved every transitive dependency to whatever's latest. Any compromised version published in the window between your last working state and now is now in your tree. Next.js, Vite, and anything else with a large transitive surface is the worst offender here.
The fix on agentic CLIs (Claude Code, Codex)
Two habits will eliminate most of this:
- Read the diff for every new import. Not the code the agent wrote — the line that says
+ import x from "y". If you don't recognizey, stop and search npm for it before you accept the change. - Pin and audit on every change. Commit your lockfile. Run
npm audit --omit=dev(orpnpm audit,pip-audit,cargo audit) after every dependency change, and make the agent fix what it surfaces.
A prompt that works well to retrofit this onto an existing project:
Audit every dependency added in the last 30 days against npm's
registry. For each, tell me: (1) weekly download count,
(2) date of most recent release, (3) whether the package name is
within edit-distance 2 of a more popular package, (4) whether any
post-install or preinstall script is declared. Flag anything
suspicious. Do not install or update anything. Output a table.
When Rafter flags a vulnerable or suspicious dependency, the Copy-for-AI button on each finding produces a fix-ready prompt you can paste straight into Claude Code or Codex — it includes the package, the CVE, the safe version range, and instructions to update the lockfile without regenerating the whole tree.
The fix on opinionated platforms (base44, Greta, OpenClaw, Replit-style builders)
You usually can't npm audit on these. The platform handles installs for you, which is the convenience and also the problem — you have no idea what versions of what packages are in your runtime, and you can't pin them.
What you can do:
- Ask the platform's agent for an inventory. A prompt like "List every third-party package or library currently used by this app, with version numbers" gets you something. If the platform refuses or can't answer, that's a finding — escalate it as a feature request, and treat that app as higher-risk until you have visibility.
- Refuse new packages by default. When the agent says "I'll add the
xyzlibrary to handle this" — push back. Ask if it can be done with what's already imported. Most of the time it can, and you've avoided adding a name to your trust list. - Push compromised-version detection upstream. If your platform doesn't tell you when a package in your app is on a published advisory, that's the single most important security feature to ask for. Until they ship it, scanning the exported source with Rafter (most of these platforms let you export a zip or push to GitHub) is the workaround.
The honest version: opinionated platforms are easier to compromise via supply chain than a self-managed CLI project, because the abstraction hides the install step. Trust accordingly.
See also
- A02 Security Misconfiguration — the other category where "the default the LLM picked" becomes your security model.
- A08 Software or Data Integrity Failures — the closest sibling to A03, covering unsigned updates and CI/CD pipeline tampering.
- A06 Insecure Design — because "the agent picked a package" is, structurally, a design decision no human made.