Back to Blog
ArticleAugust 3, 202623 min read

Vibe Coding Security Checklist: 40 Checks Before Shipping an AI-Built SaaS

A practical, evidence-based security checklist for founders and developers preparing an AI-built SaaS for production. Review authentication, authorization, Supabase RLS, secrets, billing, webhooks, abuse prevention, dependencies, backups, and release controls before real customers arrive.

Ryan Almasu

Written by

Ryan Almasu

Security shield and checklist representing 40 security checks for an AI-built SaaS before launch

The dangerous part of vibe coding is not that the application fails to run.

The dangerous part is that it runs, looks finished, accepts real users, and quietly trusts something it should never have trusted.

A signup flow can work while allowing unlimited automated account creation. A dashboard can display the correct customer data while its underlying API accepts another customer’s record ID. A Stripe checkout can complete while an unsigned webhook grants paid access. A Supabase query can return the expected rows during testing while Row Level Security remains disabled for everyone else.

These are not unusual edge cases. They are the difference between software that demonstrates an idea and software that can safely hold customer identities, private records, payments, and operational access.

Recent research into vibe-coded applications has identified recurring weaknesses such as exposed secrets, insufficient input filtering, placeholder logic, and security failures caused by an AI agent losing context or optimizing only for the immediate task. Another benchmark found a wide gap between implementations that were functionally correct and those that were secure, although its results should be understood as evidence from a particular benchmark rather than a universal measurement of every coding model. Read the 2026 study on vibe-coded application security and the earlier benchmark of security in agent-generated code.

This does not mean AI-assisted development is inherently unsuitable for production. It means generated code must be treated as unverified code until its security properties are demonstrated.

The following checklist turns that principle into a practical release gate for a Next.js and Supabase SaaS, including authentication, billing, webhooks, background work, email, admin operations, and production recovery.

What Is a Vibe Coding Security Checklist?

A vibe coding security checklist is a set of verifiable controls used to determine whether an AI-built application is safe enough to expose to real users.

It is not a prompt asking an AI agent whether the code is secure. It is not a dependency scan that returns zero known vulnerabilities. It is not a successful deployment or a green end-to-end test.

A useful checklist requires three things for every security claim:

RequirementQuestion it must answer
ControlWhat protection should exist?
VerificationHow will you prove that protection works?
EvidenceWhat result, test, configuration, or log can another person inspect?

For example, “the dashboard requires authentication” is only a claim. A stronger release check proves that an unauthenticated request to the underlying Server Action, Route Handler, and database query is rejected—even when the browser interface is bypassed.

That evidence-driven approach is consistent with the purpose of the OWASP Application Security Verification Standard, whose latest stable release is ASVS 5.0.0. OWASP describes ASVS as a basis for testing technical security controls, rather than merely listing vulnerabilities developers should know about.

Why AI-Built SaaS Products Need a Different Release Gate

Traditional security mistakes still apply to AI-generated software. Broken access control, security misconfiguration, supply-chain failures, injection, authentication failures, inadequate logging, and unsafe exception handling remain prominent categories in the OWASP Top 10:2025.

What changes with vibe coding is the way those mistakes can enter and spread through a codebase.

An AI agent may correctly implement a page while misunderstanding the authorization model behind it. It may reuse an earlier helper without noticing that the helper trusts client-supplied ownership data. It may add a service-role database client to resolve an RLS error, turning a policy problem into a full authorization bypass. It may generate retry logic that repeats a payment-side effect. It may hide a button for non-admin users without protecting the action that the button calls.

The code can look consistent because the same agent generated both the vulnerable pattern and every place where that pattern is reused.

This is why the transition described in Vibe Coding a SaaS: From Prototype to Production depends on ownership and verification, not simply producing more code. A maintainable production-ready SaaS foundation can reduce the number of security boundaries you must reinvent, but product-specific permissions and workflows still require deliberate review.

The release question is not “Did the AI finish the feature?” It is “What prevents an attacker from using this feature differently from the way the interface intended?”

How to Use This Checklist

Run the checklist against the production candidate, not an earlier local version.

Every check should finish in one of four states:

StateMeaning
PassThe control exists and current evidence proves it works.
FailThe control is absent, bypassable, or incorrectly configured.
BlockedThe result cannot be verified because access, tooling, or an environment is missing.
ExceptionThe risk was consciously accepted by a named owner with a reason and review date.

“Blocked” is not the same as “pass.” If nobody can verify whether production backups can be restored, the application does not have a verified recovery path.

The checklist should also be rerun after material changes to authentication, permissions, database policies, billing, webhooks, infrastructure, or dependency versions. Security evidence becomes stale when the code or environment it describes changes.

1. Establish Code Ownership and Threat Boundaries

AI-generated code becomes safer when someone can explain what it does, what it trusts, and what happens when that trust is abused.

Start with the workflows that can expose customer data, change access, move money, send messages, alter configuration, or perform administrative actions. These paths deserve deeper review than a static marketing page.

No.Security checkPassing evidence
1Every generated change affecting auth, billing, data access, files, email, admin actions, or infrastructure has a human reviewer.Pull request history identifies a reviewer who can explain the security boundary.
2Critical workflows have a lightweight threat model.A document names protected assets, trusted actors, entry points, likely abuse cases, and expected controls.
3The repository contains no placeholder authorization, temporary bypass, mock identity, or “allow all” production path.Searches for TODO, FIXME, mock users, test roles, bypass flags, and permissive policies have been reviewed.
4Security-sensitive changes are small enough to review.The final change separates unrelated refactors from changes to permissions or trust boundaries.
5Each risky release has a rollback or forward-fix plan.The release record explains how code, configuration, schema, and credentials can be recovered.

A threat model does not need to become a large compliance document. For a solo founder, a one-page table can be enough. The value comes from forcing the builder to identify what an attacker can control.

Consider a customer export feature. The attacker may control the requested export ID, filters, output format, and timing. The protected assets include customer records, email addresses, billing metadata, and stored files. The controls might include server-side permission checks, account-scoped queries, rate limits, audit logs, and short-lived download URLs.

Without that model, an AI agent may focus only on generating the CSV.

2. Remove Secret Exposure and Environment Confusion

Secrets commonly leak because generated code prioritizes connectivity over boundary design. When a browser request fails, the quickest apparent fix may be to move a privileged key into a client-accessible environment variable. The feature starts working, but the application’s security model collapses.

The browser must be assumed to expose every value bundled into it. Supabase publishable or anonymous keys are designed to be public; they rely on database roles and RLS for protection. The service-role key is different: it bypasses RLS and must never reach the client.

Supabase’s documentation explains that publishable keys can be recovered through browser inspection and that access must therefore be controlled through RLS and appropriate grants. Read Supabase’s API-key security guidance.

No.Security checkPassing evidence
6No privileged secret is present in browser code, static assets, source maps, logs, analytics events, or error responses.A production bundle and browser network inspection show only intentionally public values.
7Supabase service-role credentials are imported only from server-only modules.Repository search confirms there is no client import path or NEXT_PUBLIC_ service credential.
8The full Git history has been checked for committed credentials.Secret scanning reports are reviewed, not limited to the current working tree.
9Development, preview, test, and production use separate credentials and webhook secrets.Environment inventories show distinct keys and no production credentials in local defaults.
10Every exposed or uncertain credential has been revoked and rotated.Provider dashboards show the old credential disabled and the replacement deployed successfully.

A .env file is not automatically safe. Its security depends on where it is stored, whether it is committed, which build step consumes it, and whether its variable name causes a framework to expose it to the browser.

Use repository and platform controls in addition to manual review. GitHub’s secret scanning documentation explains that scanning can inspect the repository’s history for known credential patterns. The OWASP Secrets Management Cheat Sheet also recommends centralized storage, least-privilege access, auditing, rotation, revocation, and defined secret lifecycles.

Do not merely delete a leaked key from the latest commit. Anyone with the previous commit, build log, deployment artifact, or cloned repository may still possess it. Revocation is the security action; deleting the line is only cleanup.

3. Verify Authentication, Sessions, and Authorization Separately

Server-side authentication and authorization flow verifying identity, roles, team membership, and resource access

Authentication answers who the user is. Authorization answers whether that user may perform a specific operation on a specific resource.

AI-generated applications frequently implement the first and assume it provides the second.

A user being signed in does not mean they can update every project, read every invoice, trigger every export, or call every admin action. Hiding controls in the interface does not protect the underlying server operation.

Next.js explicitly recommends treating Server Actions and Route Handlers as public-facing endpoints and performing their own authorization checks. Its authentication guide also recommends centralizing secure authorization in a data access layer rather than relying only on optimistic checks in routing or interface code. Review the current Next.js authentication and authorization guidance.

No.Security checkPassing evidence
11Every protected operation authenticates the user on the server.Direct unauthenticated calls to Server Actions and Route Handlers return an appropriate rejection.
12Every resource operation verifies object-level authorization.Tests prove User A cannot read, update, export, or delete a resource owned by User B.
13Roles and permissions are loaded from trusted server or database state.Changing a cookie, form field, URL parameter, or client payload cannot grant a higher role.
14Session cookies use appropriate HttpOnly, Secure, SameSite, path, and expiration settings.Production response headers and session tests confirm the intended cookie configuration.
15Administrative and account-recovery paths receive stronger protection.Admin accounts use MFA where supported, sensitive actions require re-verification, and recovery events are logged.

Object-level authorization deserves special attention. The OWASP API Security Top 10 identifies broken object-level authorization as a leading API risk because applications frequently accept user-controlled record identifiers without confirming that the caller may access the referenced object.

A reusable authorization helper can make this boundary visible:

import "server-only";

import { createClient } from "@/lib/supabase/server";

export async function requireProjectAccess(projectId: string) {
  const supabase = await createClient();

  const {
    data: { user },
  } = await supabase.auth.getUser();

  if (!user) {
    throw new Error("UNAUTHENTICATED");
  }

  const { data: membership, error } = await supabase
    .from("project_members")
    .select("role")
    .eq("project_id", projectId)
    .eq("user_id", user.id)
    .maybeSingle();

  if (error || !membership) {
    throw new Error("FORBIDDEN");
  }

  return {
    userId: user.id,
    role: membership.role,
  };
}

The important property is not the exact helper name. It is that identity comes from the verified session, the resource comes from the operation being attempted, and permission comes from trusted application state.

Do not accept a userId, role, isAdmin, or ownerId from the browser and treat it as proof.

4. Enforce Supabase and PostgreSQL Data Boundaries

Supabase Postgres database protected by Row Level Security policies, grants, and trusted access controls

Supabase makes it convenient to query Postgres from the browser, but that convenience depends on correctly configured database controls.

RLS should not be treated as an optional hardening layer added after the application works. For tables exposed through Supabase’s API, it is part of the application’s authorization boundary.

Supabase states that RLS must be enabled on tables in exposed schemas and recommends reviewing Security Advisor findings before production. Its production checklist also warns that exposed tables without RLS and reasonable policies may allow clients to access or modify data. Read the Supabase RLS guide and production checklist.

No.Security checkPassing evidence
16RLS is enabled on every table exposed through the API.A schema query and Supabase Security Advisor show no unintentionally exposed table without RLS.
17Policies are tested with positive and negative identities.Tests cover owner, non-owner, anonymous, authenticated, disabled, and elevated cases where applicable.
18Grants match the intended API surface.anon and authenticated roles possess only the table and function privileges they require.
19SECURITY DEFINER functions and views are explicitly hardened.Functions use a controlled search_path, validate callers, and revoke unintended execute access.
20Storage and Realtime authorization match database access rules.Unauthorized users cannot list, download, upload, update, subscribe to, or infer protected objects.

A basic owner policy might look like this:

alter table public.projects enable row level security;

revoke all on table public.projects from anon, authenticated;
grant select, insert, update, delete
  on table public.projects
  to authenticated;

create policy "Users can read their own projects"
on public.projects
for select
to authenticated
using (owner_id = (select auth.uid()));

create policy "Users can create their own projects"
on public.projects
for insert
to authenticated
with check (owner_id = (select auth.uid()));

create policy "Users can update their own projects"
on public.projects
for update
to authenticated
using (owner_id = (select auth.uid()))
with check (owner_id = (select auth.uid()));

create policy "Users can delete their own projects"
on public.projects
for delete
to authenticated
using (owner_id = (select auth.uid()));

That example is intentionally simple. Real products may need team membership, organization roles, suspended-account behavior, service workflows, or immutable records. Those rules should be represented in policies and tested as explicit business cases.

The deeper Shipflash guide to Supabase Row Level Security explains how policies, grants, service-role boundaries, negative tests, and SECURITY DEFINER functions work together.

RLS also does not remove the need for application-level authorization. The application should reject an unauthorized operation intentionally, while the database prevents a missed application check from exposing the data. That is defense in depth.

5. Treat Every Input and Output as Untrusted

The interface is not the security boundary.

Attackers can call Route Handlers directly, replay captured requests, alter JSON bodies, remove hidden fields, submit oversized payloads, change content types, and replace IDs that the interface never allowed them to edit.

Server-side validation must define what the application accepts. It should reject unknown fields, malformed identifiers, impossible state transitions, unexpected file types, and values outside business limits.

No.Security checkPassing evidence
21Every mutation validates its input on the server.Tests submit missing, malformed, oversized, unexpected, and additional fields directly to the server operation.
22Database queries and commands cannot be altered through injection.Queries use parameterized clients or safely constructed SDK methods; no untrusted input is interpolated into SQL or shell commands.
23User-controlled content is safely rendered and exported.Stored HTML, Markdown, rich text, filenames, CSV values, and URLs are tested for script and formula injection.
24File uploads enforce size, type, extension, storage path, and authorization controls.Tests include disguised file types, oversized files, path manipulation, unauthorized replacement, and private-object access.
25Request and response sizes are intentionally limited.Large bodies are rejected, expensive fields require explicit selection, and private columns are not serialized by default.

Validation schemas should be narrow. Avoid accepting an entire client object and passing it directly into a database update.

For example:

import { z } from "zod";

const UpdateProfileSchema = z
  .object({
    displayName: z.string().trim().min(2).max(80),
    bio: z.string().trim().max(500).optional(),
  })
  .strict();

export async function parseProfileUpdate(input: unknown) {
  return UpdateProfileSchema.parse(input);
}

The .strict() behavior matters because it prevents the client from quietly adding fields such as role, isAdmin, billingStatus, or ownerId to a payload that was intended only to update a profile.

Next.js also provides a Server Action request-body limit and same-origin protections. These defaults are useful, but they do not replace endpoint-specific authorization, validation, rate limiting, or cost controls.

6. Protect Billing, Webhooks, Cron Jobs, and Background Work

Secure webhook processing flow with signature verification, idempotency, retries, job queues, and entitlement updates

Billing systems are especially vulnerable to implementations that are functionally convincing but operationally incomplete.

A checkout redirect proves only that a browser reached a payment page. It does not prove that the application can safely handle delayed events, duplicate deliveries, refunds, disputes, subscription changes, provider outages, or events arriving in an unexpected order.

Product access should come from verified billing state projected into your own system—not from a success-page query parameter or untrusted client response.

No.Security checkPassing evidence
26Every external webhook is cryptographically verified before parsing or processing trusted fields.Tests reject missing, invalid, expired, or altered signatures while preserving the required raw request body.
27Webhook processing is idempotent.Replaying the same provider event multiple times produces one logical state transition and no duplicated side effect.
28Entitlements are derived from verified internal billing state.Editing browser storage, redirect parameters, product metadata, or client requests cannot grant paid access.
29Retries are bounded and observable.Jobs use backoff, attempt limits, concurrency controls, terminal states, and an operator-visible replay path.
30Cron and internal job endpoints fail closed.Missing or invalid secrets are rejected, methods are restricted, and the endpoint cannot be invoked anonymously.

Stripe recommends verifying every webhook with the raw event payload, the Stripe-Signature header, and the endpoint’s secret. Review Stripe’s webhook security documentation.

A simplified Next.js verification boundary looks like this:

import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function POST(request: Request) {
  const signature = request.headers.get("stripe-signature");

  if (!signature) {
    return new Response("Missing signature", { status: 400 });
  }

  const rawBody = await request.text();

  let event: Stripe.Event;

  try {
    event = stripe.webhooks.constructEvent(
      rawBody,
      signature,
      process.env.STRIPE_WEBHOOK_SECRET!,
    );
  } catch {
    return new Response("Invalid signature", { status: 400 });
  }

  // Reserve event.id in a table with a unique constraint.
  // Process the state transition transactionally.
  // A duplicate event must become a safe no-op.

  return new Response("Accepted", { status: 200 });
}

Signature verification proves the event came from the expected provider. It does not provide idempotency. Store the provider event ID under a unique constraint and design every downstream side effect to survive retries.

The Shipflash SaaS billing architecture guide goes deeper into entitlements, provider events, reconciliation, retries, refunds, and reliable access control.

7. Limit Abuse and Secure the Software Supply Chain

An application can enforce correct permissions and still be financially or operationally unsafe.

Attackers may automate signup, password reset, contact forms, email sending, file conversion, AI generation, search, exports, or checkout creation. A technically valid request can still be abusive when repeated at scale.

Rate limits should therefore reflect business cost and risk, not only raw request volume.

No.Security checkPassing evidence
31Expensive and abuse-prone operations have layered limits.Limits exist by IP, user, account, endpoint, plan, and cost unit where each dimension is relevant.
32Bot challenges are validated on the server.Forged, expired, reused, wrong-hostname, and wrong-action challenge tokens are rejected.
33Dependencies and lockfiles are reviewed for known vulnerabilities.Dependabot or an equivalent process has no unexplained critical or high-severity production alert.
34Static analysis and security linting run in CI.Pull requests execute code scanning, type checks, linting, secret detection, and relevant security tests.
35Production deployment configuration has been hardened.HTTPS, security headers, environment separation, database migrations, preview access, and debug behavior are verified in production.

Cloudflare states that Turnstile’s client widget is not sufficient by itself: the token must be sent to the Siteverify API for server-side validation. Tokens are short-lived and single-use, so replayed or expired submissions should be rejected. Read Cloudflare’s server-side Turnstile validation guide.

GitHub’s Dependabot alerts can identify dependencies with known vulnerabilities, while code scanning can identify security weaknesses and coding errors in repository code. These tools support review; they do not prove that product-specific authorization and business logic are correct.

For a deeper runtime-control strategy, see Rate Limiting, Bot Protection, and Cost Controls for Next.js.

The critical distinction is between traffic limits and business limits. Ten requests per minute may be harmless for a profile endpoint but financially dangerous for an endpoint that invokes an expensive AI model. A production SaaS may need to limit requests, generated tokens, emails, storage bytes, export rows, or provider spend separately.

8. Prove You Can Detect, Contain, and Recover From Failure

Prevention is only one part of security.

A secure production system must also reveal suspicious behavior, preserve enough context to investigate it, contain the blast radius, and recover without inventing procedures during an incident.

Logs should help reconstruct who did what, to which resource, through which request, and with what result. They should not contain passwords, session tokens, API keys, complete webhook secrets, or unnecessary personal data.

No.Security checkPassing evidence
36Security-relevant actions produce structured, correlated logs.Authentication failures, permission denials, admin actions, billing transitions, webhook results, and job failures include request or event IDs.
37Material security and operational failures trigger actionable alerts.Test events prove alerts reach an owned channel with enough context to begin investigation.
38Backups have been restored in a test environment.A recorded restore test confirms data integrity, required credentials, duration, and recovery steps.
39Credential compromise and account takeover have documented containment steps.The runbook covers revocation, rotation, session invalidation, provider contact, evidence preservation, and customer communication.
40The release has a signed security decision with attached evidence.A named owner confirms all blocking checks passed or records explicit exceptions with review dates.

Supabase’s production guidance recommends reviewing Security Advisor findings, enabling appropriate database protections, protecting administrative accounts with MFA, and planning for availability and recovery. Its shared-responsibility model also makes clear that application architecture, access policies, and secure product configuration remain the builder’s responsibility.

Backups should not be considered verified because a provider dashboard says they exist. A usable backup requires the right project access, encryption keys, schema compatibility, restoration instructions, and enough time to perform the recovery.

The same principle applies to alerts. An alert that reaches an abandoned inbox is configuration, not incident readiness.

Which Failures Should Block the Release?

Not every incomplete improvement carries the same risk. A missing convenience alert is different from disabled RLS on a customer table.

Use the following release hierarchy:

DecisionExamplesRequired action
Block releaseExposed privileged secret, missing object authorization, disabled RLS, unsigned webhook, public admin action, unprotected cron endpoint, unrecoverable backupFix and rerun the relevant checks before production traffic is allowed.
Require explicit exceptionNon-critical dependency issue with proven unreachable code, delayed low-risk alert, incomplete hardening for an unused featureRecord owner, reasoning, compensating control, and expiry date.
Schedule improvementStronger CSP, expanded security tests, improved dashboards, more automated evidence collectionCreate an owned task without misrepresenting it as a blocking control.

Do not accept an exception merely because the fix would delay launch.

An exception is defensible only when the team understands the affected asset, realistic exploit path, possible customer impact, compensating control, and deadline for reevaluation.

A Practical Evidence Package for Launch

A completed spreadsheet with forty “yes” cells is not enough.

The launch record should contain evidence that can be reviewed after the fact:

Evidence categoryUseful artifact
Repository securityPull request links, secret scan result, dependency report, code scan result
Authentication and authorizationNegative API tests, role tests, session configuration capture
Database protectionRLS policy export, grant review, Security Advisor result, pgTAP or integration test output
Billing and webhooksSignature failure test, replay test, duplicate-event record, entitlement transition tests
Abuse controlsRate-limit tests, Turnstile failure tests, quota and spend-limit configuration
OperationsAlert test, backup restoration record, incident runbook, rollback procedure
Release decisionNamed approver, unresolved exceptions, timestamps, production commit and migration identifiers

This evidence turns security from a feeling into a repeatable process.

It also makes future AI-assisted changes safer. An agent can be instructed to preserve existing security tests, update the evidence affected by its change, and refuse to mark the work complete when a release gate fails.

Is Vibe Coding Secure Enough for Production?

It can be—but not because the application was generated by a more capable model, used a longer prompt, or passed a visual review.

A vibe-coded application becomes production-ready when its owners understand the system, define its trust boundaries, verify its security controls, test failures and bypasses, and maintain those protections as the product changes.

AI can help write policies, generate tests, identify suspicious patterns, compare code against security requirements, and investigate failures. It should not be the sole authority deciding whether its own implementation is safe.

Is a Supabase Anonymous Key a Secret?

A Supabase anonymous or publishable key is intended to be used in public clients. It should not be treated as the control that protects private data.

The actual protection comes from Postgres roles, table grants, RLS policies, storage policies, and application-level authorization.

The service-role key is privileged and must remain server-side because it can bypass RLS. Exposing it should be treated as a credential incident.

Does RLS Replace Server-Side Authorization?

No.

RLS protects database access even when an application query is incorrect or manipulated. Server-side authorization gives the application an intentional, understandable decision before it attempts the data operation.

Using both provides stronger protection than either layer alone.

Is Passing All 40 Checks a Guarantee That the SaaS Is Secure?

No checklist can guarantee that an application has no vulnerabilities.

This checklist is a release gate for recurring, high-impact risks in AI-built SaaS products. Products handling sensitive health, financial, identity, enterprise, or regulated data may require additional threat modeling, penetration testing, compliance controls, specialist review, and incident-response preparation.

Its purpose is not to declare perfect security. Its purpose is to stop preventable failures from reaching customers simply because the happy path worked.

Secure Vibe Coding Means Moving From Generation to Verification

Vibe coding can reduce the time required to create a product. It does not reduce the responsibility that begins when the product holds real identities, data, money, and access.

The most important shift is procedural:

A generated feature is a proposal. A passing interface is a demonstration. A tested security boundary is evidence.

Before launch, verify the application from the attacker’s position. Call actions without the interface. Replace resource IDs. replay webhooks. remove cookies. modify roles. exceed limits. upload hostile files. restore a backup. revoke a credential. confirm that alerts reach someone who can respond.

That is how an AI-built prototype becomes software people can reasonably trust.

For a broader launch review beyond security, continue with the Next.js SaaS Production Checklist. A production-oriented foundation such as Shipflash can provide established boundaries for authentication, billing, administration, communications, and operations, but every product must still verify the custom logic built on top of them.

Looking for more?

Explore our full collection of articles, tutorials, and industry updates.