Troja
All posts
SaaSMar 22, 2026·15 min read

SaaS Security Checklist Before Launch: The MVP Guide

Shipping your MVP this week? Run this pragmatic, prioritized security pass first — covering auth, multi-tenancy, secrets, payments, and the few headers that actually matter.

By Marc

Ship fast, but not blind

You don't need SOC 2 to launch an MVP. You do need to not leak your users' data on day one. This checklist is ruthlessly prioritized: the things that, left undone, end in an incident — not a nice-to-have wishlist.

Tier 1: Do not launch without these

Multi-tenant isolation

The #1 SaaS-specific bug: User A can read User B's data by changing an ID. Every query that touches tenant data must be scoped to the current tenant/user.

// WRONG — trusts the client's ID
const project = await db.project.findUnique({ where: { id } });

// RIGHT — scoped to the owner
const project = await db.project.findFirst({
  where: { id, orgId: session.orgId },
});

If you use a database with row-level security (Postgres/Supabase), enforce it there too — defense in depth.

Secrets out of the codebase

grep -rniE "(sk_live|api[_-]?key|secret|password)\s*[:=]" src/

Anything not reading from process.env is a finding. And make sure your client bundle doesn't contain server secrets — only NEXT_PUBLIC_* (or your framework's public prefix) should ever reach the browser.

Real authentication

  • Use a vetted provider/library (Auth.js, Clerk, Supabase Auth, WorkOS) — don't hand-roll password hashing.
  • Hash with bcrypt/argon2, never plain or MD5/SHA-1.
  • Rate-limit login, signup, and password reset.
  • Send no "user exists" oracle on login or reset.

Input validation at every boundary

import { z } from "zod";
const Signup = z.object({
  email: z.string().email(),
  password: z.string().min(12).max(200),
});
const parsed = Signup.safeParse(body);
if (!parsed.success) return badRequest();

Tier 2: Do these before you have real users

Payment security

  • Never touch raw card numbers — let Stripe/Paddle handle PCI scope.
  • Verify webhook signatures. An unverified webhook lets anyone mark invoices paid:
const event = stripe.webhooks.constructEvent(rawBody, sig, endpointSecret);
  • Use the raw request body for signature verification, not a parsed object.

Security headers

Set these once at the edge/middleware:

Strict-Transport-Security: max-age=63072000; includeSubDomains
Content-Security-Policy: default-src 'self'
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
X-Frame-Options: DENY

Cookies and sessions

HttpOnly; Secure; SameSite=Lax on every auth cookie. Expire idle sessions. Invalidate sessions on password change.

Authorization, not just authentication

Logged-in ≠ allowed. Check roles/ownership on every privileged action, not just on page load.

Tier 3: Before you scale / take money seriously

  • Rate limiting on all public and expensive endpoints.
  • Logging and alerting — you can't respond to what you can't see. Log auth events and admin actions.
  • Backups that you've actually tested restoring.
  • Dependency hygiene: npm audit in CI; patch high/critical.
  • A way to rotate secrets without a redeploy scramble.
  • An email like security@yourdomain so researchers can reach you.

The 10-minute pre-launch sweep

  1. curl -sI https://yourapp.com — headers present?
  2. Try to read another tenant's record by ID — does it 403?
  3. grep for secrets — clean?
  4. Inspect the JS bundle — no server keys?
  5. Hit your Stripe webhook with a bad signature — rejected?
  6. npm audit --omit=dev — no high/critical?
  7. Cookies — HttpOnly + Secure + SameSite?
  8. Login endpoint — rate-limited?

What you can safely defer

Pentests, a bug-bounty program, SSO/SAML, and full compliance audits can wait until you have customers who ask. Don't let "we should get SOC 2" block a launch when the actual risk is an unscoped query.

Scan it with Troja

Troja runs the whole Tier 1 and Tier 2 sweep automatically — tenant isolation, exposed secrets, missing headers, weak cookies, unverified webhooks — and gives you a launch-readiness report with fix prompts. Scan before you flip the switch to public.

Run the scan this post is about.

Free, no signup. See what's hiding inside your walls in ~30 seconds.

Free scan · no signup · results in ~30 seconds
SaaS Security Checklist Before Launch: The MVP Guide — Troja