Al Karakas
← All essays

Security · AI Building

OWASP Top 10 for Vibe Coders: The Security Checklist Your AI Won't Write For You

16 min read

The average data breach now costs $4.44 million globally, and $10.22 million in the United States. Web application credential attacks are the number one breach vector. 68% of breaches involve a human element, usually someone doing something they did not know was unsafe. I shipped two production applications using Claude and Cursor, then ran OWASP ZAP and found fourteen medium-severity issues and three highs. None of them were things my AI warned me about while it wrote the code.

$4.44M
average cost of a data breach globally in 2025 (IBM Security). $10.22M in the United States, a record high
68%
of breaches involve a non-malicious human element, someone doing something they did not realise was unsafe (Verizon DBIR 2024)
#1
attack vector for data breaches: stolen web application credentials. Your login endpoint is the most targeted thing you have built (Verizon DBIR 2024)

I shipped Aplio, my job search intelligence platform, and pmly, a PM contractor operating system, using Claude and Cursor. Both are production systems. Both handle real user data. And about six months in, I ran OWASP ZAP against Aplio for the first time and stared at the output for ten minutes trying to understand what I was looking at.

Fourteen medium-severity findings. Three highs. Things I had not thought about once.

This is the article I wanted to read before I started building. It is not written by a security engineer. It is written by a Senior PM who builds production software with AI assistance, has shipped real things, and learned the hard way that "my AI helped me build it" is not a security posture.

What vibe coding actually is

Vibe coding is the practice of building production-grade software using AI assistance, often by people who do not have a formal software engineering background. You describe what you want. The AI writes the code. You test it, iterate, ship it. The tools are Claude, Cursor, GitHub Copilot, v0. The practitioners are founders, PMs, operators, people who have a problem and a product instinct and now, for the first time, the ability to build a solution without hiring a team.

It is genuinely remarkable. I built two production systems in months that would have taken me years or a hired team otherwise. But there is a specific failure mode that vibe coding introduces that I did not appreciate until I ran that scan: the AI writes the code you asked for. It does not write the security you forgot to ask for.

Why your AI assistant won't save you

Language models are trained to produce code that works. That is a different objective from producing code that is safe.

Ask Claude to write a login endpoint and it will write a login endpoint. It will handle the happy path, probably handle some error cases, and generate something that passes your manual test. What it will not do, unless you ask explicitly and specifically, is tell you that you have no rate limiting on that endpoint, that your session tokens are stored in localStorage instead of a httpOnly cookie, or that you have not thought about timing attacks.

The model is optimising for completing your request. Security is a constraint orthogonal to the request, and constraints you do not state are constraints the model does not enforce.

I have found SQL injection vulnerabilities in code Claude wrote for me. Not because the model is bad. Because I asked for a query that worked and I got a query that worked. The parameterisation question was one I needed to think to ask.

The OWASP Top 10 is a list of the most critical web application security risks. They are not exotic. They are the things that get production applications breached every day, and most of them are things your AI assistant will happily not mention while it builds your app.

The OWASP Top 10 for Vibe Coders

1. Broken Access Control

This is the number one OWASP risk and the one I have seen most often in vibe-coded apps. Access control means: who is allowed to do what, and does your system enforce that on the server side, every time, for every request.

The failure mode looks like this. Your frontend hides the delete button from regular users. You tested it. It works. But the delete API endpoint has no server-side check to confirm the user making the request is actually allowed to delete the resource. Someone sends the request directly, bypassing your UI entirely, and it works.

Check this

Pick three sensitive operations in your app. Send the API requests yourself, using a different user's credentials or no credentials at all. What happens? If anything succeeds that should not, you have a broken access control problem. ZAP will also probe this by replaying authenticated requests without cookies.

2. Cryptographic Failures

Formerly called "Sensitive Data Exposure." The name change was deliberate: the root cause is usually a cryptographic failure, not a disclosure accident.

The practical failure modes: storing passwords as plaintext or with weak hashing (MD5, SHA1), transmitting sensitive data over HTTP instead of HTTPS, using weak or hardcoded encryption keys, storing API keys and secrets in your frontend code or in your Git repository.

For vibe coders, the most common issue is secrets in code. The AI generates a working example with a hardcoded API key, you run it, it works, you ship it, and the key is now in your public repository. GitHub's secret scanning will catch some of this retrospectively, but "retrospectively" is after the damage.

Check this

Run git log -p | grep -i "api_key\|secret\|password\|token" against your repository history. Check your .gitignore confirms .env is listed before any key was ever committed. OWASP ZAP will flag any cookies without the Secure and HttpOnly flags, and any content loaded over HTTP on an HTTPS page.

3. Injection (SQL, XSS, Command)

SQL injection has been the top vulnerability class for two decades. It should be solved by now. It is not, because every new generation of builders rediscovers it.

The mechanism is simple: if you build a database query by concatenating user input into a SQL string, an attacker can close the string early and inject their own SQL. XSS (cross-site scripting) is the same principle applied to HTML output: if you render user-controlled content into a page without escaping it, an attacker can inject scripts that run in other users' browsers.

With AI-generated code, injection vulnerabilities usually appear in data retrieval functions. The model writes a query that works for the expected input. It does not automatically parameterise the query unless you have established that as a pattern in your codebase or explicitly asked for it.

Check this

Find every place your code builds a database query, shell command, or HTML output. Are user inputs ever concatenated directly into those strings? Use parameterised queries everywhere, prepared statements, ORMs that handle this by default, and output encoding for anything rendered to HTML. ZAP's active scan will attempt SQL and XSS injection automatically and report successes.

4. Insecure Design

This one is different from the others. Every other item on this list is a specific vulnerability class. Insecure design is a structural problem: security was not considered at the architecture stage, and as a result the system has flaws that cannot be patched away because they are baked into the design.

The vibe coding version: you built the feature, it works, you shipped it. But you never thought about what happens when an attacker tries to misuse it systematically. No threat model. No consideration of trust boundaries. No rate limiting designed in from the start.

A concrete example from my own builds: Aplio sends a morning email digest. I built the endpoint. It worked. What I did not initially think about was whether that endpoint could be triggered by anyone, repeatedly, from outside the system. It could be, until I added authentication middleware.

Check this

For each of your key features, spend ten minutes as an adversary. Who could abuse this if they tried? What stops them? Rate limiting, authentication, authorisation, input validation, abuse detection, account lockout. If the answer to "what stops them" is "nothing," the design is insecure. No tool catches this automatically. It requires deliberate thought.

5. Security Misconfiguration

This is where OWASP ZAP earns its keep for vibe coders, because misconfiguration is what the tool is best at detecting.

The most common forms: leaving debug mode enabled in production, using default credentials on infrastructure or admin panels, exposing stack traces in error responses, missing or incorrect HTTP security headers, leaving directory listing enabled on your server, leaving development endpoints accessible in production.

HTTP security headers alone account for a large fraction of ZAP findings on vibe-coded apps. Headers like Content-Security-Policy, X-Frame-Options, Strict-Transport-Security, and X-Content-Type-Options are not set by default on most frameworks. They are one-time configuration tasks that make a meaningful difference.

Check this

Run ZAP's passive scan and look at the Alerts panel, specifically anything tagged as a missing security header. Then check your production environment: is debug mode off? Are error messages generic, not stack traces? Are any admin panels or development tools exposed? Are default passwords changed on all infrastructure? Check securityheaders.com with your production URL for a fast header audit.

6. Vulnerable and Outdated Components

You did not write your entire stack. Neither did I. Aplio uses rss-parser, wink-distance, Express, React, and about 180 other npm packages. Each of those packages can introduce vulnerabilities, and package vulnerabilities are patched on a cadence that is entirely disconnected from your development rhythm.

Log4Shell, the 2021 vulnerability in a Java logging library, was embedded in thousands of applications that had no idea they were using it, through transitive dependencies two or three layers deep. The same pattern applies at every scale.

Check this

Run npm audit right now if you have a Node.js project. It will report known vulnerabilities in your dependency tree with severity levels. Run it, read the output, fix the criticals and highs before you ship the next feature. Set up GitHub Dependabot or similar to automate this on a regular cadence. This takes thirty minutes to set up and saves you from the category of breach that happens through no action of your own.

7. Identification and Authentication Failures

This category covers how your app handles who a user is and proves it.

The common failures: no lockout or rate limiting on login attempts (allowing brute force), weak password requirements, no multi-factor authentication on sensitive actions, session tokens that do not expire, session tokens stored in places that can be stolen (localStorage is not appropriate for sensitive tokens), predictable or guessable session identifiers, password reset flows that can be abused.

If you used a third-party auth provider like Clerk, Auth0, or Supabase Auth, you have avoided many of these by default. If you built auth yourself with AI assistance, you need to check every item on that list.

Check this

Try logging in with invalid credentials one hundred times. Does your app stop you? Try using a password reset link twice. Does the second attempt work? Are session cookies marked HttpOnly and Secure? ZAP will probe for missing cookie flags and weak session management automatically.

8. Software and Data Integrity Failures

This is the category that covers supply chain attacks and insecure update mechanisms. When your application downloads updates, plugins, or data at runtime without verifying the integrity of what it is getting, an attacker who can modify the source can push malicious content directly into your users' browsers or your server processes.

For vibe coders, the most practical concern is JavaScript loaded from external CDNs without Subresource Integrity (SRI) checks. If you include a <script> tag pointing to an external library, and that external source is compromised, your users run the attacker's code. SRI lets you specify a hash of the expected file content so the browser refuses to execute anything that does not match.

Check this

Audit every external script and stylesheet your HTML loads. For any that are loaded from a CDN, add SRI hashes. Most CDN providers will generate these for you. Review your package.json to ensure versions are pinned, not wildcarded with ^ or ~ in ways that could pull breaking or malicious updates.

9. Security Logging and Monitoring Failures

You cannot defend against what you cannot see. This category is about whether your application generates logs that would let you detect an attack in progress, and whether anyone is watching those logs.

The failure mode: something bad happens. You find out weeks later, either from a user complaint or from reading about it in your error tracker. By then the attacker has had unimpeded access for an extended period.

Effective security logging means recording authentication events (successes and failures), access control failures, input validation failures, and anomalous patterns like many requests in a short period from the same IP. Those logs need to be stored somewhere separate from the application, with enough retention to be useful in a post-incident investigation.

Check this

Check whether your application logs failed login attempts. Check whether it logs when someone attempts to access a resource they are not authorised for. Check whether those logs are shipped somewhere durable. If none of this exists, it is a weekend's work to add structured logging and route it to a service like Datadog, Papertrail, or your cloud provider's native offering.

10. Server-Side Request Forgery (SSRF)

This is the one that surprises people who have not encountered it before. SSRF is a vulnerability where your server makes HTTP requests based on input provided by a user, and an attacker can manipulate that input to make your server request internal resources it should not be able to reach.

The most dangerous target: cloud provider metadata endpoints. On AWS, the URL http://169.254.169.254/latest/meta-data/ serves instance credentials to anyone who can reach it from inside the instance. If your application makes HTTP requests to URLs that users supply, an attacker can supply that URL, your server will fetch it, and you have handed over your cloud credentials.

Vibe-coded apps that fetch external URLs (importing an RSS feed, previewing a link, loading a user-supplied image URL) are exposed to this if they do not validate and restrict the URLs they will follow.

Check this

Find every place in your application where the server makes an outbound HTTP request based on user input. Ask: could a user supply an internal IP address, a localhost URL, or a cloud metadata URL? If yes, add allowlists and block requests to private IP ranges. ZAP's active scan probes for SSRF on input fields.

How to actually run OWASP ZAP

ZAP (Zed Attack Proxy) is free, open source, and maintained by OWASP. Download it from zaproxy.org. It runs on macOS, Windows, and Linux.

Start your application locally. Open ZAP and select "Automated Scan" from the Quick Start tab. Enter your local URL, click Attack. ZAP will crawl your application and run a battery of active tests against every URL it discovers. When it finishes, open the Alerts tab. Findings are sorted by risk level: High, Medium, Low, Informational.

Read the High findings first. Each alert includes a description of the vulnerability, the URL where it was found, the evidence ZAP collected, and a recommended solution.

Run ZAP against your local or staging environment, not against production. The active scan makes real requests, including requests designed to trigger errors.

What to do with the results

Do not panic. When I first saw my results I thought I needed to rewrite Aplio from scratch. I did not. Fix the Highs first: SQL injection, XSS, broken authentication, SSRF. These are exploitable now, by a script, by anyone who runs a basic scan. Address the Mediums next. Missing security headers are mostly middleware configuration, a focused afternoon's work. Lows are worth understanding but rarely the critical difference.

The discipline is iteration, not perfection. Run ZAP again after you fix the Highs. Recheck after your next feature release. Security is not a state you achieve once. It is a standard you maintain.

Security is not a phase

Security is not a phase you do at the end of the build. By the time you have fourteen findings on a ZAP report, most of the work to prevent them was work that needed to happen at the start: in how you configured your server, set up your authentication, structured your database queries, and chose what to log.

The defaults your AI assistant produces are optimised for function. The defaults you set up intentionally are what determine whether your application is safe. Run the scan. Read the report. Fix the criticals. Do not wait for someone else to find these for you.

When you have worked through your findings and want to go deeper, Burp Suite Community Edition is the professional tool for the same job, with more manual testing capabilities and an interface that rewards investment.