Techsy
Contact
Get Started
Back to Blog
cybersecurity

SaaS Security Checklist Before Launch: 40 Checks We Run First (2026)

Written by Mert Batur Gürbüz
Updated Jul 23, 2026
14 read
Table of Contents
SaaS Security Checklist Before Launch: 40 Checks We Run First (2026)

A SaaS security checklist before launch is worth more than a stack of compliance badges you don't have yet. Here's the uncomfortable truth: most launch checklists tell you what to secure and never show you how. This one ships the code. We build on Next.js and Supabase, we've watched a single missing tenant_id filter let a test account read another customer's data, and IBM's 2024 Cost of a Data Breach report put the global average at $4.88M. You don't need SOC 2 to go live. You need the app-layer baseline below, grouped, runnable, and mapped to OWASP and NIST.

Key Takeaways

  • You don't need SOC 2 or a pentest to launch. You need the app-layer baseline below.
  • The most dangerous launch bug is cross-tenant data leakage from a missing tenant_id check.
  • Never roll your own auth. Use Auth.js, Clerk, or Supabase Auth.
  • Audit NEXT_PUBLIC_ prefixes before you deploy. That's the fastest way to leak a secret.

This baseline maps to OWASP ASVS 5.0 and the NIST Secure Software Development Framework (SSDF), the two references Google trusts most for this topic and, notably, the two neither of the top-ranking guides bothers to cite.

Your Pre-Launch Security Checklist (Quick Version)

These are the minimum security requirements before you ship, grouped into six categories. Forty items. Run them top to bottom, hand the code sections to your dev, and treat anything marked P0 in the priority table below as a launch blocker.

Secrets & Config

  1. .env is in .gitignore from commit one and was never committed.
  2. Every NEXT_PUBLIC_ and VITE_ prefix is audited; nothing secret ships to the browser.
  3. Server secrets live in a manager (platform env vars, AWS Secrets Manager, Vault), not the repo.
  4. Any key that ever touched git history is rotated before launch.
  5. No secrets appear in logs, error payloads, or the client bundle.
  6. You grepped the built bundle for live keys (grep -r "sk_live" .next/).

Auth & Access

  1. Auth is built on a library (Auth.js, Clerk, or Supabase Auth), not hand-rolled.
  2. MFA is available on accounts.
  3. Session cookies set Secure, HttpOnly, and SameSite.
  4. No JWTs or session tokens are stored in localStorage.
  5. RBAC and least-privilege roles are enforced server-side, not just hidden in the UI.
  6. Passwords are hashed with Argon2 or bcrypt (only if you self-manage auth).
  7. Password-reset and email-verification flows are tested against abuse.

Data & Tenancy

  1. Every query carries a tenant_id filter.
  2. Tenant scoping is enforced at the ORM or repository layer, not remembered per query.
  3. Row-level security is enabled and its failure modes are understood.
  4. Every object-ID endpoint runs an ownership check (this kills IDOR).
  5. tenant_id is included in cache keys and object-storage paths.
  6. Data is encrypted at rest and in transit.
  7. Payment and webhook signatures (Stripe, etc.) are verified server-side.

Dependencies & Supply Chain

  1. npm audit or pnpm audit is clean of high and critical (or explicitly triaged).
  2. Dependabot or Renovate is enabled.
  3. Snyk or Socket runs deeper SCA plus malware and license checks.
  4. The lockfile is committed.
  5. No abandoned or unmaintained packages sit in the critical path.
  6. Container images are scanned if you ship Docker.

Network & Transport

  1. HTTPS is enforced everywhere, with HSTS preload.
  2. A Content-Security-Policy is set (report-only first, then enforce).
  3. X-Content-Type-Options: nosniff and X-Frame-Options/frame-ancestors are set.
  4. Referrer-Policy and Permissions-Policy are set.
  5. CORS uses an allow-list, never * with credentials.
  6. Rate limiting protects auth and expensive endpoints.
  7. Every endpoint validates input with a schema (Zod or similar).

Monitoring & Response

  1. Centralized audit logs record who accessed what, and when.
  2. Error handling never leaks stack traces to users.
  3. Alerts fire on auth anomalies (failed-login spikes, impossible travel).
  4. Automated backups run, and you have tested a restore.
  5. An incident-response contact and one-page runbook exist.
  6. Uptime and error monitoring (Sentry or equivalent) is live.
  7. You know the trigger for bringing in a pentest.

Fix-First Priority

Not every item blocks launch. This triage table sorts the baseline by damage-if-skipped so a founder knows what's non-negotiable. P0 = fix before launch, P1 = fix week one, P2 = fix within the quarter.

CheckCategoryIf you skip itFix effortShip-blocker?
Cross-tenant isolation on every queryData & TenancyOne customer reads another's dataMedP0: block launch
Secrets out of the client bundleSecrets & ConfigPublic API keys, account takeoverLowP0: block launch
Ownership check on object-ID endpointsData & TenancyIDOR: incrementing an id leaks recordsLowP0: block launch
Auth on a library, not hand-rolledAuth & AccessShipped auth bugs, broken sessionsMedP0: block launch
HTTPS and HSTS everywhereNetwork & TransportToken theft over the wireLowP0: block launch
npm audit clean of high/criticalDependenciesKnown CVE in a transitive depLowP1: week one
Rate limiting on auth endpointsNetwork & TransportCredential stuffing, brute forceLowP1: week one
Security headers (CSP, HSTS, nosniff)Network & TransportXSS, clickjacking, MIME attacksLowP1: week one
Centralized audit logsMonitoringYou can't see or prove a breachMedP1: week one
Tested backup restoreMonitoringA backup that doesn't restore is nothingMedP1: week one
MFA available on accountsAuth & AccessEasier account takeoverLowP2: this quarter
Full CSP enforced past report-onlyNetwork & TransportResidual XSS surfaceMedP2: this quarter

Secrets & Config: Are Any Keys Leaking Into Your Client Bundle?

Secrets hygiene at launch means no credential ever reaches the browser. The NEXT_PUBLIC_ prefix in Next.js (and VITE_ in Vite) ships a value to every visitor, so one wrong prefix leaks a key. Keep .env out of git, put server secrets in a manager, and grep your build output before you deploy.

Here's the gotcha we see most: NEXT_PUBLIC_ doesn't mean "public info." It means "I am literally shipping this to every visitor's browser." Prefix a Stripe secret or a service-role key that way and it's live in the bundle for anyone who opens DevTools.

bash
# .env.local (the mistake)
NEXT_PUBLIC_STRIPE_SECRET=sk_live_51H...   # BAD: ships to every browser
STRIPE_SECRET_KEY=sk_live_51H...           # OK: server-only

# Public (safe to expose) vs server-only
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...       # anon key is meant to be public
SUPABASE_SERVICE_ROLE_KEY=eyJ...           # never NEXT_PUBLIC_ this

# Catch a leaked key before you deploy
grep -r "sk_live" .next/    # any hit means a secret is in your client bundle

The rest of the secrets baseline is boring and non-negotiable: .env in .gitignore from the first commit, server secrets in a manager rather than the repo, and a rotation of any key that ever touched git history (deleting a commit does not un-leak it). A leaked deploy token is exactly how breaches like the Vercel incident start, so treat every token as if it's already on someone's watchlist.

Auth & Access: Should You Build Auth or Use a Library?

Should you build auth or use a library? Almost always use a library. Auth.js, Clerk, and Supabase Auth have absorbed years of edge cases you'll otherwise rediscover in production: session fixation, token revocation, reset-flow abuse. Rolling your own is defensible only with a security engineer and a reason no provider fits, which is rare.

Rolling your own auth is the most expensive way to save $25 a month. Here's how the honest options compare.

OptionBest whenMFA built-inDefault sessionGotcha
Auth.js (NextAuth)You want free, self-hosted, full controlVia providers/add-onsJWT or databaseYou own every security edge case
ClerkYou want MFA, UI, and orgs out of the boxYesManagedPaid tiers scale with active users
Supabase AuthYou already run Supabase and Postgres RLSYesJWTRLS policy quality is on you
Roll your ownYou have a security engineer and no provider fitsYou build itYou build itMost auth bugs start right here

Two pitfalls sink teams that pick a library but skip the config. First, JWT revocation is genuinely hard, so a stolen token stays valid until it expires; keep token lifetimes short and prefer server-side sessions for anything sensitive. Second, tokens in localStorage are stealable by any XSS payload, so store sessions in httpOnly cookies with Secure and SameSite. Enforce RBAC on the server, not by hiding buttons in the UI.

If your SaaS ships an AI or LLM feature, treat model input as an untrusted auth boundary too. See our guide to preventing prompt injection, because a jailbroken assistant with tool access is an access-control problem wearing a chat window.

Data & Tenancy: How Do You Stop One Tenant From Reading Another's Data?

Tenant isolation means every query, cache key, and storage path is scoped to the current tenant. A missing tenant_id filter lets one customer read another's data, the most dangerous launch bug there is. Row-level security helps, but it's a seatbelt, not a force field, so add ownership checks on every object-ID endpoint too.

This is the section no competitor covers as code, and it's the reason cross-tenant leaks slip into production. The fix starts with never trusting an ID on its own. Scope every read to the caller's tenant, and enforce it at the data layer so nobody has to remember it per query.

ts
// BAD: no tenant scope. Any valid id returns any tenant's row.
const order = await db.order.findFirst({ where: { id } });

// GOOD: scoped to the caller's tenant on every read.
const order = await db.order.findFirst({
  where: { id, tenantId: session.tenantId },
});

The related failure is IDOR (insecure direct object reference), which the OWASP API Security Top 10 files under API1: Broken Object Level Authorization. A test account increments an ID in the URL and reads a record it should never see. PortSwigger's Web Security Academy has a full walkthrough of how attackers find these. The fix is one ownership check.

ts
// GET /api/invoices/1234, a test account increments the id
// and reads another tenant's invoice. Classic BOLA.
const invoice = await db.invoice.findUnique({ where: { id } });

// Fix: verify ownership before you return anything.
if (invoice.tenantId !== session.tenantId) {
  return res.status(403).json({ error: "Forbidden" });
}

Here's the framing that keeps you honest: every IDOR is a tenant-isolation failure, but not every tenant-isolation failure is an IDOR. Postgres row-level security catches many of them at the database, but it has silent-failure modes worth knowing before you rely on it.

sql
-- Postgres RLS: a seatbelt, not a force field.
alter table orders enable row level security;

create policy tenant_isolation on orders
  using (tenant_id = current_setting('app.tenant_id')::uuid);
-- Silent-failure trap: forget to SET app.tenant_id on a pooled
-- connection and the policy reads the PREVIOUS request's tenant.

Connection-pool contamination, async-context leaks, and shared-cache poisoning all defeat RLS quietly, which is why the OWASP Multi-Tenant Security Cheat Sheet tells you to prefix cache keys and storage paths with the tenant too. The access-control chapter of OWASP ASVS 5.0 and the Supabase RLS docs are the two references worth reading in full here.

Dependencies & Supply Chain: What's Hiding in Your node_modules?

Your app is only as safe as its weakest transitive dependency. Run npm audit or pnpm audit in CI and fail the build on high or critical findings before you ever launch. Add Dependabot or Renovate for automatic updates, and Snyk or Socket for deeper malware and license checks.

The trap is running the audit once by hand, seeing green, and never running it again. Wire it into CI so a new CVE in a package you haven't touched still blocks the merge.

yaml
# .github/workflows/ci.yml: block the merge on high/critical
- name: Audit dependencies
  run: npm audit --audit-level=high    # non-zero exit fails the job

The npm audit docs cover the severity levels and the --production flag if you want to ignore dev-only findings. Automated scanning is table stakes, though; for deeper static analysis that catches code smells and injection paths a dependency scanner misses, see our SonarQube review. Commit your lockfile, drop packages that haven't shipped a release in years, and scan your container image if you deploy Docker.

Network & Transport: Which Security Headers Does a SaaS Actually Need?

Which security headers does a SaaS need? HTTPS plus HSTS and a short header set close the easiest-to-exploit gaps. Add a Content-Security-Policy, a CORS allow-list instead of a wildcard, and rate limits on auth and expensive endpoints. Validate every input with a schema like Zod so bad payloads never reach your logic.

You don't need every header ever invented. You need this short list, and the MDN security-headers reference explains each in depth.

HeaderRecommended valueWhat it stops
Strict-Transport-Securitymax-age=63072000; includeSubDomains; preloadProtocol downgrade, SSL-strip attacks
Content-Security-Policydefault-src 'self'; start report-onlyXSS, injected scripts, data exfiltration
X-Content-Type-OptionsnosniffMIME-sniffing that turns an upload into a script
X-Frame-Options / frame-ancestorsDENY (or frame-ancestors 'none')Clickjacking via hidden iframes
Referrer-Policystrict-origin-when-cross-originLeaking full URLs (and tokens inside them)
Permissions-Policycamera=(), microphone=(), geolocation=()Rogue scripts touching device APIs

Set the headers once, at the edge, and add a rate limiter so a script can't brute-force your login route all night.

js
// next.config.js: security headers on every response
const securityHeaders = [
  { key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains; preload" },
  { key: "X-Content-Type-Options", value: "nosniff" },
  { key: "X-Frame-Options", value: "DENY" },
  { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
];
// Rate-limit auth routes (Upstash example)
const { success } = await ratelimit.limit(ip);
if (!success) return new Response("Too many requests", { status: 429 });

Start your CSP in report-only so you don't break your own app, watch the violation reports for a few days, then flip it to enforce. Keep CORS to a named allow-list, and never pair * with credentials.

Monitoring & Response: How Will You Know If You've Been Breached?

You can't respond to what you can't see. Before launch, wire up centralized audit logs, alerts on auth anomalies like failed-login spikes, automated backups with a tested restore, and a one-page incident runbook. An untested backup is a hope, not a backup, and the time to write the runbook is now, not mid-incident.

IBM's data puts the average time to identify and contain a breach at 258 days, and you can't shrink that number if your logs don't record who touched what. Centralize them, alert on the anomalies that matter (failed-login spikes, impossible-travel logins, sudden export volume), and make sure your error handler returns a clean message instead of a stack trace that maps your internals.

Modern breach detection leans on anomaly monitoring rather than static rules; there's more on how that actually works in our piece on how AI prevents data breaches. For the framework backing, the NIST SSDF (SP 800-218) lays out the respond-and-monitor practices in plain language. Test a restore before launch, not after your database disappears.

What We Actually Find When We Review Our Own Launches

When our team runs a pre-launch security pass on a build, ours or a client's, two misses show up more than anything else. First: a secret riding into the browser on a NEXT_PUBLIC_ prefix, usually a third-party API key someone prefixed to make a client-side call work. Second: at least one endpoint missing its tenant_id scope or an ownership check.

The tenant-scope miss is the scary one because the app looks fine. Every page loads. The bug only shows up when someone changes an ID in the URL. On one review, GET /api/orders/:id returned any order to any logged-in user; a test account read another tenant's orders by incrementing the number. The fix was two lines: compare order.tenantId to session.tenantId before returning.

We won't quote you a fake catch-rate here. What's honest and repeatable is this: the NEXT_PUBLIC_ leak and the missing tenant scope are the two things we find in almost every first-pass review, and both are cheap to fix once you know to look for them. That's exactly why the checklist front-loads them as P0.

If you'd rather have a team run this pass for you before launch day, that's the work we do. Get a pre-launch security review →

About the Author

Mert Batur Gurbuz is Co-Founder of Techsy.io, where the team ships AI agents, automation systems, and voice/SDR pipelines for B2B clients. He studies at the University of Birmingham and writes about the LLM tooling stack the Techsy team actually uses in production. Connect on LinkedIn.

Frequently Asked Questions

What should be on a SaaS security checklist before launch?

Six categories: secrets and config (keep keys out of the client bundle), auth and access (use a library, add MFA), data and tenancy (tenant_id scoping plus ownership checks), dependencies (npm audit in CI), network and transport (HTTPS, HSTS, CSP, rate limits), and monitoring and response (audit logs, tested backups, a runbook).

Is my SaaS secure enough to launch?

You're ready when the P0 baseline is done: secrets out of the client bundle, tenant isolation on every query, auth on a library, HTTPS with security headers, and a clean dependency scan. Perfection is not the bar. A shipped, monitored app with the baseline covered beats a "perfect" one that never launches.

Do I need a penetration test before launching a SaaS?

Not to launch legally. Prioritize one if you handle payments or PII, target enterprise buyers, or an auditor or investor asks. At MVP stage, spend that effort on the app-layer baseline and the OWASP Top 10 first. A pentest finds more when the obvious IDOR and header gaps are already closed.

Do I need SOC 2 to launch a SaaS?

No. No customer expects SOC 2 from a startup that launched last week. It's an enterprise-sales unlock, not a launch gate, and it takes months. Ship with the app-layer baseline, then start the SOC 2 process when a real enterprise deal needs it, not before.

Should I build my own authentication or use a library like Auth.js, Clerk, or Supabase Auth?

Almost always use a library. Auth.js, Clerk, and Supabase Auth have handled the session, token, and reset-flow edge cases that cause most self-built auth bugs. Rolling your own is defensible only if you have a security engineer and a hard requirement no provider meets, which is genuinely rare.

How do I keep secrets out of my client bundle?

Audit every NEXT_PUBLIC_ and VITE_ prefix, because anything with that prefix ships to the browser. Keep .env out of git from the first commit, store server secrets in a manager, and grep your built bundle (grep -r "sk_live" .next/) before you deploy to catch a leaked key.

How do I isolate tenant data in a multi-tenant SaaS?

Put a tenant_id filter on every query and enforce it at the ORM or repository layer so it's automatic. Enable row-level security and learn its failure modes (pool contamination, async leaks). Add an ownership check to every object-ID endpoint to close IDOR, and scope cache keys and storage paths by tenant.

What security headers does a SaaS need before launch?

At minimum: Strict-Transport-Security (HSTS), a Content-Security-Policy, X-Content-Type-Options: nosniff, X-Frame-Options or frame-ancestors, Referrer-Policy, and Permissions-Policy. Start your CSP in report-only, review violations, then enforce it. The MDN security-headers docs list recommended values for each, and the headers table above summarizes what each one stops.

Is automated scanning like npm audit or Snyk enough?

Necessary but not sufficient. Tools like npm audit, Snyk, and Socket catch known CVEs and malicious packages, but they can't find business-logic and access-control flaws like IDOR or a missing tenant scope. Those need a human, a test account, and an explicit ownership check. Run both: the scanner and a manual pass.

The Bottom Line: A SaaS Security Checklist You Can Actually Ship

You don't need to be perfect to launch. You need the baseline. Close the P0 items first: secrets out of the bundle, tenant isolation on every query, an ownership check on every object endpoint, auth on a library, and HTTPS with headers. If you fix one thing before Friday's launch, make it tenant isolation, because that's the bug that leaks a customer's data with zero warning.

Everything here is runnable today, and none of it requires a compliance budget. Work through the 40 checks, hand the code sections to your dev, and ship. Want a second set of eyes before you go live? Get a free consultation and we'll walk the list with you.

Tags

saas security checklist before launchsaas security best practicestenant isolationowasp asvspre-launch security checklist

Share this article

Related Articles

More in cybersecurity

cybersecurity
May 20, 2026

GitHub Got Hacked by a VS Code Extension (May 2026): The 60-Minute Emergency Playbook Every Developer Should Run Tonight

GitHub confirmed 3,800 internal repos were exfiltrated via a malicious VS Code extension on May 20, 2026. Here's the 60-minute playbook every developer should run before bed — plus the misconception the headlines got wrong.

14 min read read
Read
cybersecurity
May 8, 2026

How AI Prevents Data Breaches: 7 Defenses That Stopped Real Attacks (2026)

On April 30, 2026, ~275 million students learned their LMS had been breached. Could AI have stopped it? Here are 7 defenses that already do, and how to build them into your app this week.

13 min read read
Read
cybersecurity
May 5, 2026

Copy Fail (CVE-2026-31431): The 60-Minute Emergency Patch Playbook for Linux, Kubernetes, and AI Infrastructure

Microsoft disclosed CVE-2026-31431 ('Copy Fail') on May 1, 2026 — a Linux kernel privilege escalation that bypasses Kubernetes RuntimeDefault seccomp and puts every multi-tenant inference cluster, agent runtime, and CI runner in scope. Here's the 60-minute patch playbook, with per-distro commands, a copy-paste seccomp profile, and the AI-infra exposure analysis nobody else is publishing.

12 min read read
Read
View All Posts
Start Your Project

Ready to build something extraordinary?

Let's turn your vision into reality. Our team is ready to help you create software that makes a difference.

Book a 30-min scoping callView Our Work

Hot from the library

Resources

See all
  • The Software Procurement Playbook

    A repeatable framework for buying software without burning six months and a million dollars on the wrong platform.

  • The Architecture Decision Playbook

    A practical framework for picking your stack: when to build vs. buy, monolith vs. microservices, and how to avoid resume-driven design.

  • The Vendor Selection Playbook

    How to pick the right development partner (agency, freelancer, in-house) without overpaying or shipping a half-built product.

Claude Skills

See all
  • New Post

    Full SEO blog pipeline: research, brief, write, validate, image, translate, publish to Sanity. Autonomous from start to finish.

  • Content Refresh

    Audit a stale post, find decay drivers, and ship a SERP-aligned refresh without losing existing rankings.

  • SEO Audit

    Site-wide SEO audit with prioritized fix list: technical, on-page, and EEAT signals.

AI Automations

See all
  • Security Auditor

    Weekly SCA + IaC scan with prioritized fix PRs.

  • Cold Email Writer

    Generates first-touch emails grounded in one specific public detail.

  • Lead Research Agent

    Enrich an email into a profile, score fit, alert in Slack.

Hot from the library

Resources

See all
  • The Software Procurement Playbook

    A repeatable framework for buying software without burning six months and a million dollars on the wrong platform.

  • The Architecture Decision Playbook

    A practical framework for picking your stack: when to build vs. buy, monolith vs. microservices, and how to avoid resume-driven design.

  • The Vendor Selection Playbook

    How to pick the right development partner (agency, freelancer, in-house) without overpaying or shipping a half-built product.

Claude Skills

See all
  • New Post

    Full SEO blog pipeline: research, brief, write, validate, image, translate, publish to Sanity. Autonomous from start to finish.

  • Content Refresh

    Audit a stale post, find decay drivers, and ship a SERP-aligned refresh without losing existing rankings.

  • SEO Audit

    Site-wide SEO audit with prioritized fix list: technical, on-page, and EEAT signals.

AI Automations

See all
  • Security Auditor

    Weekly SCA + IaC scan with prioritized fix PRs.

  • Cold Email Writer

    Generates first-touch emails grounded in one specific public detail.

  • Lead Research Agent

    Enrich an email into a profile, score fit, alert in Slack.

Services

  • Enterprise Solutions
  • Mobile Apps
  • Web Applications

Solutions

  • CRM Systems
  • AI Integration
  • ERP Solutions
  • Voice Agents
  • Process Automation
  • Cybersecurity

Library

  • Resources
  • Blog
  • Portfolio

Community

  • AI Automations
  • Claude Skills

Tools

  • Mobile App Cost Calculator
  • OpenAI / LLM API Cost Calculator
  • MVP Cost Calculator
  • Voice AI Agent Cost Calculator

Company

  • About
  • Partners
  • Contact

Legal

  • Privacy Policy
  • Terms of Service
  • Cookie Policy

Services

  • Enterprise Solutions
  • Mobile Apps
  • Web Applications

Solutions

  • CRM Systems
  • AI Integration
  • ERP Solutions
  • Voice Agents
  • Process Automation
  • Cybersecurity

Library

  • Resources
  • Blog
  • Portfolio

Community

  • AI Automations
  • Claude Skills

Tools

  • Mobile App Cost Calculator
  • OpenAI / LLM API Cost Calculator
  • MVP Cost Calculator
  • Voice AI Agent Cost Calculator

Company

  • About
  • Partners
  • Contact
LegalPrivacy PolicyTerms of ServiceCookie Policy
TECHSY
© 2026 Techsy. All rights reserved.