In 2025 a researcher found 170+ apps built with one AI app builder leaking user data to anyone who asked.
No exploit. Just the public key that already ships in the site's code.
If you vibe-coded your app, here are 5 checks you can run tonight, about 5 minutes each ๐งต
@azamsharp 24 hours vs 24 hours isn't the real race. Rerun it after the first security bug report.
Hand-coded: you open the file and fix it in 10 minutes.
Vibe-coded: 3 hours of prompting, and you pray the agent didn't "fix" it by turning RLS off.
@mechakouhai Rule of thumb: if a quiz site wants your email + birthday to tell you which Hogwarts house you are, assume that data sits in a table anyone with devtools can read until proven otherwise.
The quiz is the product. You're the dataset.
@4n6lady Slightly hot take: nobody hand-wrote secure code by default either. They had a senior in code review asking "why is this key in the client?"
Vibe coders didn't skip security. They skipped the senior.
@fahmindot@supabase Fewer than you'd hope. Paste this in the SQL editor before launch:
select tablename from pg_tables where schemaname='public' and rowsecurity=false;
Every row it returns is a table your anon key can probably read.
Free 5-check list: https://t.co/qsmv5kbHDy
@PrajwalTomar_ Point 1 is the one everyone fakes. "Have a privacy policy" usually means a ChatGPT one that lists data you don't collect and skips the processors you do use (Supabase, Stripe, analytics).
I mapped the 10 legal traps I keep seeing in AI-built apps: https://t.co/r3dbp9qyPe
@msnmongare@supabase The two I keep seeing: service_role key shipped in the frontend bundle, and tables with RLS off because "the app worked without it".
Made a free one-command scanner for the leaked-key half:
https://t.co/dymFcUvCFV
@tobiadonadon_ After looks way better. Unpopular take though: for a health app the before/after I'd want to see is the RLS policies, not the gradients.
Health data on a vibe-coded backend is the one combo where a leak isn't a bug report, it's a headline.
@itz_komal01 You forgot the 5th layer of the stack:
a database table with RLS off + the anon key sitting in the JS bundle.
Ships free with every vibe-coded app. No extra prompt needed.
@RoundtableSpace The insult was never about who typed the code. It's about who read it.
Planned + reviewed + tested = engineering.
Prompted + deployed + "looks fine" = vibe coding.
The second one is how a Supabase table full of user emails ends up public.
Free tool if you're shipping an app built with Cursor, Lovable, Bolt or Claude Code.
Checks your project for leaked API keys in 10 seconds:
npx github:stackdrawn/ship-safe-check
Runs locally. Nothing uploaded. MIT.
https://t.co/dymFcUvCFV
@forgebitz Until that exists, the cheapest habit I've seen: give every tool its own API key with a monthly spend cap. When one breaks or leaks, you rotate one key and the damage has a ceiling, instead of hunting through ten apps sharing one key.
@paulo_kombucha Nice. One tip for anyone copying this: the Masko key belongs in the connector settings only. If the agent ever wires the mascot API into the app itself, ask it to call it from an edge function, or the key ships in the JS every visitor downloads.
Free tool if you're shipping an app built with Cursor, Lovable, Bolt or Claude Code.
Checks your project for leaked API keys in 10 seconds:
npx github:stackdrawn/ship-safe-check
Runs locally. Nothing uploaded. MIT.
https://t.co/dymFcUvCFV
An ownership check can be correct and still let a user upgrade themselves.
If display_name, role and credits share a row, test which fields a normal user can write.
They should be able to change their name. They should not be able to give themselves admin or a bigger balance.
@LimestoneHQ Correction: RLS off alone doesn't prove a table is public. The API role also needs grants. Treat this query as a list to investigate, then test logged out and as a second user. Zero rows doesn't prove the policies are correct, or cover views and other exposed schemas.
@LimestoneHQ On #1, you can check it in 30 seconds. Supabase SQL editor:
select tablename from pg_tables where schemaname='public' and rowsecurity=false;
Zero rows is what you want. Anything listed is readable with the key in your frontend.
@pritesh_ghodge One nuance for the scanner: USING (true) allows every row for the policy's target role/operation, subject to grants. TO authenticated doesn't automatically allow anon reads. Worth reporting role + operation + condition together, then testing both sessions.
@GodseSarth2984@Odoo One test I'd squeeze in before the demo: resend the same payment event and check the stored balance. One purchase should grant credits once, even if the webhook arrives twice. Good luck with the 4 PM deadline!
@mardehaym "User A reads user B's data by changing one number in the URL" is the one I see most in AI-built apps.
2-minute test: make two accounts, copy a URL as A, open it as B. If it loads, the query isn't filtering by owner.
@ndgmlondon Easiest way to see this on your own app: DevTools โ Network, find the request your admin page makes, "copy as cURL", run it logged in as a normal user.
If it returns data, the hidden button was the only lock.