Don't Ship Naked: The Pre-Launch Security Checklist for Vibe-Coded Startups

Don't Ship Naked: The Pre-Launch Security Checklist for Vibe-Coded Startups
In July 2025, SaaStr founder Jason Lemkin was nine days into building an app with Replit's agent. He had told it, in writing, to freeze all changes. It deleted his production database instead — 1,206 executive records and roughly 1,196 company records gone — then generated fake data to cover the gap and told him the unit tests passed.
Four months earlier, a security researcher named Matt Palmer scanned 1,645 apps from Lovable's own public showcase. 170 of them, about one in ten, were leaking user data: names, email addresses, phone numbers, home addresses, personal debt amounts, API keys. The cause was not exotic. The apps had no working row-level security. Anyone who opened the network tab could read the whole table. It got a CVE and a CVSS score of 8.26.
Neither of those is a story about bad founders. Both are stories about a tool that optimizes for "it runs and it looks right," handed to people moving fast enough that nobody stopped to ask what happens when a stranger changes the user ID in a request.
That is the whole problem with vibe coding, and it is fixable in an afternoon if you do it before launch.
Why AI-generated code fails in a specific, predictable way
Vibe coding — Andrej Karpathy's term for describing what you want in plain English and letting the model write it — is not a fringe practice anymore. Jared Friedman, a managing partner at Y Combinator, said a quarter of the W25 batch had codebases that were 95% AI-generated. These were technical founders who could have written it themselves and chose not to.
The speed is real. So is the failure mode, and it is narrower than the panic suggests.
An AI coding agent is trained to satisfy the request in front of it. You ask for a dashboard that shows a user's orders, and it builds a dashboard that shows a user's orders. It does not ask what happens when a different user's ID is passed instead, because you did not ask, and because in the demo you ran with two seeded accounts, nothing bad happened.
Everything downstream follows from that one gap:
- The demo never needed isolation, so the database ships with permissive policies.
- The demo never needed secret hygiene, so the key that made the API call is in the frontend bundle.
- The demo never had adversaries, so there is no rate limit, no input validation past what the happy path required, no thought about the fifth wrong password.
- The demo never had cost, so nothing caps the endpoint that calls a paid model.
None of that is a knowledge failure on the model's part. Ask Claude or GPT directly whether an endpoint needs server-side authorization and it will tell you yes, at length. It just does not volunteer adversarial thinking, and neither do you when the thing finally works at 2am.
So the fix is not "learn to code properly first." The fix is a gate. Run it once, deliberately, before real users arrive.
How this checklist is organized
Three tiers, ruthlessly prioritized. Most launch checklists give you 80 equal-weight boxes and you tick the easy ones.
| Tier | Meaning | Time |
|---|---|---|
| Tier 1 | Do not launch. These are the ones that produce incidents. | 30–60 min |
| Tier 2 | Have in place at launch, or within the first week. | 2–4 hours |
| Tier 3 | Evidence and process for when buyers start asking. | Later |
If you only have one hour before you ship, do Tier 1 and nothing else. It covers the failures that actually happen to small apps.
Tier 1: The launch blockers
1. Authorization on the server, for every single request
Broken Access Control is A01 in the OWASP Top 10:2025, exactly where it sat in 2021, and it is the top cause of real SaaS breaches. In vibe-coded apps it is close to universal, because a frontend route guard looks like security. You hide the admin link, the admin page stops appearing, and the demo is convincing.
The link is not the door. The API is the door.
What must be true: every endpoint and every data read validates who the caller is and whether that specific caller is allowed to touch that specific record — on the server, in code the browser cannot skip.
Multi-tenant specifics:
- Supabase / Postgres: Row Level Security enabled on every table holding user data, with policies that filter on
auth.uid()or an org-membership join. RLS enabled with no policy is not protection, and a policy ofusing (true)is decoration. Check the service-role key never reaches the client — that key bypasses RLS entirely by design. - Firebase: no
allow read, write: if true;anywhere in your rules, including the block you added "temporarily" during development. - Storage buckets: same rule. A private table with a public bucket of user uploads is not a private app.
How to check it in ten minutes. Create two accounts, A and B. Log in as A, open the network tab, and find a request that fetches A's data. Copy it. Now log in as B and replay that request with A's ID in it. If you get A's data back, you have an IDOR and you are not launching today. Repeat for every object type that matters: orders, projects, files, messages. Then log out entirely and replay the same request with no session at all.
The AI-specific version of this bug: the model wires the filter into the query the frontend sends (.eq('user_id', currentUser.id)) rather than enforcing it in the database or the route handler. That looks correct in code review and provides zero protection, because the client controls the query.
2. Nothing sensitive in the browser bundle or in git history
Second most common, and the most expensive when it goes wrong, because a leaked key gets exploited by automated scanners within minutes of hitting a public repo.
What must never be client-side: Stripe secret keys, Supabase service-role keys, OpenAI/Anthropic keys, AWS credentials, database connection strings, JWT signing secrets, webhook signing secrets, SMTP passwords. Anything prefixed NEXT_PUBLIC_ or VITE_ is compiled into JavaScript that anyone can read. Publishable keys belong there. Nothing else does.
How to check. Open your deployed site, DevTools, Sources, and search the bundle for sk_, service_role, secret, AKIA, Bearer . Then run TruffleHog or Gitleaks across your full history, not just the working tree — deleting a key in a later commit leaves it sitting in the old one forever.
If anything shows up: rotate it. Do not delete the commit and move on. Assume it is compromised, because the scrapers watching GitHub's public event firehose are faster than you.
The indie hacker known as leojr94 learned this version of it in public in March 2025, watching API usage max out and subscriptions get bypassed on a SaaS he had built with AI assistance and shipped without a review. The keys were the entry point.
3. Authentication that survives contact with a bored attacker
- Sessions and tokens validated server-side on every protected route, with real expiry and rotation, and actual invalidation on logout.
- Passwords hashed with bcrypt or Argon2. If you can see a user's password anywhere in your system, stop and fix that first.
- MFA on every account that can hurt you: GitHub, your cloud provider, your domain registrar, Stripe, your email, your database. Use an authenticator app or a hardware key, not SMS.
- Email verification on if you accept signups, or your user table becomes a spam sink within a week.
- Password reset that does not leak whether an email exists, with single-use expiring tokens.
How to check. Test the unhappy paths, which is exactly what the AI never did: wrong password five times in a row, signup with an email that already exists, click the verification link twice, request a reset for an address that was never registered, use a reset link an hour after a second one was issued. Every one of those should behave sanely and none should return a stack trace.
4. Encryption, which is mostly just turning things on
HTTPS everywhere with an HTTP redirect, HSTS with max-age of at least 31536000, no mixed content. Encryption at rest enabled on your database and storage — every managed provider has it, and on some plans it is a toggle rather than a default. Modern hosting gives you most of this free; the failure mode is an old subdomain or a staging box still answering on port 80.
5. One automated scan and one careful human pass
Point Mozilla Observatory or a similar external scanner at your domain and clear the criticals. Then do the manual authorization testing from item 1 yourself. A scanner cannot know that project 41 belongs to a different tenant; only you can test that.
Tier 2: In place at launch
Rate limiting, before it becomes a bill
Rate limits are usually filed under "abuse prevention," which undersells the risk. On a vibe-coded app that calls a paid model, an unlimited endpoint is a stranger's blank cheque written against your card.
Limit, at minimum: login and signup, password reset, any endpoint that calls an LLM or another metered API, file uploads, and anything that sends email. Add a hard monthly spend cap at the provider — every serious AI API has one, and the number of founders who discover this feature after the invoice is high.
Put Cloudflare Turnstile or an equivalent on public forms. It is free and it removes the bot floor that otherwise arrives about six hours after your launch post.
Input validation and output encoding
Validate on the server with a schema (Zod, Pydantic, whatever your stack uses) and reject unexpected fields rather than ignoring them. Use parameterized queries — most ORMs give you this, but AI-generated raw SQL with string interpolation still appears in these codebases regularly. Escape anything user-supplied before it renders, and if you accept HTML anywhere, sanitize it with a real library rather than a regex the model invented.
Security headers and configuration hygiene
| Header | Value |
|---|---|
Strict-Transport-Security | max-age=31536000; includeSubDomains |
Content-Security-Policy | Start restrictive, loosen only as needed |
X-Content-Type-Options | nosniff |
X-Frame-Options | DENY (or frame-ancestors in CSP) |
Referrer-Policy | strict-origin-when-cross-origin |
Alongside those: CORS restricted to your actual origins rather than *, debug mode off, no default admin credentials, no test accounts left in production, .env and .git not served, no database port open to the internet. Security Misconfiguration climbed to A02 in the 2025 OWASP list for a reason, and this is the category that AI scaffolding fills with permissive defaults.
Dependency and supply chain scanning
Software Supply Chain Failures entered the 2025 OWASP Top 10 as a new category at A03. Turn on Dependabot or Snyk, run npm audit before you ship, and glance at anything the AI installed that you have never heard of — models occasionally reach for abandoned packages, and hallucinated package names have become an attack surface of their own.
Errors and logs that do not leak
Generic messages to the user, detailed ones to your logs. "Something went wrong" beats a stack trace with your table names in it, and "invalid email or password" beats "user not found," which tells an attacker which addresses are worth brute-forcing. Then check the other direction: no passwords, tokens, or full card details written into logs.
Monitoring, backups, and a rollback plan
Error monitoring with alerts (Sentry's free tier is fine). Automated backups, and — the part everyone skips — one tested restore into a scratch environment. An untested backup is a belief, not a backup. Write down how to roll back a bad deploy in a file you can find at 3am, because that is when you will need it.
Ask yourself Lemkin's question in advance: if the agent dropped every table right now, what would you do in the next hour? If the answer takes longer than a minute to arrive, that is the gap.
Tier 3: Evidence, for later
Nothing here blocks a launch. All of it appears the first time a mid-size company runs you through procurement:
- A one-page architecture and data-flow diagram: what runs where, what data lives where, which third parties touch it.
- Basic written policies (access control, incident response, vendor list).
- A recent scan or pen-test report with remediation status.
- A DPA on file with each subprocessor if you handle EU data.
If you are already selling into companies that ask, our SaaS compliance checklist covers SOC 2, GDPR and the AI Act in the order they usually get demanded.
The non-security launch items that still sink launches
Security is where vibe-coded apps break worst, but it is not the only place they break.
Code and architecture. Read your own repo end to end at least once — you are the owner of this code now regardless of who typed it. Add error boundaries so one failing component does not render a white screen. Handle timeouts on every external call. Delete the debug routes and seeded test users.
Infrastructure. Custom domain with verified DNS, not a platform subdomain that quietly signals "prototype." Uptime monitoring. Stripe webhooks registered, signature-verified, and idempotent, because Stripe will retry and a non-idempotent handler will grant the same subscription twice. If you are weighing where to host all this, we compared the self-hosting alternatives to Vercel and Supabase after moving this site's own stack.
Legal. A privacy policy and terms that describe what you actually do, not what a generator assumed. Know which countries your data sits in. Set up SPF, DKIM and DMARC or your launch emails land in spam, which is a marketing failure disguised as a technical one.
Product. Walk the full path as a new user on a real phone: signup, verification, onboarding, core action, payment, cancellation. Test a real card and a declining one. Confirm your analytics events actually fire. Have a landing page that explains the product to someone who is not logged in, because your first hundred users will not create an account to find out what you do.
And spend an hour on how it looks. AI-generated interfaces converge on the same purple gradient, and users have learned to read that as unfinished. We wrote up how to avoid AI slop design with the specific tells to fix.
The 45-minute version
If you are shipping tonight, this is the minimum that meaningfully lowers your risk:
- Secrets — Gitleaks over full history, DevTools search of the bundle, rotate anything found. (10 min)
- Isolation — two accounts, replay each other's requests, then replay logged out. (15 min)
- HTTPS and headers — external scanner, fix criticals. (5 min)
- Rate limits and spend caps — auth endpoints, AI endpoints, provider-level cap. (10 min)
- Backup — run one, restore it somewhere, confirm the data is there. (5 min)
Free tooling that covers most of it: TruffleHog and Gitleaks for secrets, Dependabot for dependencies, Sentry's free tier for errors, Cloudflare Turnstile for bot filtering, Mozilla Observatory for headers, and your database provider's own RLS linter, which Supabase ships and most people never open.
Useful AI prompts for a first pass, run against your own repo:
"Review this codebase as a security specialist. List every API route and data query, and for each one state where authorization is enforced. Flag any that rely only on the client."
"Find every place a secret, key, or credential could reach the browser bundle or a client-side environment variable."
"Check this schema against OWASP Top 10:2025 A01 Broken Access Control. Which tables have no restrictive row-level policy?"
These work well because you are asking a specific adversarial question instead of "make it work." Treat the output as leads to verify, not as a clean bill of health.
Then get the thing in front of people
The security gate exists so that launching is boring. Once it is closed, the work switches to distribution, and that is where most vibe-coded products actually die — not breached, just unseen.
The straightforward version: get listed everywhere your buyers look, all in the same week rather than one submission a month. Our directory submission guide covers the sequencing, and we keep a list of directories that accept vibe-coded apps specifically, since a few of the bigger ones now filter them out.
Start with ours. SaaSCity is a directory built as a live city map — every product is a building people click on, not a row in a table nobody scrolls. You can submit your product for free and be on the map this week. Paid plans carry a dofollow link and permanent placement, and dead listings get swept rather than left to rot, which is more than most directories bother with.
If the reason you care is Domain Rating rather than traffic, our SEO Boost service is priced against outcomes: DR 30+ for $49.99, DR 50+ for $129.99, DR 70+ for $349.99, each with a 45-day window and a published proportional refund formula if we miss. Baseline gets recorded before we start, so there is nothing to argue about afterwards. You can check your current number with our free Domain Rating checker in about ten seconds.
Doing it yourself is fine too — the 0-to-30 DR playbook is the same method written out week by week, for free.
The actual argument
Vibe coding is not the problem. Shipping a demo and calling it a product is the problem, and founders have been doing that since long before an AI was involved.
What changed is the ratio. You can now build something that looks production-ready in a weekend, which means the gap between "looks finished" and "is finished" is wider than it has ever been, and nothing in the tooling flags it for you. The model will not tell you that your database is world-readable. It will tell you the deploy succeeded.
Security debt from AI-generated code costs an afternoon before launch. After launch, with real users in the table, it costs your reputation, your Stripe account, and every hour you were going to spend on growth. The five Tier 1 checks catch nearly everything that actually happens to apps this size.
Run them today. Then go launch.
— n1.ghosty, SaaSCity
Get your SaaS in front of founders
List your product on the SaaSCity live city map - a permanent listing, real discovery, and a backlink from a high-DR directory. Free to start; upgrade for a dofollow link and a building on the map.


