Book a call

The production checklist for AI-built apps: 73 checks with code

A full production readiness checklist for vibe-coded and AI-built apps: secrets, auth, RLS, webhooks, LLM risks, backups, monitoring. With code.

An app that works on your laptop is not a production system. AI coding tools are good at the happy path and weak at everything around it: access control, secrets, failure handling, backups and monitoring. This guide is a reference checklist of 73 checks in 13 areas. Each area explains why it matters, gives the checks, and shows code where code helps. Every check maps to a public standard (OWASP, the Twelve-Factor App, Google SRE, web.dev), so you can verify it yourself instead of trusting us.

45%

Share of 80 coding tasks where AI-written code introduced an OWASP Top 10 vulnerability (Veracode benchmark).

A01

Broken Access Control, still the number one risk in the OWASP Top 10:2025.

2.5s

Largest Contentful Paint a page should hit to count as good on web.dev.

Source: Veracode: 2025 GenAI Code Security Report. This is a vendor benchmark of 80 prompt-style tasks scanned with Veracode's own tool. It shows how often models write vulnerable code in those tasks, not how many production apps are vulnerable.

What does production-ready mean for an AI-built app?

Production-ready means the app stays safe, correct and recoverable when real users, real money and real attackers arrive. It is a different bar from "it runs". Use the checklist below in three passes:

  • Blocker: do not launch until it is done. These are marked [B].
  • Week one: fix within seven days of launch, with a dated owner. Marked [W1].
  • Hardening: schedule it. Marked [H].

If you only have an hour, do every [B] item. That is the launch gate at the end of this post.

Why do AI-built apps fail in production?

The same failure modes keep appearing, and each one maps to a known category.

What breaksReal exampleOWASP Top 10:2025 category
Any logged-in or anonymous user can read other people's rowsCVE-2025-48757: weak Row Level Security in Lovable-generated apps let unauthenticated users read or write tables using the public keyA01 Broken Access Control
Secrets in code, open admin panels, debug routes left onKeys committed to Git, test endpoints shippedA02 Security Misconfiguration
Unchecked packages and build stepsAI suggests a library you never verifiedA03 Software Supply Chain Failures
Weak or home-made loginPasswords stored with a fast hash or in plain textA04 Cryptographic Failures, A07 Authentication Failures
User input reaches the database or the page unchangedSQL injection, cross-site scriptingA05 Injection
An agent with write access to the live databaseAn AI agent deleted a production database during a code freeze in 2025A06 Insecure Design
Nobody notices an attack or an outageNo logs, no alertsA09 Security Logging and Alerting Failures
Errors crash the app or leak stack tracesUnhandled exceptionsA10 Mishandling of Exceptional Conditions

Sources: NVD-based summary of CVE-2025-48757 at SentinelOne; Superblocks: Lovable RLS breach, 170+ apps, a vendor write-up; OECD AI Incident: Replit deletes production database. The Replit details come from screenshots shared by the user involved and press reports. The founder of Replit called the event unacceptable and announced separate dev and production databases.

The full category list is on the OWASP Top 10:2025 page. The 13 areas below cover all ten.

Area 1: Source code and dependencies

AI tools install packages freely and leave scaffolding behind. Treat every dependency as code you are shipping.

  • A1 [B] The project is in Git, with a clean history and a protected main branch.
  • A2 [B] You commit before every large AI change, so you can roll back to a known good version.
  • A3 [W1] A lockfile is committed (package-lock.json, pnpm-lock.yaml or uv.lock), and CI installs with npm ci, not npm install.
  • A4 [W1] npm audit (or your language's equivalent) runs in CI, and automated dependency updates such as Dependabot are on.
  • A5 [B] You checked that every package the AI suggested exists, has a real publisher and a believable download history. AI tools sometimes name packages that do not exist, and attackers can register those names.
  • A6 [B] Demo routes, /api/test, seed users, "magic" login links and debug flags are removed.
# Run before every release
npm ci
npm audit --omit=dev
npx tsc --noEmit
npm run build

Area 2: Secrets and configuration

The Twelve-Factor App config rule gives a simple test: could you open-source your repository today without leaking a credential? If not, config is still mixed into code.

  • B1 [B] No API key, token or password appears in source files. Search the repo, including old commits.
  • B2 [B] .env* files are listed in .gitignore.
  • B3 [B] In Next.js only values meant to be public carry the NEXT_PUBLIC_ prefix. Anything with that prefix is bundled into browser code and readable by every visitor. Next.js lists this in its production checklist.
  • B4 [B] Development, staging and production use different keys. A leaked dev key must not open production.
  • B5 [W1] GitHub push protection or another secret scanner blocks commits that contain keys. See GitHub: about push protection.
  • B6 [W1] The app validates its environment variables at startup and refuses to boot if one is missing.
// src/env.ts
import { z } from 'zod';

const schema = z.object({
  DATABASE_URL: z.string().url(),
  STRIPE_SECRET_KEY: z.string().startsWith('sk_'),
  STRIPE_WEBHOOK_SECRET: z.string().startsWith('whsec_'),
  NEXT_PUBLIC_SITE_URL: z.string().url(),
});

export const env = schema.parse(process.env); // throws at boot, not at 2 a.m.

What if a secret was already committed?

Deleting the line does not remove it from Git history. Follow this order.

  1. 01

    Revoke or rotate the key first

    Create a new key at the provider and disable the old one. Once revoked, the leaked value cannot be used.

  2. 02

    Check the provider's logs

    Look for use of the old key from addresses you do not recognise, and for unexpected charges.

  3. 03

    Purge it from history if needed

    GitHub recommends git-filter-repo with the sensitive-data-removal flag, then a force push. Forks keep the old history, so check them too.

  4. 04

    Add a scanner so it cannot recur

    Turn on push protection and a pre-commit secret check.

Source: GitHub Docs: Removing sensitive data from a repository.

Area 3: Authentication

Do not let an AI invent a login system. Authentication is a solved problem, and the safest code is code you did not write.

  • C1 [B] Sign-in uses a maintained provider or library (for example Auth.js, Clerk, Supabase Auth or your framework's official option).
  • C2 [B] If you must store passwords yourself, use Argon2id. OWASP's minimum is 19 MiB of memory, 2 iterations and 1 degree of parallelism. Use bcrypt with a work factor of at least 10 only on legacy systems.
  • C3 [B] Session cookies are set HttpOnly, Secure and SameSite=Lax or Strict.
  • C4 [W1] Login, sign-up and password reset are rate limited per IP and per account.
  • C5 [W1] Password reset and email verification links expire and can be used once.
  • C6 [H] Admin accounts require multi-factor authentication.

Source: OWASP Password Storage Cheat Sheet.

Area 4: Authorization and data access

This is the area that matters most, because Broken Access Control is OWASP's number one risk. Authentication asks who you are. Authorization asks what you are allowed to touch. AI-generated code often gets the first right and skips the second.

  • D1 [B] Every database read and write is scoped to the signed-in user, on the server.
  • D2 [B] If you use Supabase or another database exposed to the browser, Row Level Security is enabled on every table in an exposed schema, and each table has a policy.
  • D3 [B] The service-role or secret key never reaches the browser. It bypasses Row Level Security by design.
  • D4 [B] Each server action and API route checks authentication and permission itself. Next.js says not to rely on layout or page-level checks alone.
  • D5 [B] Admin routes check a role on the server, not a hidden button in the interface.
  • D6 [B] You tested with two accounts that user A cannot open user B's records by changing an ID in the URL or request body. This bug is called an insecure direct object reference (IDOR).
-- Supabase / Postgres: owner-only access
alter table public.reports enable row level security;

create policy "Owners can read their reports"
on public.reports for select
to authenticated
using ( (select auth.uid()) = user_id );

Source: Supabase Docs: Row Level Security. A table with RLS on and no policy returns nothing, which is the safe failure. A table with RLS off in an exposed schema can be read and written by any role with a grant.

// Next.js server action: check inside the action, not only in the page
'use server';
import { z } from 'zod';

const Input = z.object({ reportId: z.string().uuid() });

export async function deleteReport(raw: unknown) {
  const { reportId } = Input.parse(raw);
  const session = await getSession();               // your auth helper
  if (!session) throw new Error('Unauthorized');

  // Scope the query to the owner, so a guessed ID deletes nothing
  const result = await db.report.deleteMany({
    where: { id: reportId, userId: session.userId },
  });
  if (result.count === 0) throw new Error('Not found');
}

Area 5: Input validation and injection

Everything that comes from outside your server is untrusted: form fields, query strings, headers, uploaded files and webhook bodies.

  • E1 [B] Every endpoint validates its input on the server with a schema (Zod, Valibot, Pydantic). Client-side checks are for user experience only.
  • E2 [B] Database access uses parameterized queries or an ORM. OWASP lists prepared statements first among its SQL injection defenses and strongly discourages manual escaping.
  • E3 [B] The app never builds HTML from user text. Avoid dangerouslySetInnerHTML, or sanitize the input first.
  • E4 [W1] Uploads have a size limit, an allowed-type list, a random file name and storage outside the web root.
  • E5 [W1] Any URL the server fetches for a user (image proxy, link preview) is checked against an allow-list, to block server-side request forgery.
// Unsafe: string building
const rows = await db.$queryRawUnsafe(`SELECT * FROM users WHERE email = '${email}'`);

// Safe: parameterized
const rows = await db.$queryRaw`SELECT * FROM users WHERE email = ${email}`;

Source: OWASP SQL Injection Prevention Cheat Sheet.

Area 6: Browser and transport hardening

  • F1 [B] The whole site is served over HTTPS and HTTP redirects to HTTPS.
  • F2 [W1] Security headers are set. OWASP's recommended values are in the snippet below.
  • F3 [H] A Content Security Policy limits where scripts can load from. Start in report-only mode so it does not break the site.
  • F4 [B] CORS allows only your own origins, never * together with credentials.
  • F5 [W1] State-changing requests are protected against cross-site request forgery. Next.js server actions and SameSite cookies cover most cases. Check any custom route.
// next.config.js
const securityHeaders = [
  { key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
  { key: 'X-Content-Type-Options', value: 'nosniff' },
  { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
  { key: 'Permissions-Policy', value: 'geolocation=(), camera=(), microphone=()' },
  { key: 'Content-Security-Policy', value: "frame-ancestors 'none'" },
];

module.exports = {
  async headers() {
    return [{ source: '/(.*)', headers: securityHeaders }];
  },
};

Source: OWASP HTTP Headers Cheat Sheet. The preload flag in HSTS is a long commitment, so add it only when every subdomain is on HTTPS.

Area 7: Payments and webhooks

Money flows are where a small bug becomes a real loss. These rules apply to Stripe and most payment providers.

  • G1 [B] The price comes from your server or the provider, never from a value the browser sends.
  • G2 [B] Webhook endpoints verify the signature using the raw request body. Parsing the body first breaks verification.
  • G3 [B] Live and test keys are separate, and the live site uses live keys only.
  • G4 [B] Webhooks are idempotent: processing the same event twice does not ship two orders. Stripe says duplicates can occur and tells you to log processed event IDs.
  • G5 [W1] The handler returns a 2xx status quickly and does slow work in a queue, because delivery timeouts trigger retries.
  • G6 [W1] You do not depend on event order. Stripe does not guarantee it, so fetch the current object when in doubt.
// app/api/stripe/webhook/route.ts
import Stripe from 'stripe';

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

export async function POST(req: Request) {
  const body = await req.text();                       // raw body, do not JSON.parse first
  const signature = req.headers.get('stripe-signature');
  if (!signature) return new Response('Missing signature', { status: 400 });

  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(body, signature, process.env.STRIPE_WEBHOOK_SECRET!);
  } catch {
    return new Response('Invalid signature', { status: 400 });
  }

  // Idempotency: the primary key rejects a second insert of the same event
  const first = await recordEventOnce(event.id);       // see SQL below
  if (!first) return new Response('Already processed', { status: 200 });

  await queue.enqueue(event);                          // slow work happens elsewhere
  return new Response('ok', { status: 200 });
}
create table processed_events (
  id text primary key,
  received_at timestamptz not null default now()
);
-- insert ... on conflict (id) do nothing; if 0 rows inserted, it was a duplicate

Source: Stripe Docs: Receive Stripe events in your webhook endpoint.

Area 8: AI features inside your app

If your product calls a language model, you have a second attack surface. The OWASP Top 10 for LLM Applications 2025 names the risks. These five come up most in small products.

  • H1 [B] Treat all model input as hostile (LLM01 Prompt Injection). A web page, email or PDF the model reads can carry instructions. Never let retrieved text change what the app is allowed to do.
  • H2 [B] Give the model the least power possible (LLM06 Excessive Agency). Read-only tools by default. Anything that sends, pays, deletes or posts needs a human approval step. Our guide to AI action guardrails shows how.
  • H3 [B] Treat model output as untrusted (LLM05 Improper Output Handling). Validate it against a schema before it reaches a database, a shell, a URL or a web page.
  • H4 [B] Spend is capped (LLM10 Unbounded Consumption). Set a hard monthly limit at the model provider, and a per-user rate limit and token cap in your app. One abusive user or a loop bug should not produce a surprise bill.
  • H5 [W1] No secrets in the system prompt (LLM07 System Prompt Leakage). Assume users can read it. Keep keys and business rules on the server.
  • H6 [W1] You know what personal data you send to the model vendor, and your privacy policy says so (LLM02 Sensitive Information Disclosure).
// Validate model output before acting on it
const Action = z.object({
  type: z.enum(['draft_reply', 'tag_lead']),     // no "send_email", no "delete"
  text: z.string().max(2000),
});

const parsed = Action.safeParse(JSON.parse(modelOutput));
if (!parsed.success) return fallbackToHuman();

Area 9: Data, environments and backups

  • I1 [B] Development, staging and production are separate environments with separate databases. Any AI coding agent works on development only.
  • I2 [B] Agents and automation tools hold the smallest permissions they need. No agent gets a credential that can drop production tables.
  • I3 [B] Automatic backups are on, and you have restored one into a test database. A backup you never restored is a hope.
  • I4 [W1] Schema changes are versioned migrations in Git, not manual edits in a dashboard. Each migration has a rollback plan.
  • I5 [W1] You know your recovery goals: how much data you can afford to lose (recovery point) and how long you can be down (recovery time). Write them down, even as rough numbers.
  • I6 [H] You have a deletion and retention plan for personal data, so a user's "delete my account" request has a real procedure.

Area 10: Testing and continuous integration

AI-written code usually passes the happy path and fails at the edges. Test the edges on purpose.

  • J1 [B] You tried the unhappy paths by hand: wrong password, empty form, expired session, duplicate submit, a payment that fails, a slow network.
  • J2 [W1] CI runs type checks, lint, unit tests and a production build on every pull request, and a failing job blocks the merge.
  • J3 [W1] One automated end-to-end test covers your money or sign-up flow from start to finish.
  • J4 [H] A simple load test shows the app survives ten times your expected day-one traffic.
# .github/workflows/ci.yml (minimal)
name: ci
on: [pull_request]
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22, cache: npm }
      - run: npm ci
      - run: npx tsc --noEmit
      - run: npm run lint
      - run: npm test --if-present
      - run: npm run build

Area 11: Observability and incident response

You cannot fix what you cannot see. The Google SRE book defines four golden signals to monitor for any user-facing system.

SignalWhat it measuresA simple starting alert
LatencyTime to serve a request, tracking failed requests separatelySlow responses above your target for 5 minutes
TrafficDemand on the systemA sudden drop to near zero, or a sharp spike
ErrorsRate of failed requests, explicit or implicitError rate above a small threshold
SaturationHow close the most constrained resource is to its limitDatabase connections or memory near the cap
  • K1 [B] An error tracker such as Sentry captures server and browser exceptions with the release version.
  • K2 [B] An uptime check hits a health endpoint every minute and alerts a person by email, SMS or chat.
  • K3 [W1] Alerts go to a human who will act, not to an unread inbox. Name who.
  • K4 [W1] Logs are structured, include a request ID, and never contain passwords, tokens or full card data.
  • K5 [W1] You monitor business signals too: sign-ups, payments and emails sent. An app can return 200 OK and still be wrong. We explain this trap in agents that return 200 OK and are still wrong.
  • K6 [H] A short runbook says what to do for the three most likely failures: site down, payments failing, database full.

Area 12: Performance, errors and discoverability

  • L1 [W1] Core Web Vitals meet the "good" thresholds at the 75th percentile: Largest Contentful Paint at or under 2.5 seconds, Interaction to Next Paint at or under 200 milliseconds, Cumulative Layout Shift at or under 0.1. See web.dev: Web Vitals.
  • L2 [W1] Run next build and next start locally and test Lighthouse in an incognito window before release, as the Next.js checklist suggests. Lighthouse is a lab test, so confirm with field data later.
  • L3 [B] Custom 404 and error pages exist, so a crash shows a calm message and never a stack trace.
  • L4 [H] Images use the framework image component, fonts are self-hosted, and third-party scripts are deferred.
  • L5 [W1] robots.txt, a sitemap, page titles, descriptions and share images are set, so search engines and link previews work from day one.

Area 13: Launch day, rollback and legal basics

  • M1 [B] You know how to roll back. On Vercel and similar hosts that means redeploying the previous build. Test it once before launch, with a stopwatch.
  • M2 [W1] Risky features sit behind a flag, so you can switch them off without a deploy.
  • M3 [B] A privacy policy and terms of service are live and match what the app really collects. Cookie consent is added where your audience's law requires it. Have a qualified professional review them if you handle sensitive data.
  • M4 [B] Domain, DNS and TLS certificate renewals are automatic, and the renewal email goes to a monitored address.
  • M5 [W1] A status or contact page tells users where to report a problem.
  • M6 [W1] You launch to a small group first, watch the dashboards for a day, then open the doors.

Time how long it takes to restore the previous version. If it takes more than five minutes, fix the process before launch day.

5 minROLLBACK DRILL

The launch gate: what must be green before you go live

Print this table. Do not launch with any blocker open.

GateChecks that must passWhy this one
Code is recoverableA1, A2, A5, A6You can undo mistakes and nothing risky shipped by accident
Secrets are privateB1, B2, B3, B4One leaked key can empty an account
Login is trustedC1, C2, C3Never ship home-made authentication
Data is isolatedD1, D2, D3, D4, D5, D6Broken access control is the top OWASP risk
Input is safeE1, E2, E3Injection is preventable with standard patterns
Transport is secureF1, F4Basic hygiene for every site
Money is correctG1, G2, G3, G4Payments need integrity and idempotency
AI is boundedH1, H2, H3, H4Limits agency and spend
Data is recoverableI1, I2, I3A tested backup is your last defense
Failures are visibleJ1, K1, K2, L3You find out before your users do
You can retreatM1, M3, M4Rollback, legal basics and renewals

If you are short on time, the order of risk is: access control (Area 4), secrets (Area 2), backups (Area 9), payments (Area 7), then everything else.

What should you do if you cannot tick the blockers?

Do not launch until they are done, or launch to a private group with no real data. For the rest, write a dated list with an owner and fix it in the first week. If you want an expert to run this checklist against your app, our AI audit is a fixed-price option, and you can book a call to talk through your project. If you want to build the workflow yourself, see our Claude Code beginner guide, compare tools in Claude Code vs Cursor vs Codex, or join the Autopilot Studio course.

References and further reading

Quick answers

Frequently Asked Questions

What is a production readiness checklist for an AI-built app?

It is a list of checks that cover security, data safety, testing, monitoring and rollback, run before real users arrive. This one has 73 checks in 13 areas, each marked as a launch blocker, a week-one fix or a hardening task.

Is vibe coding safe for a real business app?

It can be, if you add the checks that AI tools tend to skip: protected secrets, tested login and access rules, backups and error alerts. In one vendor benchmark, AI-written code introduced an OWASP Top 10 vulnerability in 45% of the 80 tasks tested, so review is not optional.

Which checks matter most before launch?

Access control, secrets, backups and payment handling come first. Broken access control has been the top OWASP risk, and a leaked key or an untested backup causes the most expensive failures.

Madhav Agarwal
Written byMadhav AgarwalCo-Founder, Autopilot Studio

Keep reading

Related guides

Want this built for you?

Autopilot Studio designs and builds custom software and AI automation systems. See what we build, what it costs, learn it yourself in our course, or talk to us directly.

Book a call