Guides · weekly digest · 2026-09-04
VibeAudit weekly digest: 28 Aug – 4 Sep 2026
50 repos scanned. 34 of them shipped with at least one critical or high finding. Average launch-readiness score was 58/100. The recurring problems are missing ownership checks, secrets in the wrong place, and payment flows that trust the client.
The number: 34 of 50
34 of the 50 repos we scanned this week had at least one critical or high finding after verification. That is 68%. The average launch-readiness score was 58/100.
First-pass counts by category: 120 security, 65 correctness, 48 reliability, 40 data, 14 devops, 10 performance, 6 maintainability. By severity: 22 critical, 71 high, 114 medium, 89 low, 7 info. Security is not the whole story, but it is most of the story.
Problem 1: routes that fetch by id and never check who is asking
The single most common critical finding was a handler that takes an id from the URL or body, loads the row, and returns it. Examples this week: /api/orders/[id] returning any order, /api/retrieval/retrieve running match_file_items_* on caller-supplied fileIds, a getSuggestions server action with no auth check, and a delete_storage_object() RPC marked SECURITY DEFINER that anyone can call.
This happens because generated code tends to reach for the Supabase service-role client on the server. It makes the query work in one shot, and it also bypasses RLS, so the ownership check that the database would have enforced is gone and nothing replaces it. "use server" files make it worse: every exported function becomes a public endpoint, including generateTitleFromUserMessage, which was an unauthenticated, unmetered LLM call.
Problem 2: secrets where the browser can see them
We saw the Supabase service-role key exposed through a NEXT_PUBLIC_ env var in more than one repo, and one repo where the production service_role_key was meant to be pasted into a committed SQL migration. Several Stripe webhooks did not verify signatures at all.
The pattern is the same each time: the model picks whichever env var name makes the code run in the client component it is writing. NEXT_PUBLIC_ is the prefix that works, so it gets used. Nobody asks whether the key should ever leave the server.
Problem 3: payment and LLM flows that trust the request
checkoutWithStripe accepted a client-supplied Price object, so users could set their own trial length and choose any price. A pricing page 'Manage' button created a new Checkout session for already-subscribed users, which double bills. One webhook returned HTTP 400 for every unhandled event type; Stripe will mark that endpoint failing and eventually disable it.
On the LLM side: POST /generate was unauthenticated with optional rate limiting, so anyone could spend unlimited Replicate credits. A rate limiter fell back to an empty identifier and trusted x-real-ip, so every user shared one bucket. /api/chat/tools fetched URLs and headers taken straight from the request body (SSRF). Generated code copies the happy path from docs and skips the part where the server decides what the client is allowed to do.
False positives, honestly
The first automated pass is cheap and over-reports on purpose. Of the critical/high findings that went through second-pass Fable review this week, 50 were confirmed, 92 were false positives, and 26 stayed uncertain. That is roughly 55% false positive on the first pass, with another 15% we could not settle without more context.
If you run only the first pass, expect to throw out about half of the red items. The confirmed half is the number that matters, and it is still 34 repos out of 50.
Three checks to run on your repo this week
None of these need a tool. Each takes under an hour.
- Grep for
NEXT_PUBLIC_and forservice_roletogether. Any service-role key that appears in aNEXT_PUBLIC_variable, a client component, or a committed migration is a critical. Move it to a server-only variable and rotate it. - List every API route and server action that takes an id. For each one, point at the line that compares the row's owner to the authenticated user. If the query uses the service-role client and there is no such line, that is an IDOR.
- In your Stripe code: confirm the webhook calls signature verification before touching the payload, returns 200 for event types you do not handle, and that the price and trial length come from a server-side lookup, not from the request body.
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.