ai newsDo., 5. Nov.
EilmeldungI ran a pre-launch audit on a deliberately broken vibe-coded app. Here are 10 findings and the prompt that fixes each one

I ran a pre-launch audit on a deliberately broken vibe-coded app. Here are 10 findings and the prompt that fixes each one

Disclosure: I built LaunchVet, the tool I use in this article. The free core is MIT licensed. Every fix prompt below works in a plain Claude Code session, so you do not need the tool to use them. AI will write you an app that runs. It will not tell you the database is open to anyone holding your pu

dev.toMittwoch 23:58 · 04.11.8 Min. Lesezeit
Bild: dev.to

Disclosure: I built LaunchVet, the tool I use in this article. The free core is MIT licensed. Every fix prompt below works in a plain Claude Code session, so you do not need the tool to use them.

AI will write you an app that runs. It will not tell you the database is open to anyone holding your public key. Veracode's 2025 GenAI Code Security Report (80 coding tasks, 100+ models) found AI-generated code introduced security vulnerabilities in 45% of test cases. That is test cases, not apps, and not all AI code, but it is enough reason to look before real users arrive.

So I made a demo app the way a rushed AI-assisted build often turns out: a resume generator called ResumeFlash, on Next.js, Supabase and Stripe, with the usual mistakes left in on purpose. Then I ran a pre-launch audit on it from inside Claude Code. The report came back with 28 findings: 7 critical, 14 high, 6 medium and 1 low. Verdict line: "NOT READY TO LAUNCH: fix the critical findings first."

Here are the 10 I think are the most relatable, with the file the audit pointed at, why it hurts, and the prompt from the report that fixes it. The prompts are shortened here. The full versions are in the report: https://github.com/Lt23live/launchvet/blob/main/examples/broken-app/launchvet-report.md

Paste a prompt into Claude Code, read the diff, commit. Do not accept a change you have not read.

Where: lib/supabaseClient.js:6. Critical.

The Supabase service role key is exposed to the browser. Fix it: 1. In.env.local rename NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY to SUPABASE_SERVICE_ROLE_KEY.

2. Split lib/supabaseClient.js into two files. lib/supabaseClient.js exports only `supabase`, created with NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY. New file lib/supabaseAdmin.js starts with `import 'server-only';` and exports `supabaseAdmin` created with SUPABASE_SERVICE_ROLE_KEY.

3. Update every import of supabaseAdmin in app/api/** to the new file. Client components must import only lib/supabaseClient.js.

Do not change unrelated code. After the change, run `npm run build` and search the.next/static folder for the text "service_role"; it must not appear. Then tell me to rotate the service role key in the Supabase dashboard, because the old one has been public.

Enter fullscreen mode Exit fullscreen mode Rotate first, then fix the code. A key that has been public stays compromised after you hide it.

Where: supabase/migrations/20261101000000_init.sql:20. Critical.

documents and subscriptions were created without enable row level security, and the migration even runs disable row level security on subscriptions with the comment "RLS kept blocking the dashboard". The anon key ships in your frontend by design. With RLS off, anyone holding it can read and edit every resume and every billing record. This is the most expensive mistake on the list.

Create a new Supabase migration in supabase/migrations/ that secures the documents and subscriptions tables. Remove nothing from the old migration; add a new file. In it: 1. alter table public.documents enable row level security; 2. alter table public.subscriptions enable row level security; 3. documents policies: select, update and delete allowed only when auth.uid() = owner_id; insert allowed only with check (auth.uid() = owner_id).

4. subscriptions policies: select allowed only when auth.uid() = user_id. No insert, update or delete policies for normal users; only the server (service role, used in the Stripe webhook) writes this table. Do not change unrelated code.

Then show me how to test with two different test users that user B cannot see user A's documents or subscription. Enter fullscreen mode Exit fullscreen mode 3. Anyone can read any user's documents Where: app/api/documents/[id]/route.js:6.

Critical.

GET /api/documents/[id] loads a document by id with the service role client and never checks who is asking. The ids are bigserial numbers, so they can be counted 1, 2, 3. Hiding a button is not access control, and a login is not an ownership check. Change the number in the URL and you are reading someone else's resume.

Remove the OpenAI key from app/dashboard/page.jsx. Delete the OPENAI_KEY constant and the direct fetch to api.openai.com. Make quickPreview() call our own POST /api/generate route instead, sending only the text. The server route reads OPENAI_API_KEY from the server environment. Then search the whole repo for "sk-proj-" and "api.openai.com" and make sure they appear only in server code under app/api/. Do not change unrelated code. After the change, remind me to revoke the old key in the OpenAI dashboard and create a new one, because the old key has been public. Enter fullscreen mode Exit fullscreen mode 5. The AI endpoint has no login and no rate limit Where: app/api/generate/route.js:3. High.

It calls gpt-4o with max_tokens: 4000 for anyone, with no login, no rate limit and no quota. It is not only login endpoints that need limits. Any route that calls a paid API, sends email or accepts uploads can be hit by a script in a loop, and the damage is your bill.

The webhook reads request.json() and trusts it. It never verifies the Stripe-Signature header, then upgrades whichever profile id is in client_reference_id to pro. Anyone can send one fake "checkout completed" request and give any account the paid plan for free.

Secure app/api/webhooks/stripe/route.js: 1. Read the raw body with `const body = await request.text()` (not request.json()). 2.

Verify it with `stripe.webhooks.constructEvent(body, request.headers.get('stripe-signature'), process.env.STRIPE_WEBHOOK_SECRET)`. On error return 400 and do nothing else. 3.

Add STRIPE_WEBHOOK_SECRET to.env.local (empty value for now) and to.env.example. 4. Make the handler idempotent: store processed event ids in a new table stripe_events (id text primary key) and skip events already seen.

5. Find the user from the Checkout Session's customer or metadata that the server set when creating the session, not from anything the browser sent. Do not change unrelated code.

Verify with the Stripe CLI: `stripe listen --forward-to localhost:3000/api/webhooks/stripe` then `stripe trigger checkout.session.completed` must succeed, and a curl POST with a made-up JSON body must get 400. Enter fullscreen mode Exit fullscreen mode 7. Checkout trusts the price and user id from the browser Where: app/api/checkout/route.js:5.

High.

/api/checkout takes priceInCents and userId from the request body and uses them as the amount and the account to upgrade. A buyer can edit the request to pay the minimum Stripe allows, or pay for someone else's account. The same finding noted there was no refund policy anywhere and no support contact. Stripe expects both to be visible on your site, and they are what a confused customer looks for before they email you or dispute the charge with their bank.

Stripe says the card industry treats dispute activity above 0.75% as excessive, and a sudden spike can trigger monitoring earlier. Under Visa's VAMP, a dispute-plus-fraud ratio of 1.5% (US, EU, Canada, AP, LAC) is Excessive, and Visa may fine merchants above that. For a small shop, a few disputes is a large percentage.

Checkout creates a monthly subscription with a 7-day trial, but the pricing page shows no price, no billing interval and no "renews automatically" text. The account page says "Contact support" to cancel, with no contact and no cancel button. Signing up takes one click and cancelling needs a contact that does not exist. That is how unhappy users become chargebacks.

California Penal Code 637.2 lets a person injured by a violation sue for the greater of $5,000 per violation or three times actual damages, and actual damages need not be shown. Plaintiffs have sued sites over trackers under this law. Courts differ on how far it reaches, so treat this as a reason to check, not a verdict.

The only thing in the footer was "Made with love." The app collects emails and work history and sends them to Supabase, Stripe, OpenAI, Google Analytics and Meta. Privacy laws, Stripe, app stores and Google sign-in expect a policy that says what you collect and who you share it with.

If you serve users in the EU and your app generates content with AI, there is a second page to think about. The AI Act's Article 50 transparency obligations have applied since 2 August 2026. Providers of AI systems that talk directly to people must make clear it is an AI, unless obvious. Providers of generative systems must mark outputs machine-readably. Breaches of the AI Act transparency duties can be fined up to EUR 15 million or 3% of worldwide annual turnover, whichever is higher. For SMEs and startups the cap is the lower of the two.

/plugin marketplace add Lt23live/launchvet /plugin install launchvet@launchvet Enter fullscreen mode Exit fullscreen mode Open Claude Code in your project and run /launchvet:launchvet, or just ask "Run LaunchVet on this app." (If you copy the skill folder into ~/.claude/skills/ instead of installing the plugin, the command is /launchvet.) You need Node.js 18 or newer for the scanner.

You get launchvet-report.md in your project, ranked worst first. Every finding has the file, the reason and a fix prompt. Work from the top, paste a prompt, check the diff, run it again to confirm the finding is gone. LaunchVet never edits your code.

LaunchVet runs inside your own Claude Code session. There is no LaunchVet server, no signup, and nothing is uploaded to us. The scanner script makes no network calls. Claude Code itself still reads your files and talks to Anthropic the way it does for any task, so if that is a problem for your repo, this does not change it.

Free and Pro, in two lines: the free core is MIT licensed on GitHub with 26 checks. Pro is a one-time $19 on Gumroad ($12 until 11 Nov 2026, 11:59 pm PT, link below) and adds more checks, payments and Stripe readiness, an app store module, legal page starter templates and a CLAUDE.md security rulebook, and you do not need it to fix what the free core finds.

Repo: https://github.com/Lt23live/launchvet Full demo report: https://github.com/Lt23live/launchvet/blob/main/examples/broken-app/launchvet-report.md Site: https://lt23live.github.io/launchvet/ Pro on Gumroad ($12 with the launch code until 11 Nov 2026, 11:59 pm PT, then $19): https://greystatedevelopment.gumroad.com/l/launchvet/LAUNCH12

LaunchVet is an automated checklist, not a security guarantee or legal advice. AI is used in this product and can make mistakes.

Which check do you think is missing? Tell me in the comments.

Quelle: dev.to (dev.to). Original öffnen