You built it in three weeks with Cursor or Claude, it works, and real users are about to arrive. Before they do, set aside one focused half-day and run the pass below. It is the same sequence we use in the first hours of a paid audit, trimmed to what a founder can do alone with a terminal and two test accounts. No security background required — every step says what to run and what a bad result looks like.
Work in order. The early steps are the ones that turn into incidents fastest.
Start with the question that has ended startups: is anything secret sitting in the repository? A leaked .env, a live Stripe key in a config file, an AWS key pasted into a service class. Check the working tree and the git history — deleting a file in a later commit does not unleak it:
If you find anything: rotate the credential at the provider first, then clean the history. Rotating APP_KEY deserves care — it invalidates encrypted values and signed cookies, so plan a session reset.
On the production server (or a shell into the container), one command summarizes the dangerous toggles:
Then probe from outside, as an attacker would:
A 200 on Telescope is a full leak of requests, jobs, mail and queries. Gate it or remove it from production builds. The complete production-settings sweep (sessions, cookies, CORS, headers) is its own article: Turning off the footguns.
This step finds the bugs that actually breach vibe-coded apps. Create two ordinary accounts, A and B. Log in as A, perform every meaningful action, and collect the URLs and API calls. Then replay them as B, swapping in A's IDs:
GET /invoices/17 — can B read A's invoice?PUT /api/projects/9 — can B rename A's project?DELETE /uploads/123 — can B delete A's file?Anything that succeeds is an insecure direct object reference (IDOR) — the highest-frequency critical bug in AI-generated Laravel, because assistants add auth middleware and stop there. The fix pattern (policies plus Gate::authorize) is covered in Top Laravel vulnerabilities in AI-generated code. To see the attack surface in one place:
Read every POST/PUT/PATCH/DELETE row and ask: where is the line of code that checks ownership, not just login?
Four patterns account for most injection and mass-assignment findings. Grep for all of them:
For each hit, trace the variable: does user input reach it without validation? ->all() into an update() is a finding. DB::raw('count(*) as total') with no variables is fine. When in doubt, rewrite toward $request->validate() and query bindings — the safe patterns are in Why AI loves raw queries.
If the app accepts files, check three things: validation rules exist (image/mimes/max), stored names are Laravel's hashed names rather than getClientOriginalName(), and nothing private lives under public/. Then look at what is already there:
Anything a user uploaded that another user should not see must be served through a controller with an authorization check or a temporary signed URL — never by a guessable public path.
Two commands, two minutes:
Vibe-coded apps often pin whatever version the assistant's training data remembered. You do not need everything bleeding-edge before launch; you do need zero known-vulnerable packages.
Confirm a throttle exists on: login, registration, password reset, OTP/2FA checks, contact forms, and anything that sends email/SMS or hits a paid API. The test is empirical — hit the endpoint 30 times with curl and expect 429s:
All 200/422? Add a named limiter and throttle: middleware — it is six lines.
Assistants love excluding routes from CSRF to make integrations work, and skipping webhook signature checks to make tests pass. Review both lists:
Every CSRF-exempt route must verify a signature or token some other way. An unverified /webhooks/stripe endpoint means anyone can POST "payment succeeded" to your app.
Trigger an error on purpose (a 500 on a junk route, a failed validation, a wrong password five times) and check two places: what the user saw (it must not be a stack trace) and what the log recorded. You want failed logins and 500s visible in logs, secrets absent from them, and something — even a free Slack webhook on the critical channel — that tells you when production is on fire. Silent failure is how a breach goes unnoticed for months.
Sort findings into three buckets and act accordingly:
Re-run the relevant check after each fix — a security fix you did not verify is a hope, not a fix.
This checklist catches the mechanical 70%. What it cannot catch is the business-logic layer: tenant isolation in your data model, privilege boundaries, race conditions around payments. If the app handles money, health data, or other people's customers, that is the point of a fixed-price secure code review — senior eyes, every finding with a fix, sample report here.
Three to five focused hours for a typical MVP: the greps and config checks take minutes; the two-account authorization sweep is where the time goes — and where the findings are. Budget more if you have a large API surface.
Scanners are good at config and dependency issues (steps 2 and 6) and nearly blind to authorization logic (step 3), which is where vibe-coded apps actually fail. Run a scanner as a supplement, not a substitute.
A working IDOR on customer data. It needs no skill to exploit, it is usually a reportable data breach the moment a stranger triggers it, and it is almost always systemic — one missing policy means many.
Book a fixed-price review and launch knowing what an attacker would find first.