Security & Privacy

How to Secure a Vibe-Coded App: Complete Checklist

J
James Eriksson
··12 min read
Secure Vibe-coded apps with infrastructure authentication, Row-Level Security, and a pre-deployment checklist. Verify security without reading all the code.
TL;DR
  • Vibe-coded apps are fast but blind: AI generates code without context.
  • Test authentication at the infrastructure layer, not in code.
  • Enable Row-Level Security on your database to enforce data access.
  • Scan dependencies and search for hardcoded secrets before deploying.
  • Use the pre-deployment checklist to verify all three security layers.

Your Vibe-coded app works. But with security, "works" is not the same as "safe." AI-generated code is fast but blind to context: it hallucinates missing auth checks, skips input validation, and leaves secrets in config files. This tutorial walks you through three security layers--authentication, data access, and code quality--plus the infrastructure checks you need before going live.

Why Vibe-Coded Apps Fail Silently

Your app works. No errors on deploy. No crashes in the first week. That is why Vibe-coded apps are dangerous: they can fail silently on the security layer. You have no logs for the exploit. No one told you it happened.

Here is what happens: An AI coder runs through a checklist that is only as good as your prompt. You ask for "a user profile page." The AI builds it. Does it check that you own this profile? Maybe, maybe not. The hallucination is subtle. The button works. The data appears. Only later--when someone else's profile loads in your account, or when a competitor runs a security scan--does the blindness become obvious.

Real story: Pythagora.ai built a business on Vibe-coded internal tools. Their own AI-generated code got them hacked. The vulnerability was authorization: the code checked authentication (are you logged in?) but not authorization (are you allowed to see this?). It took a breach to find it.

The speed of Vibe coding is the same thing that breaks it. Traditional development: you write code, you review it, you test it. Vibe coding: an AI writes 10,000 lines in an afternoon. You deploy it because it works. You don't read 10,000 lines. You can't.

This guide teaches you how to verify what you can't read. You will not review the codebase line-by-line. Instead, you will audit the three layers where breaches actually happen: authentication (who gets access?), data access (what can they see?), and code quality (are the basic patterns correct?). Each layer has a checklist. By the end, you will have concrete confidence, not hope.

Security Layer 1: Authentication

Authentication is binary: either the user is who they claim to be, or they are not. Vibe-coded apps often get this wrong because the AI does not know the infrastructure. It generates code that assumes a framework will handle auth, then that framework is missing from the export.

The fix: authenticate at the infrastructure layer, not the code layer. Your app should not even see an unauthenticated request.

Here is the check:

  1. Disable public access to all endpoints. If you are on Ship, this is handled by the platform's auth proxy. If you are self-hosting, set your reverse proxy (NGINX, Caddy) to require valid JWT tokens or session cookies before any request reaches your app.

  2. Test it: open your browser's developer tools. Go to your app without logging in. You should see a 401 or 302 redirect. You should not see HTML. If you see your app, auth is not working.

  3. Check environment variables. Your AI probably did not read them correctly. Is your JWT secret in process.env.JWT_SECRET? Is it real? Run echo $JWT_SECRET in your deploy environment. If it is blank or generic, fix it now.

  4. Verify session timeout. Sessions should expire. Check your code: what is the TTL? Is it configured? Default is often 30 days, which is too long. Move it to 4 hours.

  5. Audit login endpoint rate limits. An AI often misses this. If you have /api/login, can an attacker request it 1,000 times per second? Enable rate limiting: 100 requests per hour per IP. Most frameworks have middleware for this; your AI might not have added it.

Why not rely on the app code? Because you cannot read 10k lines fast. You can test the infrastructure in minutes.

Security Layer 2: Authorization and Row-Level Security

Authentication says you are who you claim. Authorization says you can do what you are trying to do. Vibe-coded apps fail here because the AI does not understand your data model.

Example: You have a SaaS with a users table and a documents table. Alice works for Acme Inc. Bob works for Bezos Corp. They both log in. The AI generated code that shows Alice all documents. Does it check that the document belongs to Acme? Only if you were specific in your prompt. Usually not.

The fix: Row-Level Security (RLS) in your database. RLS is a layer that sits in Postgres (or Supabase) and says "this user can only see rows they own." It is not a code feature. It is a database policy.

Here is the checklist:

  1. Enable RLS on every sensitive table. If you use Supabase, go to the SQL Editor and run: ALTER TABLE documents ENABLE ROW LEVEL SECURITY;

  2. Write a policy. Example:

CREATE POLICY "users can only see their own documents" ON documents
  FOR SELECT
  USING (auth.uid() = user_id);

This says: only return rows where the user_id matches the logged-in user's ID.

  1. Test it with two accounts. Log in as Alice. Can you see Bob's documents? You should not. If you can, RLS is not enforcing.

  2. Check all data-access endpoints. For each API call that fetches data, verify: does it rely on the user's session ID, or does it fetch all rows? If it is the latter, RLS will rescue you. But if your AI left a bypass--a direct SQL query without filtering--RLS does not help.

Why not just hide the button? Because users see what they can request. If the data is there, someone will request it.

Relying on UI tricks (hiding buttons, not showing tabs) is security theater. Relying on RLS is security. Test it. Document it. Move on.

Security Layer 3: Code Quality

The third layer is what the AI actually wrote. You cannot read it, but you can scan it. Three things matter: input validation, dependency risk, and secrets.

Input validation: Does the code assume user input is safe? An AI might generate code like:

const user = await db.query(`SELECT * FROM users WHERE email = '${email}'`);

This is SQL injection. The email is not escaped. But if you use an ORM (Sequelize, TypeORM, Prisma), the AI usually gets it right because the ORM handles escaping. Check: does your code use parameterized queries or string interpolation? Search your codebase for backticks in SQL. If you find them, you have a problem.

Dependencies: Your AI imported 40 packages. Are any of them compromised? Run a dependency scan.

npm audit

This is free. GitHub Advanced Security is also free on public repos and cheap on private ones. Snyk is free for open-source dependencies. Pick one and run it. If you get medium or high vulnerabilities, you need to fix them or upgrade the package. Do not ignore this.

Secrets: Passwords, API keys, JWT secrets should never be in code. They should be in environment variables. Search your codebase for hardcoded values:

grep -r "password". --include="*.js" --include="*.ts"
grep -r "sk_". --include="*.js" --include="*.ts"

If you find secrets in code, rotate them immediately. They are compromised.

Why these three? Because they are the patterns that cause 90 percent of breaches. You cannot check everything. You can check these.

The Pre-Deployment Checklist

Before you let customers in, run through this. It takes an hour. Skipping it has cost companies millions.

  1. Authentication infrastructure is in place. Unauthenticated requests return 401, not HTML. Test it.

  2. All sensitive tables have RLS enabled. You have run a two-account test. You verified that Bob cannot see Alice's data.

  3. Environment variables are set correctly. No hardcoded secrets in code. JWT_SECRET, API_STRIPE_KEY, DB_PASSWORD--all in .env, not in git.

  4. Dependencies have been scanned. You ran npm audit or Snyk. You have upgraded or documented why you are ignoring medium-risk findings.

  5. Rate limiting is enabled on auth endpoints. /api/login, /api/signup, /api/reset-password all have 100-request-per-hour rate limits per IP.

  6. Session timeout is 4 hours or less. Check your auth configuration.

  7. HTTPS is enforced everywhere. Your domain should have an SSL certificate. If you use GDPR-compliant hosting, this is automatic. If you self-host, use Let's Encrypt. Test: visit http://yoursite.com. You should be redirected to https.

  8. Security headers are set. Your app should send:

    • Strict-Transport-Security: max-age=31536000
    • Content-Security-Policy (prevents XSS)
    • X-Frame-Options: DENY

Most frameworks include middleware for this. Check if your AI added it.

  1. Error messages do not leak information. If login fails, say "Invalid email or password." Do not say "Email not found" or "Password incorrect." This tells attackers which emails exist.

  2. You have a response plan. If something goes wrong, you know who to call. Do you have PagerDuty set up? Do you have logs? Can you see who accessed what?

Done. You are ready for customers.

Monitoring in Production

Deploying is not the end. Monitoring is where you catch attacks.

What to watch:

  1. Failed login spikes. If you see 1,000 failed logins from the same IP in an hour, you are under attack. Your rate limiting should have blocked it, but check: did the rate limit work? If not, manually block the IP.

  2. Unusual data access patterns. If a user who normally logs in once a day suddenly makes 10,000 API calls, something is wrong. Set up alerts: if any user makes more than 100 requests per minute, page you.

  3. Unexpected geographic access. If your users are all in Germany but someone is logging in from Indonesia, investigate. This could be a stolen session.

  4. Dependency vulnerabilities appearing. Packages you trusted yesterday might be compromised today. Set up GitHub alerts (free). They will email you when a zero-day is announced.

  5. Database query times. If a query that took 50ms now takes 5 seconds, someone might be running a data export. Check query logs.

Tools:

  • Logs: Check your hosting provider. If you use a managed platform like Ship, your infrastructure logs are available. If you self-host, use ELK (Elasticsearch) or Grafana. Free tier is enough to start.
  • Alerting: Set up basic alerts in your provider's dashboard. Most platforms give you email notifications.
  • Audit log: Every access to sensitive data should be logged. User ID, timestamp, action, result. Review weekly.

This sounds paranoid. It is not. It is standard. Do it.

Managed vs. DIY Infrastructure

You have secured the app. Now, infrastructure. Two options: managed or DIY.

Managed (Ship): You deploy your app. Ship handles certificates, firewalls, backups, DDoS protection, compliance. You do not touch servers. You focus on your code.

Pros:

  • SSL certificates rotate automatically. You never deal with expiration.
  • Firewalls are pre-configured. Most attacks never hit your app.
  • GDPR hosting. If you need data in Germany or EU-only, Ship has data residency.
  • Incident response. If there is a zero-day, Ship patches it before you wake up.
  • Compliance. SOC 2, GDPR, HIPAA-ready. No extra work.

Cons:

  • More expensive than DIY.
  • Less control (you do not configure NGINX directly).

DIY (Hetzner + Coolify): You rent a server, deploy your app using Coolify or Docker, manage everything yourself.

Pros:

  • Cheaper per month. Raw compute is cheaper on Hetzner than managed platforms.

Cons:

  • You own SSL certificate rotation. If it expires, your site goes down.
  • You patch. If NGINX has a zero-day, you fix it.
  • You manage backups. If your database corrupts, you restore it.
  • Compliance is your job. You write the SOC 2 doc yourself.
  • You respond to incidents at 3am.

Which is right for you? If you have a team and a budget, managed is worth it. If you are solo and paranoid about costs, DIY is possible but requires discipline. Most Vibe-coded apps are built by small teams or solo founders. Managed is the right choice because it lets you stay focused on the business logic, not server hardening.

Ship handles the infrastructure so you do not have to. You own the security of the code. Ship owns the security of the platform. Both matter.

Frequently Asked Questions

How secure is vibe coding really?

As secure as you make it. The code speed does not reduce security. The blindness does. An AI can write secure code. It can also write vulnerable code and not know the difference. The three-layer approach treats AI-generated code as untrusted input. If you run the checklist, your Vibe-coded app is as secure as a hand-written app.

Can I secure a vibe-coded app without reviewing all the code?

Yes. You do not review the code. You verify the layers. Authentication: test it. Authorization: test it with two accounts. Secrets: scan for them. Dependencies: run npm audit. Done. You have high confidence without reading 10k lines.

How can I check if my app has exposed API keys?

Run grep -r "sk_". --include="*.js" to find Stripe keys. Run grep -r "password\|secret\|token". to find others. Use the command-line grep, not your editor search--it is faster and catches comments too. If you find anything, rotate it immediately. It is compromised.

What's the easiest way to enable Row-Level Security?

Supabase has a UI for it. Postgres does not. If you use Supabase, go to the SQL Editor and run the policy commands from this guide. If you use vanilla Postgres, connect via psql and run the same commands. Test with two accounts to verify it works.

Should I use a managed host or self-host for security?

Managed is more secure for most teams because you have fewer patches to apply and professionals managing the infrastructure. Self-hosting is possible but requires discipline. If you are solo or early-stage, managed is the faster path to security.

How often should I scan dependencies?

Weekly, at minimum. Set up GitHub Advanced Security alerts. They will email you when a dependency has a zero-day. You are not paranoid. You are paying attention.

What's the most common security mistake in AI-generated apps?

Trusting that authentication is done. The AI generates code that looks like it checks auth, but it assumes a framework handles it. The framework is missing on export. Always verify at the infrastructure layer.

The Bottom Line

You have secured the app layer. You know what to test. You know what to verify. The infrastructure layer--certificates, firewalls, backups, compliance--is a separate concern. If you choose a managed platform, you offload that work. If you self-host, you own it. Either way, your app is safer than it was yesterday.

Start with the checklist. Run it before you let in customers. After that, monitor. Breaches happen in production, not in dev. Stay alert.

Deploy securely
Ship handles infrastructure security so you can focus on app logic.
Get Started Free

Ready to self-host your own apps?

One server. Multiple apps. No per-app fees.

Get started →