// docs / security guides / pre-ship checklist
Vibe coding 安全檢查清單:上線前 44 項
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. Items reference FixVibe check IDs in category.check-id form where relevant.
Secrets and API keys (8 items)
Hardcoded keys are the single most common finding in vibe-coded apps. Eight items to keep them out:
- PRE — Audit
NEXT_PUBLIC_env vars. Anything prefixedNEXT_PUBLIC_ships in client bundles. If one is a Supabaseservice_rolekey (decodes to JWT with"role":"service_role"), delete it and route through a server-only client (src/lib/supabase/service.tswithimport 'server-only'). - 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.localand reference viaprocess.env.*server-side only. - PRE — Verify
.gitignore. Confirm.env*.local,.npmrc,.yarnrc, and any provider-specific credentials files are ignored. Rungit ls-filespiped through your provider patterns to find anything already committed. - PRE — Scan the built bundle. Run
npm run build, then grep.next/staticand anydist/output for the same patterns. If a key reaches the bundle, the dev never had clean env separation. - 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. - DEPLOY — Disable build-log secret echo. Some CI configs
echoenv vars during build. Audit yourvercel.json, GitHub Actions workflows, or Cloudflare Pages settings for anyecho $SECRETthat would push the value into public build logs. - POST — Run a passive scan. FixVibe's Free tier covers this: paste the deployed URL, wait ~20s, look for
secrets.*findings. The secrets.browser-storage check catches keys that landed inlocalStorageorsessionStoragevia a misuse of the SDK. - 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.
- PRE — Force RLS on every
public.*table. In Supabase: each table must haveALTER TABLE ... ENABLE ROW LEVEL SECURITYandFORCE ROW LEVEL SECURITY. Force matters: without it, Postgres bypasses RLS for table owners. - 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 inuser_idchanges that reroute ownership. - 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}withallow read, write: if request.auth.uid == userId; - PRE — Lint migrations in CI. Run
supabase db lintor an equivalent before merging. CI should fail the build if anyCREATE TABLE public.*lacks a matching RLS policy. - 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.
- POST — Run an active scan against a verified domain. The baas.supabase-rls 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().
- PRE — Replace
getSession()withgetUser().getSession()reads the cookie and trusts it;getUser()verifies with the Supabase backend. On server routes always usegetUser(). - 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.
- PRE — Verify JWT
audandexp. If you decode tokens manually anywhere, check both claims. Better: use the SDK'sgetUser()which does it for you. - PRE — Audit cookie flags. Custom session cookies should be
Secure; HttpOnly; SameSite=Lax(orStrictfor non-OAuth flows). No session material inlocalStorage. - PRE — Validate the
nextredirect param. Thenextquery param after sign-in must start with/and not//(open-redirect to attacker.example). Reject anything else server-side. - 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.
- POST — Active probe. The active.auth-flow and active.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.
- PRE — Ship a real CSP. Minimum:
script-src 'nonce-{NONCE}' 'strict-dynamic'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'. No'unsafe-inline'inscript-src. Next.js auto-applies the nonce when middleware sets thex-noncerequest header. - PRE — Add the legacy three.
X-Content-Type-Options: nosniff,X-Frame-Options: DENY(or rely on CSPframe-ancestorsalone),Strict-Transport-Security: max-age=31536000; includeSubDomains. - PRE — Tighten
Referrer-Policy. Defaultstrict-origin-when-cross-originis fine for most apps. Don't shipunsafe-urlor no header at all. - PRE — Replace
Access-Control-Allow-Origin: *. Grep for it. Replace with an explicit origin allowlist. Anywhere it's*alongsidecredentials: include, the browser will refuse the request — but that's no defence against a misconfigured backend. - 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'inscript-src. - POST — Run headers.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.
- PRE — Reverse-proxy analytics where possible. PostHog, Plausible, Umami all support proxying through your own domain (e.g.
/api/posthog). This keepsconnect-srcon the same origin and survives ad-blockers. - 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-srcfor loaders,connect-srcfor telemetry,frame-srcfor iframes,img-srcfor pixels). - 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.
- PRE — Lock
package-lock.jsonin CI. Runnpm ci(notnpm install) in production builds. Audit dependencies withnpm auditor Snyk before each release. - POST — Watch discovery.tech-fingerprint. 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.
- PRE — Disable
x-powered-by. Innext.config.js:poweredByHeader: false. Removes a free version-disclosure signal. - PRE — Confirm middleware lives at
src/middleware.ts. With thesrc/directory layout, Next.js ignores a root-levelmiddleware.ts. Misplaced middleware silently fails to set CSP / auth headers / rate limits. - PRE — Sanity-check Vercel deployment protection. Production should be public; Preview should be password-protected or limited to org members. discovery.platform-vercel reports the surface.
- PRE — Block dotfile and config probes at the edge. Add a rewrite or a deny rule for
/.env,/.git/*,/.aws/*,/.next/tracepatterns. Vercel returns 403 for many of these by default; cross-check. - DEPLOY — Separate environments. Production, Preview, Development. Each gets its own set of secrets. Live keys never reach Preview, Stripe test mode never reaches Production.
- 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.
- 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.
- POST — Confirm health-check endpoints are minimal. A
/api/healthshould return200 OKwith 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.
- Verify your production domain in FixVibe. Dashboard → Domains → DNS TXT or HTTP file verification. This unlocks scheduled re-scans, active probing, and live threat monitoring.
- Schedule passive re-scans. Pro plans support 3-hour cadence; Unlimited supports 6-hour cadence. Every scheduled scan that surfaces a new finding triggers an email (and a webhook if you've wired one).
- 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. - Enable live threat monitoring on Unlimited. Certificate-transparency log diffs, DNS changes, JS bundle secret leaks, threat-intel listings — fired the moment they're detected, not on the next scheduled scan.
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.
