FixVibe

// docs / security guides / pre-ship checklist

The vibe coding security checklist: 51 items before you ship

A practical, phase-organised checklist for apps built with Cursor, Claude Code, Lovable, Bolt, v0, Replit, and Windsurf. Each item is actionable in under five minutes. Run through it before you push to production, then again before each major release. Items are grouped into seven categories β€” secrets, database, auth, headers, third-party, deployment, monitoring β€” and tagged with the deploy phase they apply to.

PRE = pre-deploy (audit your source). DEPLOY = at deploy time. POST = post-deploy verification.

Secrets and API keys (8 items)

Hardcoded keys are the single most common finding in vibe-coded apps. Eight items to keep them out:

  1. PRE β€” Audit NEXT_PUBLIC_ env vars. Anything prefixed NEXT_PUBLIC_ ships in client bundles. If one is a Supabase service_role key (decodes to JWT with "role":"service_role"), delete it and route through a server-only client (src/lib/supabase/service.ts with import 'server-only').
  2. PRE β€” Grep for hardcoded provider keys. Search source for sk_live_, pk_live_, STRIPE_SECRET, sk-ant-, sk-, AIza, AKIA, and JWT-looking strings (eyJ). Move every hit into .env.local and reference via process.env.* server-side only.
  3. PRE β€” Verify .gitignore. Confirm .env*.local, .npmrc, .yarnrc, and any provider-specific credentials files are ignored. Run git ls-files piped through your provider patterns to find anything already committed.
  4. PRE β€” Scan the built bundle. Run npm run build, then grep .next/static and any dist/ output for the same patterns. If a key reaches the bundle, the dev never had clean env separation.
  5. DEPLOY β€” Set secrets per environment. Vercel: Settings β†’ Environment Variables, scope each to Production / Preview / Development. Never share sk_live_* with the Preview env. Use Vercel's encrypted env-var storage, not inline workflow secrets.
  6. DEPLOY β€” Disable build-log secret echo. Some CI configs echo env vars during build. Audit your vercel.json, GitHub Actions workflows, or Cloudflare Pages settings for any echo $SECRET that would push the value into public build logs.
  7. POST β€” Run a passive scan. FixVibe's Free tier covers this: paste the deployed URL, wait ~20s, look for secrets.* findings. The Secrets in JavaScript Bundles check catches keys that landed in localStorage or sessionStorage via a misuse of the SDK.
  8. POST β€” Rotate any key that ever shipped. If a key was in a public bundle for even minutes, treat it as compromised. Rotate Supabase service-role keys via the dashboard, regenerate Stripe restricted keys, revoke Anthropic / OpenAI / Google keys via their consoles.

Database access control: RLS and Firestore rules (6 items)

BaaS defaults are permissive on purpose so the first tutorial works. Production needs explicit policies.

  1. PRE β€” Enable RLS on every public.* table. In Supabase: each table needs ALTER TABLE ... ENABLE ROW LEVEL SECURITY plus explicit policies for each role. RLS is what stops the anon and authenticated API roles; FORCE ROW LEVEL SECURITY only extends it to the table owner and is optional defence in depth.
  2. PRE β€” Write a policy per (table, role, action). Minimum: a SELECT policy that joins on auth.uid(). Better: separate INSERT / UPDATE / DELETE policies so an UPDATE can't smuggle in user_id changes that reroute ownership.
  3. PRE β€” Replace default Firebase rules. Default test-mode rules read allow read, write: if true;. Replace with auth-bound rules per collection: match /users/{userId} with allow read, write: if request.auth.uid == userId;
  4. PRE β€” Lint migrations in CI. Run supabase db lint or an equivalent before merging. CI should fail the build if any CREATE TABLE public.* lacks a matching RLS policy.
  5. DEPLOY β€” Confirm RLS survived deploy. Re-check in Supabase Studio after deploy: Tables β†’ each row β†’ RLS toggle is ON. Production database migrations occasionally race ahead of policy files; verify the policy is live.
  6. POST β€” Run an active scan against a verified domain. The Supabase Row-Level Security active check writes to a tiny seed row using the anon key and reports back if the write succeeded β€” i.e. RLS isn't actually enforcing.

Authentication and sessions (7 items)

Auth bugs in AI-coded apps tend to be subtle: an off-by-one in token verification, a missed HttpOnly flag, a getSession() where there should be a getUser().

  1. PRE β€” Replace getSession() with getUser(). getSession() reads the cookie and trusts it; getUser() verifies with the Supabase backend. On server routes always use getUser().
  2. PRE β€” Confirm token expiry. Magic-link, password-reset, and email-verification tokens need server-enforced expiry. Default Supabase magic-links expire after 1 hour β€” don't override that to a higher number without a real reason.
  3. PRE β€” Verify JWT aud and exp. If you decode tokens manually anywhere, check both claims. Better: use the SDK's getUser() which does it for you.
  4. PRE β€” Audit cookie flags. Custom session cookies should be Secure; HttpOnly; SameSite=Lax (or Strict for non-OAuth flows). No session material in localStorage.
  5. PRE β€” Validate the next redirect param. The next query param after sign-in must start with / and not // (open-redirect to attacker.example). Reject anything else server-side.
  6. POST β€” Test logout. Sign in, sign out, inspect cookies (DevTools β†’ Application β†’ Cookies). The session cookie must be cleared on the same response. If it persists, the logout handler isn't actually destroying server-side state.
  7. POST β€” Active probe. The Auth Flow Defects and Account Enumeration checks surface broken auth boundaries β€” different responses on "user exists" vs "wrong password", missing rate-limit on login, unsigned reset tokens.

HTTP security headers and Content Security Policy (6 items)

Headers are the cheapest hardening in the entire pipeline and the most consistently skipped by codegen.

  1. PRE β€” Ship a real CSP. Minimum: script-src 'nonce-{NONCE}' 'strict-dynamic'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'. No 'unsafe-inline' in script-src. Next.js auto-applies the nonce when middleware sets the x-nonce request header.
  2. PRE β€” Add the legacy three. X-Content-Type-Options: nosniff, X-Frame-Options: DENY (or rely on CSP frame-ancestors alone), Strict-Transport-Security: max-age=31536000; includeSubDomains.
  3. PRE β€” Tighten Referrer-Policy. Default strict-origin-when-cross-origin is fine for most apps. Don't ship unsafe-url or no header at all.
  4. PRE β€” Replace Access-Control-Allow-Origin: *. Grep for it. Replace with an explicit origin allowlist. Anywhere it's * alongside credentials: include, the browser will refuse the request β€” but that's no defence against a misconfigured backend.
  5. DEPLOY β€” Verify headers post-deploy. Open DevTools β†’ Network β†’ click your root document β†’ Headers tab. CSP, HSTS, X-Frame-Options, X-Content-Type-Options should be present. CSP must not have 'unsafe-inline' in script-src.
  6. POST β€” Run HTTP Security Headers. The passive header check reports each missing header with deploy-platform fix guidance (Vercel vercel.json, Cloudflare Pages _headers, Netlify _headers, Next.js middleware).

Third-party integrations and APIs (5 items)

Every script you include is a CSP exemption and a potential supply-chain surface. Treat third parties as part of your trust boundary.

  1. PRE β€” Reverse-proxy analytics where possible. PostHog, Plausible, Umami all support proxying through your own domain (e.g. /api/posthog). This keeps connect-src on the same origin and survives ad-blockers.
  2. PRE β€” CSP-allowlist the rest. For Google Analytics, Stripe.js, Sentry, Intercom, GTM, etc., add each vendor's origins to the matching CSP directive (script-src for loaders, connect-src for telemetry, frame-src for iframes, img-src for pixels).
  3. PRE β€” Use Stripe Checkout, not raw card forms. Stripe Checkout is a top-level redirect; no CSP entry needed for the script. Hosted PCI surface lives entirely on Stripe's domain. Roll your own only if you have a strong reason.
  4. PRE β€” Lock package-lock.json in CI. Run npm ci (not npm install) in production builds. Audit dependencies with npm audit or Snyk before each release.
  5. POST β€” Watch Technology Fingerprinting. The passive tech-stack discovery surfaces library versions visible to a crawler. If you ship an EOL React, jQuery, or Bootstrap, FixVibe flags it and links to known CVEs.

Deployment hygiene and infrastructure (8 items)

How you deploy matters as much as what you deploy. AI-coded apps especially benefit from explicit deploy hardening.

  1. PRE β€” Disable x-powered-by. In next.config.js: poweredByHeader: false. Removes a free version-disclosure signal.
  2. PRE β€” Confirm middleware lives at src/middleware.ts. With the src/ directory layout, Next.js ignores a root-level middleware.ts. Misplaced middleware silently fails to set CSP / auth headers / rate limits.
  3. PRE β€” Sanity-check Vercel deployment protection. Production should be public; Preview should be password-protected or limited to org members. Vercel-Specific Exposure reports the surface.
  4. PRE β€” Block dotfile and config probes at the edge. Add a rewrite or a deny rule for /.env, /.git/*, /.aws/*, /.next/trace patterns. Vercel returns 403 for many of these by default; cross-check.
  5. DEPLOY β€” Separate environments. Production, Preview, Development. Each gets its own set of secrets. Live keys never reach Preview, Stripe test mode never reaches Production.
  6. DEPLOY β€” Enable Vercel Web Application Firewall. Pro and Enterprise plans include WAF with managed rules. Cloudflare Pages has Bot Fight Mode. Both reduce automated-scanner abuse and password-spray load.
  7. POST β€” Verify TLS configuration. SSL Labs or testssl.sh against your production domain. TLS 1.2 minimum, prefer TLS 1.3, no weak ciphers, HSTS preload eligible.
  8. POST β€” Confirm health-check endpoints are minimal. A /api/health should return 200 OK with no body. Don't echo environment, build hash, or deploy timestamp without auth.

Ongoing monitoring and re-scanning (4 items)

Security is not a one-shot pre-ship audit. Drift happens on every deploy.

  1. Verify your production domain in FixVibe. Dashboard β†’ Domains β†’ DNS TXT or HTTP file verification. Active scans require Hobby or above, scheduled scans require Pro or Unlimited, and live threat monitoring requires Unlimited.
  2. Schedule passive re-scans on Pro or Unlimited. Choose an available cadence for your verified domain. Scheduled runs share your scan allowance; enable completion notifications and signed webhooks as needed. A scheduled scan observes the app at run time, not continuously.
  3. Wire outbound webhooks. Account β†’ Webhooks β†’ add an HTTPS endpoint, subscribe to scan.completed + finding.created + scan.active_api.first_used. Route into Slack / Discord / PagerDuty.
  4. Enable live threat monitoring on Unlimited. Periodic certificate, DNS, bundle, and threat-intelligence checks can surface changes between scheduled scans. Coverage and alert timing depend on the signal and successful polling.

Replit Agent

If you build with Replit Agent, also check:

  1. Don't paste keys into the Agent prompt. Add them in Secrets and give the Agent the variable name instead, so the value never appears in generated code or chat history.
  2. Test CORS on the production domain. The preview pane and your deployed URL are different origins. Check your CORS allowlist against the domain users actually load.
  3. Replit Agent sometimes hardcodes test values. The Agent might generate code with API_URL = 'http://localhost:5000</code>. Before shipping, replace with <code>os.environ[API_URL'].

Firebase Studio

If you build with Firebase Studio, also check:

  1. Test-mode rules are temporary by intent, not by default. Test mode lets anyone read and overwrite data. If the generated rule carries a date cutoff, client access stops on that date; if not, the database stays open. Replace test rules before launch either way.
  2. Security rules protect data, not Hosting. Firestore and Storage rules don't govern what Firebase Hosting serves: everything in the public directory is public. Keep exports, backups and admin pages out of it.

Claude Code

If you build with Claude Code, also check:

  1. PRE β€” Keep personal Claude Code settings out of git. Commit .claude/settings.json for shared project settings, but keep .claude/settings.local.json out of the repo: Claude Code excludes it from git when it creates the file, so check it if you created it by hand. Session history lives in ~/.claude, outside the project (Claude Code docs).
  2. Bash operations are unverified. Claude Code executes bash commands directly. git commit -m "fix" is convenient, but if the working directory has a .env file and a glob includes it, it commits. Always review git diff --cached before Claude Code commits.

Next steps

Want the educational backdrop on why these items matter? Read AI-generated code security scanning. Want concrete code snippets for each hardening step? See How to secure an app built with AI coding tools.

// scan your app

Stop reading. Start finding the gaps in yours.

Drop in a URL β€” FixVibe runs every passive check from this guide plus the rest of its 230+ passive checks, usually in under a minute. Free, no install, no card.

  • Free tier β€” 3 scans / month, no card.
  • Passive scans against any URL β€” no domain verification needed.
  • Tuned for Cursor, Claude Code, Lovable, Bolt, v0, Replit.
  • Coding-agent prompts for code/config findings, plus operator steps for DNS/provider fixes.
Run a free scan β†’

no signup required

The vibe coding security checklist: 51 items before you ship Β· FixVibe