← Posts
Security · Vibe Coding

Security in vibe coding: 19 checks before you ship.

AI writes working code in minutes; it doesn't write secure code. Backed by research and real incidents from 2025, the 19 steps to check before you put an application live.

In February 2025, Andrej Karpathy talked about something he called "vibe coding": building software by describing what you want to an AI and accepting the result without looking at the code. Within a year this became the way not just weekend projects, but products with real users, get built.

Here's the problem: AI is very good at writing code that works. The login screen opens, the form saves, the payment page loads. But the distance between "it works" and "it's secure" is as large as the distance between a demo and a live product. And since nobody writes that distance into the prompt, the model doesn't close it either.

In this post I've grouped the 19 steps I check before putting an application live into six layers. For each one, you'll find what AI typically does, what you should do, and a sentence you can add to your prompt. Let's start with the numbers.

By the numbersHow secure is the code AI writes?
45%Share of output with a security flaw in coding tasksVeracode · 100+ models
86%Share of cases where code secure against XSS wasn't producedVeracode · CWE-80
19.7%Share of suggested packages that don't actually existUSENIX Security 2025
6.4%Secret leak rate in public repos using Copilot (overall average 4.6%)GitGuardian 2025
2,000+Vulnerabilities found in 5,600 vibe-coded apps; 400+ exposed secretsEscape.tech
$4.44MGlobal average cost of a data breachIBM 2025
Sources at the end of the post. The Veracode rate is calculated over 80 tasks × more than 100 large language models.

Why isn't "it works" enough?

A language model is trained to produce what it's asked for. When you say "let users log in", you get a screen where users can log in. Unless you say "limit login attempts, store passwords with Argon2, and don't reveal whether a username exists on a failed login", those things most likely won't come. Security is a requirement nobody asks for but everyone assumes.

Veracode's 2025 report showed this is measurable: when they could choose between a secure and an insecure path, models chose the insecure one in 45% of cases. More striking still, while models wrote syntactically better code every year, their security success stood still.

There's a human side too. A team from Stanford had participants solve the same security tasks with and without an AI assistant:

Stanford · ACM CCS 2023Those using an assistant wrote less secure code, and thought their code was more secure
With an AI assistant
  • Noticeably less secure code
  • More likely to believe their code was secure
  • Fewer flaws among those who trusted the assistant less and refined their prompts
Without an assistant
  • More secure solutions
  • More cautious about their own code
  • The same five tasks; Python, JavaScript and C
Perry, Srivastava, Kumar, Boneh · "Do Users Write More Insecure Code with AI Assistants?" · CCS '23

The most dangerous point is the second one: overconfidence. Because the code arrives fast, we assume it arrived correct. 2025 showed the price of that assumption plainly, more than once.

2025What happened in one year
  1. MarchEnrichLead shut down

    A SaaS product whose founder announced he had built it "without writing a single line of code by hand" was attacked shortly after: API keys were in the front end, the subscription check was bypassed, and the keys' quotas were drained. The product was shut down.

    Secret management · Authorization · Rate limiting
  2. MayCVE-2025-48757 · Lovable

    Missing Row-Level Security policies in generated apps let unauthenticated people read and write tables. Of 1,645 apps one researcher scanned, about 170 had their data exposed.

    IDOR · Database security
  3. JulyA Replit agent deleted a production database

    Despite an instruction "not to make changes without approval", the agent deleted a database containing 1,206 executive records and more than 1,196 company records, then said rollback was impossible. The data was restored from backup.

    Database security · Backup
  4. JulyThe Tea app

    About 72,000 images leaked from a Firebase storage bucket left open to the public; about 13,000 of them were verification selfies and ID photos. A second dataset containing 1.1 million private messages surfaced afterwards.

    File upload · Authorization
  5. 2025 · ResearchEscape.tech: 5,600 apps

    They passively scanned more than 5,600 public apps built with vibe coding platforms: more than 2,000 vulnerabilities, more than 400 exposed secrets and 175 exposures of personal data (health records, IBANs, phone numbers).

    All of them
NVD · OECD AI Incidents · BleepingComputer · Escape.tech

None of these incidents was a zero-day or a state-sponsored attack. Each was the absence of one or more of the basic checks in the list below.

19 checks, 6 layers

Rather than memorising the list item by item, it helps to think in layers. Even if one layer is bypassed, another should stand behind it.

MapLayers of defence
01Identity and accessWho can access what?AuthenticationAuthorizationIDOR / ownership
02Input and outputDon't trust incoming dataInput validationSQL injectionXSSFile upload
03Browser and transportIn transit and on the clientHTTPSSecure cookiesCORSSecurity headers
04Application behaviourLoad, errors, tracesRate limitingGlobal exception handlingLogging / audit
05Data and secretsThe most valuable assetsSecret managementDatabase securityBackup
06Supply chainOther people's codeDependency scanningCI/CD security scanning

This list isn't random. Every item on OWASP's Top 10, published in November 2025 and based on data from more than 2.8 million applications, is covered by at least one of these 19 checks:

OWASP Top 10 · 2025Which risk is closed by which check?
A01Broken Access ControlAuthorization · IDOR · CORS
A02Security MisconfigurationSecurity headers · Cookies · DB settings
A03Software Supply Chain FailuresDependency scanning · CI/CD
A04Cryptographic FailuresHTTPS · Secret management · Password hashing
A05InjectionInput validation · SQLi · XSS
A06Insecure DesignRate limiting · Ownership · File upload
A07Authentication FailuresAuthentication · Rate limiting · Cookies
A08Software or Data Integrity FailuresCI/CD · Backup · File upload
A09Security Logging & Alerting FailuresLogging / audit
A10Mishandling of Exceptional ConditionsGlobal exception handling
OWASP Top 10:2025. A03 and A10 became separate categories for the first time in this edition.

Layer 1 · Identity and access

01 Authentication

Authentication answers the question "who are you?". In Verizon's 2025 report, the most common starting point for breaches is still stolen or misused credentials (22%). In other words, the door usually isn't broken down; it's opened with a key.

AI usually

Writes the login system from scratch. Stores passwords in plain text, MD5 or unsalted SHA-256. Signs JWTs with a key like "secret" and no expiry. Reveals that an account exists by saying "this email isn't registered".

What you should do

Use the framework's built-in auth system or a mature provider. Store passwords with Argon2id or bcrypt. Enforce two-factor authentication on admin accounts. Put an expiry on sessions and keep error messages generic.

Don't write authentication from scratch; use the framework's built-in auth system. Hash passwords with Argon2id, add 2FA for admin accounts, and put time limits on sessions and tokens. On a failed login, only say "email or password is incorrect".

02 Authorization

Authentication tells you who someone is; authorization tells you what they can do. "Broken Access Control" was first on the OWASP list in 2021, and it's first again in 2025. The most common mistake in vibe-coded apps is doing the permission check only in the interface: the admin button isn't shown to normal users, but the endpoint is open to everyone. That's exactly how the paid features were bypassed at EnrichLead.

The rule is simple: every request is denied by default, on the server; what is explicitly allowed gets checked.

# Bad: the check lives only in the front end
@api_view(["DELETE"])
def delete_course(request, pk):
    Course.objects.filter(pk=pk).delete()

# Good: on the server, on every request
@api_view(["DELETE"])
@permission_classes([IsAuthenticated, IsCourseAdmin])
def delete_course(request, pk):
    course = get_object_or_404(Course, pk=pk, organization=request.user.organization)
    course.delete()

IBM's 2025 report shows this applies to AI systems themselves too: 97% of organisations that suffered a breach of an AI model or application had no AI-specific access controls.

For each endpoint, state explicitly who can access it, and do the check on the server. The default behaviour should be to deny. Hiding a button in the interface is not authorization.

03 IDOR and ownership checks

If you can open /api/invoices/1043, you can try 1044 too. If the server doesn't ask "does this invoice belong to this user?", you'll see someone else's invoice. This is called IDOR (Insecure Direct Object Reference), and it's one of the flaws automated scanners find hardest to catch and attackers find easiest.

# Bad: returns any invoice
invoice = Invoice.objects.get(pk=pk)

# Good: query limited to the owner; 404 for anyone else's
invoice = get_object_or_404(Invoice, pk=pk, owner=request.user)

Using UUIDs makes guessing harder, but it doesn't replace an ownership check. If the front end talks directly to the database (Supabase, Firebase), this check must be done at the database level with Row-Level Security. The Lovable case shows what happens when that policy is missing.

Limit every query that fetches a resource by ID to the user in the session (owner=request.user). Return 404, not 403, for a record that belongs to someone else. If you use Supabase, enable RLS on every table and write the policies.

Layer 2 · Input and output

04 Input validation

Validation in the browser is for user experience, not security. An attacker doesn't use your form; they send the request directly. Every input must be validated on the server for type, length, format and range. Use an allowlist where possible: not "what should I block?" but "what should I accept?"

A commonly skipped kind is mass assignment: a serializer that accepts all of a model's fields opens the door for a user to add "is_admin": true to the request.

class ProfileSerializer(serializers.ModelSerializer):
    class Meta:
        model = Profile
        fields = ["display_name", "bio", "avatar"]   # list them explicitly
        read_only_fields = ["role", "is_verified"]
Validate all input on the server with a schema (DRF serializer, Pydantic or Zod). List the accepted fields explicitly; don't use "__all__". Set length and range limits.

05 SQL injection protection

If you use an ORM, you're largely protected. AI, however, tends to switch to raw SQL exactly where the ORM isn't enough (dynamic filters, search and sorting) and to embed the value in the string.

# Bad: user input becomes part of the query
cursor.execute(f"SELECT * FROM products WHERE name LIKE '%{q}%'")

# Good: parameterised query
cursor.execute("SELECT * FROM products WHERE name LIKE %s", [f"%{q}%"])

# A sort column can't be a parameter: use an allowlist
order = {"price": "price", "date": "created_at"}.get(sort, "created_at")
If you need to write raw SQL, use parameterised queries only; no f-strings or string concatenation. Use an allowlist for ORDER BY and table names.

06 XSS protection

In Veracode's tests, writing code secure against XSS was where models struggled most: they failed in 86% of cases.

Veracode 2025Share of cases where models failed to produce secure code
Log injectionCWE-11788%
Cross-site scriptingCWE-8086%
All tasks in Java70%+
All tasksaverage of 100+ models45%
Veracode 2025 GenAI Code Security Report. In Python, C# and JavaScript the rate is between 38% and 45%.

Modern template engines (Django, Jinja, React) escape output by default. The flaw appears where you deliberately turn that protection off: |safe and mark_safe in Django, dangerouslySetInnerHTML in React, v-html in Vue, innerHTML in plain JavaScript. If you have to display rich text, sanitise the HTML with an allowlist (nh3 in Python, DOMPurify in the browser) and put a Content Security Policy behind it.

This blog works the same way: the Markdown I write is sanitised with nh3 before it reaches the page. Even if my admin account were taken over, a <script> couldn't be embedded in a post.

Never render user content into HTML without escaping it. Don't use innerHTML, dangerouslySetInnerHTML, v-html or |safe; if you must, sanitise with DOMPurify or nh3 first.

07 File upload validation

An "upload a profile photo" feature means giving people the right to write files to your server. Checking the extension isn't enough; photo.jpg.php, or an SVG with a script inside, gets through an extension check.

  • Validate the file's content (magic bytes, or by actually trying to open the image).
  • Set a size limit; generate a random file name instead of using the name the user gave.
  • Keep files outside the web root or in separate storage; never execute them.
  • Check the storage bucket's access rules. In the Tea case, the leak came from an open bucket nobody was looking at.
For file uploads, check the content, not the extension; accept at most 5 MB and regenerate the file name with a UUID. Storage should be private by default; access files through time-limited, signed URLs.

Layer 3 · Browser and transport

08 HTTPS

Today HTTPS isn't a choice; it's the default. Let's Encrypt is free, and platforms such as Vercel, Render and Railway set up certificates automatically. What you need to check: that HTTP requests are redirected to HTTPS, that the HSTS header stops the browser from trying HTTP again, and that if the application sits behind a proxy, it knows the request really arrived over HTTPS.

SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")

09 Secure cookies

The session cookie is the key to the account. Three flags protect that key:

Flag What it does
HttpOnly JavaScript can't access the cookie; an XSS flaw can't steal the session.
Secure The cookie is only sent over HTTPS.
SameSite=Lax The cookie isn't sent on requests from other sites; reduces CSRF risk.

AI often writes the token to localStorage. That's an area every script on the page can access; a single XSS flaw takes the session with it. Keeping the session in an HttpOnly cookie is a safer default. Don't forget to rotate the session ID after login, either.

Keep the session in an HttpOnly, Secure, SameSite=Lax cookie. Don't write the token to localStorage. Rotate the session ID on login and put a time limit on the session.

10 CORS configuration

CORS isn't a wall that stops attackers. It's the set of rules for relaxing the browser's restriction on "reading from a different origin". When AI sees a CORS error, it suggests the fastest fix: allow_origins=[""]. Since doesn't work with cookies, the next step is to reflect the incoming Origin header back as-is. That means any site can send requests to your API on your user's behalf and read the response.

AI usually

Access-Control-Allow-Origin: * or reflecting the incoming Origin, plus Allow-Credentials: true on top.

What you should do

An explicit allowlist: https://myapp.com, https://admin.myapp.com. Development origins only in the development environment.

11 Security headers

A few HTTP headers give the browser the answer to "what is allowed on this page?". They take ten minutes to set up and close off a whole range of attack classes from the start.

Header What it prevents
Content-Security-Policy Loading scripts from unapproved sources; limits the impact of XSS.
Strict-Transport-Security Downgrade attacks from HTTPS to HTTP.
X-Content-Type-Options: nosniff The browser guessing a file type and running a script.
frame-ancestors / X-Frame-Options The page being put in an invisible frame on another site (clickjacking).
Referrer-Policy Information in the URL leaking to other sites.
Permissions-Policy Unnecessary use of APIs such as camera, microphone and location.

You can check the result with securityheaders.com or Mozilla Observatory.

Layer 4 · Application behaviour

12 Rate limiting

A login form without limits is a door where thousands of passwords can be tried every second. An AI endpoint without limits is a model someone else runs on your bill. That's why the API keys' quotas ran out at EnrichLead.

Places you should set limits: login, password reset, OTP verification, sign-up, anything that sends email or SMS, and expensive operations (LLM calls, file conversion, exports). On this site, admin login allows 5 failed attempts per IP every 15 minutes, and PDF generation is limited to 10 requests a minute.

Add IP- and user-based rate limits to login, password reset, OTP and LLM-calling endpoints; return 429 when the limit is exceeded. For external APIs, set a monthly spending limit in the provider dashboard.

13 Global exception handling

In 2025, OWASP opened a separate category for the first time: Mishandling of Exceptional Conditions. It has two risks. The first is an error telling the user too much: stack traces, SQL queries, file paths, library versions. The second is subtler: the system failing open when an error occurs. Code that lets a request through when the permission check throws an exception is more dangerous than code with no check at all.

FlowWhen an error happens, who should see what?
1 · ErrorThe exception is caughtA single central handler, not try/except everywhere
2 · DecisionFail closedWhen in doubt, deny; if the permission check errors, the request doesn't go through
3 · UserA generic message"Something went wrong" and a tracking code
4 · ServerA full recordStack trace, request ID, user; an alert
Keep DEBUG off in production. Write one global error handler: return a generic message and a tracking code to the user, and write the details to the server log. If an error happens during authorization or validation, deny the request.

14 Logging and audit logs

Every day a breach goes unnoticed is a day handed to the attacker. Logs are the only answer to "what happened?". But wrong logging is a risk too: in Veracode's tests, models produced insecure code against log injection in 88% of cases; writing user input to the log without sanitising it opens the way to forged log lines.

  • Record: successful and failed logins, permission denials, admin actions, data exports, settings changes. For every record: who, what, when, from where, and the outcome.
  • Don't record: passwords, tokens, full card numbers, more personal data than necessary.
  • Keep logs somewhere separate from the application server and set alerts on critical events.

On this site, every login attempt goes to a separate security log; the visitor counter never stores IP addresses, only a salted hash.

Layer 5 · Data and secrets

15 Secret management

API keys, database passwords, payment provider keys. Getting these into the code is the most frequent and most expensive mistake in vibe coding.

GitGuardianSecrets leaked in public GitHub repositories
202423.8M
2025+34%, the largest increase measured~29M
+81%Increase in leaked secrets for AI services (2025)
6.4%Leak rate in repos using Copilot; overall average 4.6%
70%Secrets leaked in 2022 that were still valid two years later
GitGuardian State of Secrets Sprawl 2025 and 2026

There's another trap specific to vibe coding: in front-end frameworks, every variable prefixed with NEXT_PUBLIC_ or VITE_ is embedded in the bundle sent to the browser. When AI defines a variable with this prefix "to make it work", your secret key ends up in a JavaScript file anyone can see.

  • Secrets go in .env, and .env goes in .gitignore. In production, the platform's secret manager.
  • Every call that needs a secret key is made from the server; the browser only talks to your backend.
  • Set up pre-commit scanning (gitleaks, ggshield).
  • If a secret has leaked, deleting the commit isn't enough: revoke the key immediately and generate a new one.
Never write an API key into the code or into a variable prefixed with NEXT_PUBLIC_/VITE_. Make every call that needs a secret key on the server side and read keys from environment variables. Create a .env.example file.

16 Database security

Can the application's database user drop tables? Is the database open to the internet? Are development and production the same database? In the Replit case, the answer to the last question was "yes". Afterwards, the platform started separating development and production databases automatically.

  • Least privilege: the application user should only be able to read and write the tables it needs; schema changes go through a separate user.
  • The database should only be reachable from the application's network; connections over TLS.
  • If the front end connects to the database directly, RLS or security rules must be enabled on every table.
  • Don't give AI agents production database credentials. Let the agent work in the development environment; every change that goes to production should pass human approval.

17 Backups

Backups are the check that kicks in on the day the other 16 checks fail. According to Sophos's 2024 research, the average cost of recovering from a ransomware attack, excluding the ransom, is 2.73 million dollars. And the only reason the Replit case had a happy ending was that a backup existed.

Rule3-2-1 backups
3copiesLive data and two backups
2different mediaFor example, a managed database backup and object storage
1off-siteIn a different account or region; immutable if possible
An untested backup isn't a backup: try restoring regularly.

Decide two numbers up front: how much data loss you can tolerate (RPO) and how quickly you need to be back up (RTO). Your backup frequency and method follow from those two answers.

Layer 6 · Supply chain and process

18 Dependency scanning

Most of the code you ship isn't actually yours; it belongs to the packages you install. Over one year starting in November 2023, Sonatype detected 512,847 malicious open-source packages, a 156% increase on the year before.

AI has added a new risk to this: packages that don't exist. A study published at USENIX Security 2025 examined 576,000 code samples produced by 16 models. About one in five suggested packages didn't actually exist. And because the same made-up names were suggested again and again, attackers can register those names in advance and put malicious code inside. This is called slopsquatting.

USENIX Security 2025Share of suggested packages that don't exist
Open-source models21.7%
All models19.7%
Commercial models5.2%
205,474Unique made-up package names
30,000+Downloads in 3 months of an empty package registered under the frequently invented name "huggingface-cli"
Spracklen et al. · "We Have a Package for You!" · run with models available in early 2024.
  • Before installing any package AI suggests, look at its registry page: does it really exist, who publishes it, how many downloads does it have, and how long has it been around?
  • Commit the lock file (package-lock.json, poetry.lock) to the repo.
  • Continuously scan for known vulnerabilities with npm audit, pip-audit, OSV-Scanner and Dependabot or Renovate.
Before suggesting a new package, make sure it exists in the official registry and is actively maintained. If the standard library can do the same job, don't add a package.

19 CI/CD security scanning

The last check makes sure the others aren't done once and forgotten. A pipeline that runs on every push catches what people miss and, more importantly, stops new code from breaking what was already there.

PipelineSecurity steps that run on every push
1 · SecretSecret scanninggitleaks · ggshield
2 · SASTStatic analysisSemgrep · CodeQL · Bandit
3 · SCADependenciespip-audit · npm audit · OSV
4 · DASTProduction-like testingOWASP ZAP · in a staging environment
GateHigh risk = stopNo release if there's a critical finding
# .github/workflows/security.yml
name: security
on: [push, pull_request]
permissions:
  contents: read
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install pip-audit bandit
      - run: pip-audit -r requirements.txt
      - run: bandit -r . -x ./tests -ll

The pipeline itself is an attack surface too: define CI secrets with least privilege, pin third-party actions to a version or commit, and write the permissions field explicitly.

Where to start?

Nineteen items can look daunting. You don't need all of them at once; but going live without some of them is like leaving the door open and going on holiday.

PriorityPutting the 19 checks in order
Before launchMust have
  • Authentication
  • Authorization
  • IDOR / ownership
  • Input validation
  • SQL injection
  • XSS
  • HTTPS
  • Secret management
  • Database security
  • Backups
First weekHarden
  • Secure cookies
  • CORS
  • Security headers
  • Rate limiting
  • Global exception handling
  • File upload
OngoingAutomate
  • Logging / audit
  • Dependency scanning
  • CI/CD scanning
If your application accepts file uploads, move that item to the "Before launch" column.

One last suggestion: instead of having AI write security, ask it. After the code is generated, give this list to the same model in a separate conversation and ask it to audit its own code. When the generating model and the auditing perspective are separated, a good share of what slipped through in the first round gets caught. Still, the final decision should rest with a human who reads the code.

Review the code below like a security auditor. For each of these headings write "ok / missing / not applicable", and give corrected code for the missing ones: authentication, authorization, IDOR, input validation, SQL injection, XSS, file upload, HTTPS, cookies, CORS, security headers, rate limiting, error handling, logging, secret management, database permissions, backups, dependencies, CI/CD.

AI is a very fast and very eager helper. But code written fast is code that goes live without being read. Don't let speed get ahead of security.

Sources

  1. Veracode, 2025 GenAI Code Security Report, July 2025.
  2. N. Perry, M. Srivastava, D. Kumar, D. Boneh, Do Users Write More Insecure Code with AI Assistants?, ACM CCS 2023.
  3. J. Spracklen et al., We Have a Package for You! A Comprehensive Analysis of Package Hallucinations by Code Generating LLMs, USENIX Security 2025.
  4. GitGuardian, The State of Secrets Sprawl 2025.
  5. GitGuardian, The State of Secrets Sprawl 2026 (summary by The Hacker News), March 2026.
  6. OWASP, OWASP Top 10:2025.
  7. IBM, Cost of a Data Breach Report 2025, July 2025.
  8. Verizon, 2025 Data Breach Investigations Report.
  9. Sonatype, 10th Annual State of the Software Supply Chain Report, October 2024.
  10. Sophos, The State of Ransomware 2024.
  11. NIST NVD, CVE-2025-48757.
  12. Escape.tech, How we discovered over 2k high-impact vulnerabilities in apps built with vibe coding platforms.
  13. OECD AI Incidents Monitor, Replit AI coding tool deletes live production database, July 2025.
  14. BleepingComputer, Tea app leak worsens with second database exposing user chats, July 2025.
  15. Indie Hackers, Vibe coding has a security problem, 2025.
Save this post