Why it matters

Your AI is shipping security bugs you can't see.

Cursor, Claude, Copilot, Windsurf — they all write code that runs. That's the bar for an AI assistant. It's not the bar for code that goes to production. This page walks through the eight mistakes AI-generated code makes most often, with real examples, why they happen, and how to spot them. No fearmongering. Just the patterns.

01
Secrets

Hardcoded API keys end up in the repo

When you tell an AI "set up Stripe" or "connect to OpenAI," it often inlines a placeholder key that looks like a real one — and sometimes the key you pasted into the chat. Those keys end up in .env, config.ts, or worse, in code that gets bundled to the browser.

// AI-written, lives in src/lib/openai.ts
const client = new OpenAI({ apiKey: "sk-proj-9xN2..." });
Why it happens: the AI optimizes for "code that runs in the next 5 seconds." A hardcoded key satisfies that. Pulling from process.env requires more setup the AI can't see.
02
Public Bundle

Server secrets shipped to every visitor

In Next.js, Vite, and Expo, any environment variable prefixed with NEXT_PUBLIC_, VITE_, or EXPO_PUBLIC_ is inlined into the client JavaScript bundle. AIs frequently pick these prefixes because the variable "needs to be accessible from the frontend" — without realizing the cost.

// .env.local — written by AI, read by visitors
NEXT_PUBLIC_STRIPE_SECRET_KEY=sk_live_...
NEXT_PUBLIC_DATABASE_URL=postgres://...
Why it happens: the AI sees process.env.X failing in the browser and "fixes" it by adding the public prefix. The fix works. The cost ships to production.
03
Auth

API routes with no authentication check

"Build me an endpoint that returns the user's invoices." The AI writes the SQL, the route handler, and the response. What it skips: the line that confirms the request is from that user, not a stranger with a guessed user ID.

// app/api/invoices/[userId]/route.ts
export async function GET(_, { params }) {
  // Anyone with the URL can read anyone's invoices.
  return Response.json(await db.invoices.findMany({ where: { userId: params.userId }}));
}
Why it happens: auth is application-specific. The AI doesn't know your session system. It defers — and "defer" usually means "skip."
04
Injection

String-interpolated SQL queries

Modern ORMs make SQL injection nearly impossible — until the AI hits a query the ORM can't express, falls back to raw SQL, and concatenates user input into a string. Now any visitor with a curl command can read your entire users table.

// AI-written for a "search users" feature
db.query(`SELECT * FROM users WHERE name LIKE '%${query}%'`);

// What it should be
db.query("SELECT * FROM users WHERE name LIKE $1", [`%${query}%`]);
Why it happens: the AI's training data has more raw-SQL examples than your ORM. Under pressure, it falls back to what it has more of.
05
XSS

User content rendered as raw HTML

"Render the markdown the user wrote." The AI reaches for dangerouslySetInnerHTML, v-html, or {@html} because it's the path with the fewest steps. Now anything a user writes — a comment, a profile bio — runs as JavaScript in every other user's browser.

<div dangerouslySetInnerHTML={{ __html: comment.body }} />
Why it happens: the AI conflates "render markdown" with "render HTML." Sanitization (DOMPurify) is one extra dependency the AI didn't think to add.
06
CORS

Wildcard CORS plus credentials = CSRF gift

Almost every "fix CORS" prompt resolves to Access-Control-Allow-Origin: *. When that combines with cookies or Allow-Credentials: true, you've handed any malicious site permission to make authenticated requests on behalf of your logged-in users.

res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Credentials", "true");
Why it happens: "fix CORS" sounds like a fence repair. The AI removes the fence. The error goes away. So does your security boundary.
07
Crypto

Math.random() used for security tokens

Password reset tokens, magic-link codes, session IDs, share-link slugs — when the AI needs "a random thing," it reaches for Math.random(). That's not cryptographically secure. Given a few outputs, an attacker can predict the next ones.

const resetToken = Math.random().toString(36).slice(2);

// Safe
const resetToken = crypto.randomUUID();
Why it happens: Math.random() is the most common JavaScript "random" function in training data. The AI doesn't distinguish between "random for shuffling an array" and "random for issuing a security credential."
08
TLS

Certificate verification disabled "to make it work"

The AI hits a cert verification failed error during local development. It googles its own context and disables TLS verification — and the toggle stays off in the deployed code. Now your server trusts any TLS handshake, valid or not.

// Node
const agent = new https.Agent({ rejectUnauthorized: false });

// Python
requests.get(url, verify=False);

// Go
http.DefaultTransport.(*http.Transport).TLSClientConfig =
  &tls.Config{InsecureSkipVerify: true};
Why it happens: the AI optimizes for "make the error go away." Disabling the check is a one-line answer that always works. The trade-off — silently accepting any certificate — isn't visible at the time the fix is applied.
This is what we built VybeSafe to catch.

Run one command. See exactly which of these you have.

VybeSafe scans your project locally — no upload, no signup — and flags every instance of these eight patterns plus 50 more. Each finding ships with a copy-paste prompt for your AI to fix it.

no spam · unsubscribe in one click · we never share your email