Secure vibe coding is not about slowing down. It is about running a deliberate pass before you ship — because vibe coding tools (Cursor, Claude Code, Lovable, Bolt, Windsurf, Replit) optimize for code that works, not code that is secure. They inline credentials to unblock integrations, skip authorization checks that the feature does not need to function, and reach for string concatenation before parameterized queries. In our scan of 976 real AI-built apps, 1 in 8 had a high-severity flaw. This checklist covers every category.
Why vibe coding produces predictable security gaps
The same models make the same tradeoffs every time they are asked to move fast. When Cursor needs a credential to wire up an integration, the path of least resistance is to write the literal value into the source file. When Lovable generates an API route, it creates the handler that satisfies the feature requirement — the server-side check that the caller owns the resource is not required for the feature to work, so it gets skipped. These are not random bugs. They are systematic patterns that appear in almost every AI-generated codebase, which means they are also fast to find and fix once you know what to look for.
Scan your own app for issues like these
Paste your live URL. We check what your app serves publicly for exposed keys and misconfigurations. No account, no install.
Pass 1: secrets and credentials
This is the highest-blast-radius category and the fastest to check. Scan your full codebase and git history for API keys, connection strings, and credentials written directly into source. Use Prbl’s free secret checker to scan any public URL instantly, or read what happens when you push an API key to understand why rotation comes first.
- Search for patterns like
sk_live_,ghp_,eyJ(base64-encoded JWT headers),postgres://,mongodb+srv:// - Check migration files, seed scripts, and config files — these are where AI tools most often inline real values
- Check git history, not just the current branch:
git log --all --full-history -- "*.env" - If any key appears in source or history, rotate it immediately — assume it was found
- Move all credentials to environment variables with no
NEXT_PUBLIC_prefix on server-only keys
Also check for fallback secrets in environment variable lookups — a pattern AI tools produce frequently:
// DANGEROUS: if JWT_SECRET is unset in production, this uses a predictable default
const secret = process.env.JWT_SECRET || 'dev-secret-change-me';
// CORRECT: fail loudly if the secret is missing rather than silently using a weak default
const secret = process.env.JWT_SECRET;
if (!secret) throw new Error('JWT_SECRET environment variable is required');Pass 2: authentication and authorization
Missing auth checks are the most common finding in AI-generated code. There are two separate problems to check for:
- No auth check at all. The route returns or mutates data for any caller. Add a session check at the top of every route that handles user data.
- Auth without ownership check (BOLA). The route confirms the user is logged in, but returns any record regardless of who owns it. Every route that reads a specific record by ID must also verify the record belongs to the requesting user.
// VULNERABLE: checks auth but not ownership
app.get('/api/invoices/:id', authenticate, async (req, res) => {
const invoice = await db.invoice.findById(req.params.id); // returns anyone's invoice
res.json(invoice);
});
// CORRECT: checks both auth and ownership
app.get('/api/invoices/:id', authenticate, async (req, res) => {
const invoice = await db.invoice.findOne({
_id: req.params.id,
userId: req.user.id, // must belong to the requesting user
});
if (!invoice) return res.status(404).json({ error: 'Not found' });
res.json(invoice);
});Pass 3: JWT handling
AI tools frequently generate code that decodes a JWT instead of verifying it. Decoding reads the payload with no secret key and no signature check — anyone can forge a token with any claims they want. Every route that trusts a JWT must use verify(), not decode():
// VULNERABLE: decode() does not check the signature
const payload = jwt.decode(token); // trusts any token unconditionally
// CORRECT: verify() checks the signature and throws on tampered or expired tokens
const payload = jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ['HS256'],
});Full explanation: JWT decode vs verify.
Pass 4: database queries
- Replace every string-concatenated SQL or NoSQL query with a parameterized query or ORM method. See SQL injection from string concatenation.
- If using Supabase, enable Row Level Security on every table and verify an anonymous read returns nothing. Use the free Supabase RLS checker to confirm.
- Check that the
service_rolekey is never in client code — it bypasses RLS entirely. Learn the difference: anon key vs service_role key.
Pass 5: wire it into CI so it stays clean
A one-time audit goes stale the moment you ship the next AI-generated feature. The only sustainable approach is continuous scanning:
- Add a scanner to your CI pipeline that runs on every pull request
- Configure it to comment on the PR with any new findings before merge
- Treat a HIGH finding like a failing test — do not merge until it is addressed
This also generates the audit trail that matters for SOC 2 and enterprise security questionnaires: months of evidence that findings are caught and fixed as a matter of routine.
Secure vibe coding: the pre-deploy checklist
Run through this before every production deploy:
- No API keys, connection strings, or JWT secrets in source files or git history
- No
NEXT_PUBLIC_prefix on server-only secrets - No
process.env.SECRET || 'fallback'patterns — fail loudly if a required var is missing - Every API route that reads or writes user data has a server-side auth check
- Every route that fetches a record by ID checks that the record belongs to the requesting user
- JWT tokens are verified with
jwt.verify(), not decoded withjwt.decode() - No string-concatenated SQL queries — parameterized queries or ORM methods only
- Supabase RLS is enabled on every table and tested with an anonymous request
- The
service_rolekey is only in server-side environment variables, never in client code - A scanner runs on every pull request in CI before merge
Common questions
Is vibe-coded code less secure than hand-written code?
Yes, measurably. In our scan of 976 real AI-built apps from GitHub, 1 in 8 had at least one high-severity finding. The patterns are predictable: hardcoded secrets, missing server-side auth checks, JWT tokens decoded without verification, and fallback secrets in environment variable lookups. These aren't random bugs — the same models make the same tradeoffs every time they're asked to move fast.
How do I secure a vibe-coded app?
Work through it in four passes. First, run a secrets scan across the codebase and git history and rotate any key that appears in source. Second, check every API route for a server-side auth check — confirm it verifies the caller owns the resource, not just that they're logged in. Third, check JWT handling: decode is not verify. Fourth, wire a scanner into CI so every future push is checked automatically. The first three passes are a one-time catch-up; the fourth is what keeps it clean.
What are the most common security issues in AI-generated code?
In order of frequency from our 976-repo scan: missing authentication on API routes (the single most common), hardcoded API keys and credentials, fallback secrets in environment variable lookups (process.env.SECRET || 'default'), JWT tokens decoded without signature verification, SQL/NoSQL injection from string concatenation, and Broken Object Level Authorization (checking that a user is logged in, but not that they own the specific record).
How do I check if my vibe-coded app has hardcoded secrets?
Run a secrets scanner across your full codebase and git history, not just the current branch. Tools like truffleHog, gitleaks, or Prbl's scanner check for API key patterns across file types. Pay special attention to migration files, seed scripts, config files, and any file created during an AI session to bootstrap an integration.
What is BOLA and why do AI tools miss it?
BOLA (Broken Object Level Authorization) means your route checks that a user is authenticated, but not that they own the specific object they're requesting. For example, GET /api/invoices/:id might check for a valid session but return any invoice regardless of whose it is. AI tools generate the authentication check (is the user logged in?) but frequently omit the ownership check (is this user allowed to see this specific record?), because only the auth check is needed to make the happy path work.
Do I need to secure vibe-coded apps differently from regular apps?
The vulnerabilities are the same classes as any web app, but the distribution is different. AI-generated code has a higher concentration of hardcoded secrets, missing authorization, and fallback credentials than hand-written code — so those categories deserve disproportionate attention in a vibe-coded codebase. The remediation steps are identical: rotate secrets, add auth checks, use parameterized queries, verify JWTs.