← Library

concepts · tweet · 8 min

AI-Generated Code Security Vulnerabilities

Burak Eregar · Jan 25, 2026

everyone is talking about how fast you can build with ai tools like claude code, cursor, antigravity, and lovable. nobody is talking about how fast bad actors can break into what you built to steal your users data or consume your ai credits.

i have audited hundreds of "vibe coded" apps in the last month and accessed over 600000 users' personal data. the reality is harsh: speed is creating a security crisis and ai turned this into an epidemic.

(ps: i included a downloadable 'S-Tier' rules/skills file at the bottom of this post. feed it to your ai agents to stop them from writing this vulnerable code.)

senior engineers mock vibe coders. they say ai generated code is sloppy and insecure. the uncomfortable truth? right now, they are right.

but you don't have to stop building fast. you just have to stop being lazy. here is the manifesto on how to secure your app before a bad actor (or me) finds it.

  1. the "direct-to-db" trap

the "magic" of supabase/firebase is that you can query your database directly from the frontend. ai models love this. they will happily write:

supabase.from('users').update({ is_pro: true }).eq('id', user.id)

this is a death trap.

when you write business logic in the client, you are trusting the user. never trust the user. a hacker can open their browser console and run:

supabase.from('users').update({ is_admin: true })

if your RLS policy has a single logic error (and trust me, ai writes terrible RLS policies), bad actors just became the owner of your app.

  1. the "hidden columns" vulnerability

even if you have RLS policies, they usually default to protecting rows, not columns.

you might have a policy that says: "users can update their own rows."

cool. i own my row. but unless you explicitly restricted which columns i can update, i can edit every single field in my profile.

you think you are letting me update my display_name. i intercept the request and add role: 'admin' or subscription_status: 'active' to the payload.

since i own the row, postgres allows the update. i just hacked your entire business logic because you trusted the frontend to only send the "safe" fields.

  1. the "self-ddos" (why RLS is not enough)

it doesn't stop at privilege escalation. when you let the client talk directly to the db, you remove the only gatekeeper: rate limiting.

without a backend middleware to throttle requests, i can write a 5-line script to insert 10 million rows into your todos table while i sleep.

even if i can't make myself an admin, i can bloat your database until it chokes. i will destroy your storage limits, slow your queries to a crawl, and rack up a massive bill on your cloud provider.

you are handing me the keys to ddos your own database.

  1. the mobile "forever" nightmare

web developers often forget this: you cannot hot-fix a mobile app.

when you put your business logic in the frontend (which direct-to-db tools encourage), you are hardcoding your security rules into the binary on the user's phone.

on the web, if you find a logic bug, you push to vercel and it’s fixed in 30 seconds. on mobile? you fix it, then you submit to apple. you wait 48 hours for review. then you wait 2 weeks for users to actually update their app.

if that "logic bug" was a vulnerability or a pricing error, you are bleeding money for days with no way to stop it.

  1. the architecture fix: go backend-first

so, how do we fix all of this?

stop connecting directly to the database. if you are using supabase / firebase, ask ai to write edge functions (or server actions).

the new rule:

  1. enable RLS on your tables but do not add any "public" rules.

  2. make your database inaccessible to the outside world.

  3. only allow your edge functions to access the database using the service role key.

think of your service role key as a "super admin." it should live ONLY in your edge function environment variables. never hardcode it. never let it touch the client.

this way, your logic, rate limiting, and column filtering all happen on the server, where bad actors can't touch them.

bonus: the "anon key" myth

many vibe coders think the anon key is safe because it's "anonymous." false.

the anon key exposes your entire database schema via the REST API. if i have your url and anon key, i can map out every table and column you have.

if you switch to the "backend-first" approach i described above, you can disable the public REST API entirely. this means even if i have your keys, i can't see your schema or query your data.

  1. lock down your database functions (RPCs)

this is the most common vulnerability i find in vibe-coded apps.

when you write a postgres function (to handle complex logic), it is executable by public by default. even if you don't expose it in your ui, i can scan your database, find the function name, and call it.

if you have a function called add_credits(user_id, amount), i can call it with my id and add 1,000,000 credits.

the fix: you need to explicitly revoke public access. run this sql for every function you create:

revoke execute on function your_function_name from public;
revoke execute on function your_function_name from anon;
grant execute on function your_function_name to service_role;
  1. your "private" storage is probably public

i see this constantly. you ask the ai to "upload a profile picture." it creates a supabase storage bucket. it sets it to "public" because that's easier to code.

then it uploads files like: avatar_1.jpg avatar_2.jpg

if i know your project url, i can just iterate through numbers and download every single user's photo. worse, i've seen apps upload invoices and id cards to public buckets. (remember the tea app? this's exactly how they leaked hundreds of thousands of their users' passports, id cards and pictures)

the fix:

  1. never use public buckets for sensitive user data.

  2. turn off "public" access.

  3. use Signed URLs. this means the file is private, but the server generates a temporary, time-limited link for the user to view it.

  4. force your ai to rename files with UUIDs (random strings) so they cannot be guessed.

  5. verify your webhooks or go bankrupt

if you are using stripe, lemon squeezy, or any payment provider, you rely on webhooks to know when a user paid. ai agents often forget to verify the signature.

they just write: if (req.body.type === 'payment_success') { give_access() }

if you deploy this, i can send a fake request to your server saying "i paid," and your app will believe me. i get your product for free.

the fix: never trust the payload. always use the provider's official library to constructEvent and verify the cryptographic signature. if the signature fails, reject the request immediately.

the tip: using randomised names for your webhook url reduces both attacks. so instead of going for

go for

stripe1eT35gWebhook

  1. don't trust the ai with ".env"

i scanned 100+ github repos. the amount of people who commit .env files with SUPABASE_SERVICE_ROLE_KEY is terrifying.

once that key is on github, your database belongs to the internet. bots scan github in real-time for these keys.

the fix: add .env to your .gitignore file before you write a single line of code. use .env.local for development. never, ever let the ai "hardcode" a key into a file to "fix a connection issue."

  1. rate limits are not optional anymore

without limits i can:

  • brute force magic links

  • inserts millions of rows to your database tables

  • enumerate ids

  • drain quotas

  • ddos your wallet via stripe calls

the fix:

apply limits at:

  • api routes

  • auth endpoints

  • webhooks

even a simple kv-based limiter is enough to stop 90 percent of abuse.

final thoughts

vibe coding is the future. being able to build software without deep engineering knowledge is a superpower. but with great power comes great responsibility.

the internet is not a friendly place. there are bots and scripts scanning your new "lovable" website the second it goes live.

you don't need to be a security expert. you just need to stop trusting the defaults.

want to know if you are safe?

i built a tool that automates these checks. it scans your app for exposed keys, public functions, and weak rls policies in minutes.

check your app before hackers do:

here is 50% discount code: XARTICLE (available for first 30 people, first come, first serve)

you can also hire me to run a full penetration testing through AuditYourApp

stay safe and copy this rules file to your repository:

markdown

SECURITY & ARCHITECTURE RULES
This project enforces a STRICT "Backend-First" security model.
AI MUST follow these constraints to prevent Vibe Coding vulnerabilities.

1. ARCHITECTURE: BACKEND-ONLY DATA ACCESS
- **NEVER** write business logic in Client Components.
- **NEVER** use `supabase-js` client-side methods (`.select`, `.insert`, `.update`, `.delete`) directly in the frontend.
- **ALWAYS** use Next.js Server Actions, API Routes, or Supabase Edge Functions for ALL data access (Read & Write).
- The Frontend is a View Layer only. It speaks to APIs, not the Database.

2. DATABASE & RLS (Supabase) - THE "ZERO POLICY" RULE
- **RLS IS MANDATORY:** Enable Row Level Security on every table immediately.
- **NO POLICIES ALLOWED:** Do NOT create any RLS policies (e.g., `create policy...`). 
  - *Context:* Enabling RLS without policies acts as a "Deny All" firewall.
  - *Effect:* The `anon` key (Client) will have ZERO access to data.
- **SERVICE ROLE ONLY:** All data interaction must occur via the `service_role` key inside Edge Functions or Server Actions (which bypasses RLS).

3. STORAGE SECURITY
- **NO PUBLIC BUCKETS:** Never set `public: true` for storage buckets.
- **UUID FILENAMES:** Always rename files to a `crypto.randomUUID()` string before uploading to prevent enumeration attacks.
- **SIGNED URLS:** Always use `createSignedUrl` for retrieving files. Never expose the direct path.

4. PAYMENTS & WEBHOOKS
- **VERIFY SIGNATURES:** When writing a webhook handler (Str