VibeAudit

Guides · weekly digest · 2026-09-07

VibeAudit weekly digest: Aug 31 – Sep 7, 2026

64 repos scanned. 43 had at least one critical or high finding. Average launch-readiness score was 59/100. The recurring problems were missing ownership checks, privileged Supabase keys in the wrong place, and payment code that trusts the client. About half of the automated critical/high findings did not survive a second review pass.

64
repos scanned
59/100
average score
43
with critical / high
63%
first-pass false positives

The number: 43 of 64

43 of the 64 repos scanned this week had at least one critical or high finding on the first automated pass. That is two out of three. The average launch-readiness score was 59/100.

Across all repos the scanner logged 397 findings: 27 critical, 87 high, 150 medium, 123 low, 10 info. Security dominated with 164, followed by correctness (80), reliability (67) and data (50). DevOps, performance and maintainability together were 36.

Problem 1: fetching by id without checking who owns it

The most repeated finding was a route or server action that takes an id from the request and returns or mutates the row without checking the caller owns it. Examples this week: an /api/orders/[id] route that returns any order, retrieval endpoints that run match_file_items_* on caller-supplied fileIds, a docx processing route that writes file_items for any fileId, and a getSuggestions server action with no auth at all.

This happens because AI-generated handlers are written to make the query work. Auth gets added at the page or layout level, and the handler assumes the id it receives is legitimate. When the handler also uses a service-role client, RLS no longer catches the mistake.

Problem 2: the service-role key where it does not belong

Several repos exposed the Supabase service-role key through a NEXT_PUBLIC_ variable, which ships it to the browser. One repo had the production service_role_key written into a committed SQL migration. Another exposed a SECURITY DEFINER RPC, delete_storage_object(), callable by anyone, that deletes any storage object. An unauthenticated /api/chat/custom used the service-role client to read other users' custom model API keys.

The pattern is the same: a query failed under RLS, and the fix that got generated was to reach for the key that bypasses RLS. It works locally, and nothing tells you it is wrong until someone reads your bundle.

Problem 3: payment and webhook code that trusts the client

A checkoutWithStripe function accepted a client-supplied Price object, so users could pick any price and set their own trial length. A pricing 'Manage' button created a new Checkout session for already-subscribed users, which double-bills. One webhook did not verify signatures. Another returned HTTP 400 for every unhandled event type, which makes Stripe mark the endpoint as failing and eventually disable it.

Generated payment code covers the demo path: one plan, one new customer, one event type. Real accounts hit the other branches.

A related cluster: unauthenticated or unmetered LLM and image-generation routes. POST /generate with optional rate limiting, a generateTitleFromUserMessage exported from a "use server" file, and a rate limiter whose identifier fell back to an empty string so every user shared one bucket.

How many of these were real

The first automated pass is cheap and over-reports on purpose. Of the critical/high findings sent to the second Fable review pass, 58 were confirmed, 98 were false positives, and 29 stayed uncertain. That is roughly a 53% false-positive rate on the severe tier.

Read the first-pass list as a set of places to look, not a list of bugs. The confirmed set above is what the recurring problems in this digest are drawn from.

Three checks for your repo this week

None of these need a tool.

  • Grep for the service-role key. It should appear in server-only code and nowhere under NEXT_PUBLIC_, in migrations, or in files that also export client-reachable functions. Also list every export in each "use server" file; each one is a public endpoint.
  • For every handler that takes an id from the URL or body, find the line that ties the query to the current user (user_id = session.user.id or equivalent). If the handler uses a service-role client, that line has to be there explicitly because RLS will not add it.
  • Open your Stripe webhook. Confirm it calls signature verification before parsing the body, and that unhandled event types return 200, not 400. Then confirm the 'Manage' path sends existing subscribers to the billing portal rather than a new Checkout session.
grep -rn "SERVICE_ROLE\|service_role" --include=*.ts --include=*.tsx --include=*.sql --include=.env* .
grep -rln '"use server"' src app | xargs grep -n "^export"

Check your own repo for this.

Free quick scan reads your highest-risk files in under a minute. The $19 deep audit reads the whole codebase and writes a fix prompt per finding.

Scan my repo →

Written from VibeAudit audit findings. Public open-source starters are named with file and line; other apps are anonymized. Not a substitute for a professional security assessment.