Security checklist

Vibe coding security checklist: what to check before you ship an AI-built app

AI app builders get you to a working app fast and leave the checks that keep it safe to you. Pick what your app is built on and get the ones that apply, each with how to verify it.

Updated . Every check links the vendor’s docs or OWASP.

Your stack

Pick what your app is built on. The list below updates as you go.

32 checks for an app built on Next.js, Supabase, Stripe, Vercel and OpenAI: 7 must-dos, 16 should-dos and 9 good-to-haves.

Every AI-built app

  • Keep secret keys out of the browser: not in client code, not in public env vars

    Must do

    Anything the browser downloads can be read, and AI builders often wire a secret key into client code to make a feature work. Only publishable keys belong in NEXT_PUBLIC_, VITE_ or EXPO_PUBLIC_ variables.

    Verify from the command line

    Build the app, then search the client output for secret key formats (Stripe, OpenAI, Anthropic, Supabase, AWS) and list your public variable names. A pass finds no keys, and no public variable is named like a secret.

    grep -rnE "sk_live_|rk_live_|sk-(proj-)?[A-Za-z0-9_-]{20,}|sk-ant-|sb_secret_|AKIA[0-9A-Z]{16}" .next/static dist build 2>/dev/null
    grep -rhoE "(NEXT_PUBLIC|VITE|EXPO_PUBLIC)_[A-Z0-9_]+" --exclude-dir=node_modules . | sort -u

    Read more on cheatsheetseries.owasp.org

  • Check authorization on the server for every record, not only in the UI

    Must do

    Generated apps often hide a button but leave the API open, so anyone can fetch or change another user’s data by changing an id.

    How to verify

    Sign in as user A, copy a request for one of A’s records from the browser’s network tab, swap in the id of user B’s record and send it again. You should get 403 or 404, never B’s data. Repeat for update and delete requests.

    Read more on api-security.owasp.org

  • Verify the signature on every webhook

    Must do

    Stripe, Clerk, GitHub and most others sign their webhooks. Without the check, anyone can post a fake “payment succeeded” or “user created” event to your endpoint.

    Verify from the command line

    Post to each webhook URL without a valid signature header; it must be rejected (400 or 401). For Stripe, stripe listen and stripe trigger send real signed events to test the happy path.

    curl -i -X POST https://<your-app>/api/webhooks/<provider> -H "Content-Type: application/json" -d '{"type":"test"}'

    Read more on docs.stripe.com

  • Rate-limit sign-in, sign-up, password reset and AI endpoints

    Should do

    Bots hammer login forms, and every call to an AI endpoint costs you money. One script can run up a month’s bill in an hour.

    Verify from the command line

    Send a burst of requests to your AI route and your sign-in route as one user or IP. After your limit you should get 429 responses.

    for i in $(seq 1 30); do curl -s -o /dev/null -w "%{http_code} " -X POST https://<your-app>/api/<route> -H "Content-Type: application/json" -d '{"message":"hi"}'; done

    Read more on api-security.owasp.org

  • Validate every input on the server with a schema

    Should do

    Client-side checks are skipped by anyone calling your API directly; unvalidated input leads to injection bugs, crashes and junk data.

    How to verify

    Send each API route a request with a missing field, a wrong type and a very long string. Each should get a 400 with a short message, never a 500 or a saved record.

    Read more on cheatsheetseries.owasp.org

  • Treat AI model output as untrusted input

    Should do

    Text a user or a web page feeds the model can override your instructions. Never let model output run tools, queries or HTML without checks.

    Check the code

    Model output is escaped before it’s rendered (no dangerouslySetInnerHTML on raw output), and tool calls that change data need an allow-list or a user’s confirmation.

    Read more on genai.owasp.org

  • Allow only your own origins in CORS

    Should do

    A wildcard origin with credentials lets any website call your API as your signed-in users.

    Verify with a request

    Send a request from an origin you don’t trust. A pass: no Access-Control-Allow-Origin for that origin, and never * together with credentials.

    curl -sI https://<your-app>/api/<route> -H "Origin: https://evil.example" | grep -i access-control

    Read more on cheatsheetseries.owasp.org

  • Send security headers: Content-Security-Policy, HSTS and frame protection

    Should do

    Cheap protection against cross-site scripting, clickjacking and downgrade attacks.

    Verify with a request

    The site’s responses should carry these headers.

    curl -sI https://<your-app> | grep -iE "content-security-policy|strict-transport-security|x-frame-options|x-content-type-options"

    Read more on cheatsheetseries.owasp.org

  • Check dependencies for known vulnerabilities before launch

    Should do

    AI tools install packages from their training data, which can be versions with published vulnerabilities or packages that were deprecated since.

    Verify from the command line

    Run the free check at stackprism.ai/check on your repo, or npm audit for production dependencies. Fix anything critical before launch.

    npm audit --omit=dev

    Read more on top10.owasp.org

  • Show generic error messages; keep stack traces out of responses

    Good to have

    Stack traces and database errors tell an attacker your framework, file paths and table names.

    How to verify

    Request a page that doesn’t exist and send an API route malformed JSON in production. You should see a plain error page or a short JSON error, with no stack trace or SQL.

    Read more on cheatsheetseries.owasp.org

  • Log errors and sign-ins without secrets or personal data

    Good to have

    You need logs to investigate an incident, but logs with tokens, passwords or card numbers become a leak of their own.

    How to verify

    Trigger a failed sign-in and a server error, then read the logs: the events are there, with no passwords, tokens, API keys or full request bodies.

    Read more on cheatsheetseries.owasp.org

  • Check auth in every Server Action and Route Handler, not only in middleware

    Must do

    Middleware can be skipped by some routes and matchers; each data access needs its own check.

    Verify from the command line

    List every Server Action and Route Handler, then check each one reads the session and checks the user may touch that specific record before reading or writing data.

    grep -rlE "['\"]use server['\"]" app src --include=*.ts --include=*.tsx
    find app src -name "route.ts" -o -name "route.js"

    Read more on nextjs.org

  • Add a Content Security Policy

    Should do

    Limits the damage of any XSS bug.

    Verify with a request

    The response should carry a Content-Security-Policy header (a nonce-based one if you use inline scripts).

    curl -sI https://<your-app> | grep -i content-security-policy

    Read more on nextjs.org

  • Keep data access in server-only modules

    Good to have

    Stops server code and secrets leaking into client components by accident.

    Check the code

    Modules that touch the database or secrets start with import 'server-only'; importing one from a client component then fails the build.

    import 'server-only';

    Read more on nextjs.org

  • Don’t render untrusted HTML with dangerouslySetInnerHTML

    Should do

    It skips React’s escaping, so user or AI-generated content can run scripts in your users’ browsers.

    Verify from the command line

    Every match should render trusted content or sanitize it first (for example with DOMPurify).

    grep -rn "dangerouslySetInnerHTML" --include=*.tsx --include=*.jsx --exclude-dir=node_modules .

    Read more on react.dev

  • Call the API from your server only; never ship the key to clients

    Must do

    A key in a browser or mobile bundle gets scraped and billed to you.

    Verify from the command line

    Build the app, then search the client output; a pass finds no key. No variable holding the key should start with NEXT_PUBLIC_ or VITE_.

    grep -rnE "sk-[A-Za-z0-9_-]{20,}" .next/static dist build 2>/dev/null

    Read more on help.openai.com

  • Set project budgets and per-user rate limits

    Should do

    One abusive user or a retry loop can burn your monthly budget in hours.

    How to verify

    In the OpenAI dashboard, set a budget with an alert in your project’s limits settings. Then call your own AI route quickly several times as one user; your app should answer 429 after its per-user limit.

    Read more on platform.openai.com

  • Retry 429s with exponential backoff

    Good to have

    Rate limits are normal under load; fail gracefully instead of erroring.

    Check the code

    The official SDKs retry 429s and server errors twice by default; check you haven’t set maxRetries to 0 and that your UI shows a friendly message when retries run out.

    const openai = new OpenAI({ maxRetries: 2, timeout: 60_000 });

    Read more on platform.openai.com

  • Send idempotency keys on create requests

    Should do

    A retried request after a timeout otherwise charges the customer twice.

    Check the code

    Every create call passes an idempotency key derived from your own record (order or cart id), so a retry can’t charge twice.

    await stripe.paymentIntents.create(params, { idempotencyKey: `order-${order.id}` });

    Read more on docs.stripe.com

  • Handle duplicate and out-of-order webhook events

    Should do

    Stripe delivers at least once; record processed event ids and don’t assume ordering.

    Verify from the command line

    Resend an event that was already processed. Your handler should recognise its id (stored in a processed-events table) and do nothing the second time.

    stripe events resend <evt_id>

    Read more on docs.stripe.com

  • Use restricted API keys per service

    Good to have

    Limits the blast radius if one key leaks.

    How to verify

    In the Stripe Dashboard under Developers → API keys, each service should use its own restricted key (rk_live_…) with only the permissions it needs, and the full secret key shouldn’t be deployed where it isn’t needed.

    Read more on docs.stripe.com

  • Enable Row Level Security on every table

    Must do

    Tables in an exposed schema without RLS can be read and written by anyone holding the public anon key.

    Verify with SQL

    Run this in the SQL editor. It lists tables in the public schema with Row Level Security off; a pass returns no rows. The Security Advisor (Advisors in the dashboard) flags the same thing.

    select tablename from pg_tables
    where schemaname = 'public' and not rowsecurity;

    Read more on supabase.com

  • Keep the service_role key server-side only

    Must do

    It bypasses RLS. Never ship it to the browser, a mobile app or a NEXT_PUBLIC_ variable.

    Verify from the command line

    Search your code for public environment variables that hold a service role or secret key; a pass prints nothing. The key should only be read in server code (API routes, server actions, edge functions).

    grep -rnE "(NEXT_PUBLIC|VITE|EXPO_PUBLIC)_[A-Z0-9_]*(SERVICE|SECRET)" --exclude-dir=node_modules .

    Read more on supabase.com

  • Turn on Point-in-Time Recovery and test a restore

    Should do

    Daily backups can lose up to a day of data; PITR restores to the second.

    How to verify

    Open Database → Backups in the dashboard. The Point in Time tab should show PITR switched on with a recovery window; then restore to a new project once to prove it works. PITR is an add-on on the Pro, Team and Enterprise plans.

    Read more on supabase.com

  • Connect serverless functions through the connection pooler

    Good to have

    Each serverless invocation opening its own connection exhausts Postgres quickly under load.

    Check the config

    The connection string your serverless functions use should point at the pooler: a host ending in pooler.supabase.com on port 6543 (transaction mode), not the direct database host on 5432.

    postgres://postgres.<project-ref>:<password>@<region>.pooler.supabase.com:6543/postgres

    Read more on supabase.com

  • Walk through the Supabase production checklist

    Good to have

    Covers SSL enforcement, network restrictions, MFA on the org and email rate limits.

    How to verify

    Work through each section of the checklist, then open Advisors → Security Advisor and Performance Advisor in the dashboard: a pass shows no errors.

    Read more on supabase.com

Authentication

  • Offer MFA, and require it for admin accounts

    Should do

    Stops most account takeovers from reused passwords.

    How to verify

    In your auth provider’s settings, MFA is available to users and required for admin accounts. Sign in as an admin from a new browser; it should ask for a second factor.

    Read more on cheatsheetseries.owasp.org

  • Set session expiry and revoke sessions on password change

    Should do

    Stolen sessions shouldn’t work forever.

    How to verify

    Sign in on two browsers, change the password in one, then refresh the other: it should be signed out. Sessions should also expire on their own.

    Read more on cheatsheetseries.owasp.org

  • Protect preview deployments

    Should do

    Preview URLs are public by default and often talk to real data.

    How to verify

    Project → Settings → Deployment Protection: Vercel Authentication (or Password Protection) is on for preview deployments. Open a preview URL in a private window; it should ask you to sign in.

    Read more on vercel.com

  • Scope environment variables per environment

    Should do

    Keep production secrets out of Preview and Development builds.

    Verify from the command line

    List the project’s variables; production secrets should be scoped to Production only, not Preview or Development.

    vercel env ls

    Read more on vercel.com

  • Set spend management limits

    Good to have

    A traffic spike or a loop in a function shouldn’t produce a surprise bill.

    How to verify

    Team Settings → Billing → Spend Management: an on-demand budget is set with notifications, and Pause Production Deployments is on if you want a hard stop. (Pro and Enterprise plans.)

    Read more on vercel.com

  • Review the Vercel Firewall and add rate limits to hot routes

    Good to have

    Blocks abusive traffic before it reaches your functions.

    How to verify

    Open the project’s Firewall tab: check the traffic view and that login, sign-up and AI routes have a rate limit rule.

    Read more on vercel.com

Then check what your agent installed

AI tools pick package versions from their training data, which can be releases with published vulnerabilities. The dependency check reads your repo against today’s advisories, end-of-life dates and deprecations.

Questions

What is vibe coding, and why does it need its own checklist?

Vibe coding is building an app by describing it to an AI tool and accepting what it writes. The tools are good at making features work and bad at the parts you can’t see in the browser: who may read which record, where secret keys end up, and what happens when someone calls your API directly. This list covers those parts.

Is this list enough to call my app secure?

No. It catches the common, serious gaps in AI-built apps and tells you how to check each one yourself. If you handle payments at scale, health data or anything regulated, have someone review the app as well.

How do I check my dependencies?

Paste your public GitHub repo into the free dependency check. It reads your manifests and lists vulnerable, end-of-life and deprecated packages against live data, with a fix task you can give your coding agent.

Where do these checks come from?

From each vendor’s own documentation and the OWASP cheat sheets; every item links its source. The checks for individual technologies are StackPrism’s production checklists, which build on the lists the editor’s Improve panel uses.

Vibe coding security checklist | StackPrism