Which OWASP list are we using?
OWASP publishes the Top 10 as an awareness document for developers. This guide uses the 2021 edition, whose category names and numbers are listed on the OWASP Top 10 site. OWASP now also hosts a newer edition on the same site, so check which version a scanner or checklist refers to before comparing results.
The list is about categories of risk, not individual bugs. Treat it as a checklist for the questions to ask of any app, whoever or whatever wrote it.
The full mapping
The right-hand column is my analysis of how each category typically shows up when a model writes the code. It is a judgment, not a measured frequency.
| Category | How it shows up in AI-generated code |
|---|---|
| A01:2021 Broken Access Control | Routes check login but not ownership; database tables left open; admin actions guarded only in the interface |
| A02:2021 Cryptographic Failures | Sensitive data in plain text, homemade encryption, weak hashing for passwords, keys hard-coded |
| A03:2021 Injection | Queries built by joining strings with user input instead of parameters |
| A04:2021 Insecure Design | No rate limit on login or OTP, trust in client-side prices, no thought given to abuse of a feature |
| A05:2021 Security Misconfiguration | Permissive CORS, public buckets, debug mode on, default credentials, verbose errors |
| A06:2021 Vulnerable and Outdated Components | Unreviewed or stale packages installed to solve a problem |
| A07:2021 Identification and Authentication Failures | Weak session handling, predictable reset links, no lockout, tokens kept in insecure places |
| A08:2021 Software and Data Integrity Failures | Unsigned webhooks accepted as genuine, scripts loaded from untrusted sources, unchecked updates |
| A09:2021 Security Logging and Monitoring Failures | No record of who did what, so an exposure goes unnoticed |
| A10:2021 Server-Side Request Forgery (SSRF) | A feature that fetches a URL supplied by the user, such as link previews or image imports, with no destination limits |
Deep example 1: Broken Access Control (A01)
Broken Access Control is first on the 2021 list, and it is the failure that generated code commits most naturally. The model writes a route that fetches an order by ID. The user is logged in, so the request passes. Nothing checks whose order it is.
The vulnerable pattern loads a record using only the ID from the request. The fix scopes the query to the authenticated user, and returns the same not-found response whether the record is missing or belongs to someone else, so the response does not confirm which IDs exist.
app.get("/api/orders/:id", requireUser, async (req, res) => {
const order = await db.orders.findById(req.params.id);
if (!order) return res.status(404).end();
res.json(order);
});The corrected access-control route
The corrected version identifies the user from the verified session on the server, never from a field the client sends, and includes that identity in the query itself. OWASP's authorization guidance describes the underlying principle: validate permission on every request and deny by default.
app.get("/api/orders/:id", requireUser, async (req, res) => {
const order = await db.orders.findOne({
id: req.params.id,
userId: req.user.id,
});
if (!order) return res.status(404).end();
res.json(order);
});Deep example 2: Injection (A03)
Injection happens when user input becomes part of a command the system executes. Models write it when asked for a quick search endpoint, because joining strings is the shortest code. OWASP's cheat sheet states that SQL injection is best prevented with parameterized queries, which keep the query structure separate from the data.
app.get("/api/products", async (req, res) => {
const term = req.query.q;
const rows = await pool.query(
"SELECT id, name FROM products WHERE name ILIKE '%" + term + "%'"
);
res.json(rows.rows);
});The corrected injection example
Pass the value as a parameter. The database driver sends the query and the value separately, so the input can never change the query's structure. Add basic validation on top, but treat the parameter as the actual defense.
app.get("/api/products", async (req, res) => {
const term = String(req.query.q ?? "").slice(0, 100);
const rows = await pool.query(
"SELECT id, name FROM products WHERE name ILIKE $1",
["%" + term + "%"]
);
res.json(rows.rows);
});Deep example 3: Server-Side Request Forgery (A10)
SSRF appears when your server fetches a URL a user supplies. Typical features are link previews, avatar imports from a URL or webhook testers. If the destination is unrestricted, a user can make your server request addresses only the server can reach, such as internal services or cloud metadata endpoints.
OWASP's prevention guidance recommends an allowlist as the primary defense: match the host against approved destinations and build the request yourself, rather than accepting a complete URL from the user. Deny-lists are described as bypass-prone.
app.post("/api/import-avatar", requireUser, async (req, res) => {
const response = await fetch(req.body.url);
const data = await response.arrayBuffer();
await saveAvatar(req.user.id, Buffer.from(data));
res.status(204).end();
});The corrected SSRF example
The fix accepts only HTTPS URLs whose host is on a short list you control, and refuses redirects so an approved host cannot bounce the request elsewhere. If you truly need arbitrary destinations, that is a design decision to review with a security professional, not something to solve with a quick filter.
const ALLOWED_HOSTS = new Set(["avatars.example-cdn.com"]);
app.post("/api/import-avatar", requireUser, async (req, res) => {
let target;
try {
target = new URL(req.body.url);
} catch {
return res.status(400).json({ error: "Invalid URL" });
}
if (target.protocol !== "https:" || !ALLOWED_HOSTS.has(target.hostname)) {
return res.status(400).json({ error: "Host not allowed" });
}
const response = await fetch(target, { redirect: "error" });
if (!response.ok) return res.status(502).end();
const data = await response.arrayBuffer();
await saveAvatar(req.user.id, Buffer.from(data));
res.status(204).end();
});Using the list without being overwhelmed
For a small team, the practical move is to rank the categories by your exposure. If your browser talks to a database, start with A01 and A05. If you take payments, add A04 and A08. If your app fetches URLs or accepts uploads, add A10. Then review the rest as capacity allows.
Whichever categories you pick, test them against your own app and re-check after every generated feature. A checklist only helps if someone runs it.
Frequently asked questions
Does AI-generated code have more vulnerabilities than human code?
I am not aware of a settled, reliable answer, and I will not quote a number. What is clear is that AI tools reproduce the same categories OWASP already lists and rarely flag them, so unreviewed generated code needs the same checks as any code.
What is the number one OWASP risk for web apps?
In the 2021 edition, A01 Broken Access Control is first. It covers users acting outside their permissions, such as reading other people's records. It is especially common in AI-built apps because a working preview never reveals missing ownership checks.
Is there a newer OWASP Top 10 than 2021?
OWASP's site now also presents a newer edition alongside the 2021 list. The 2021 categories are still widely referenced in tools and audits, so confirm which version a document or scanner uses before comparing results.
Can I fix these issues just by asking the AI to fix them?
Sometimes, but verify. Ask for a specific fix, such as scoping queries to the signed-in user, then test with two accounts. A model can claim a fix and leave a gap, so the test matters more than the reassurance.