← Back to blog
CLAUDE CODE

Claude Code Security Mistakes: What AI Gets Wrong

We built vibeAudit with Claude Code. Then we scanned it. We found vulnerabilities in our own app — written by the same AI that powers our scanner. Here's what Claude Code gets wrong.

This isn't a hit piece. We use Claude Code every day. It's fast, it's good at architecture, and it writes clean code. But clean code isn't the same as safe code. And if you're shipping anything Claude Code wrote without scanning it, you're gambling.

Built something with Claude Code? Paste your URL into vibeAudit and see what it missed. 30 seconds, free. Read-only scan, your app is never modified.

1. Hardcoded Secrets That Ship to Production

Claude Code puts placeholder keys directly in source files. It does this because it's trying to give you working code fast. The problem: those placeholders look real, nobody remembers to swap them, and they end up in your deployed app.

// What Claude Code generates in utils/stripe.ts:
const stripe = new Stripe("sk-test-4eC39HqLyjWDarjtT1zdp7dc")

// What it should generate:
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)

// That test key? It's in your Git history forever.
// Even if you fix it later, anyone who clones your repo has it.

We caught this in our own codebase. A test Stripe key sitting in a utility file, committed three weeks before anyone noticed. Claude Code wrote it. We shipped it. Our scanner found it. (Same problem with Cursor — here's the full breakdown)

2. API Endpoints With Zero Input Validation

Ask Claude Code to "add an endpoint that creates a user." You'll get a working endpoint. You won't get input validation, rate limiting, or size limits. It builds the happy path and nothing else.

# What Claude Code generates:
@app.post("/api/users")
async def create_user(data: dict):
    user = await db.users.insert(data)
    return user

# What you actually need:
from pydantic import BaseModel, EmailStr, constr

class CreateUserRequest(BaseModel):
    email: EmailStr
    name: constr(min_length=1, max_length=100)

@app.post("/api/users")
@limiter.limit("5/minute")
async def create_user(data: CreateUserRequest):
    user = await db.users.insert(data.dict())
    return user

The first version accepts anything. A 10MB name field. SQL in the email. A million requests per second. Claude Code doesn't think about abuse because you didn't ask it to.

3. CORS Set to Allow Everything

Claude Code defaults to Access-Control-Allow-Origin: * because it makes development easy. It never warns you to change this before deploying. Every origin on the internet can now make authenticated requests to your API.

# Claude Code's default — works in dev, dangerous in prod:
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,  # This + wildcard = disaster
    allow_methods=["*"],
    allow_headers=["*"],
)

# What you need in production:
app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://yourdomain.com"],
    allow_credentials=True,
    allow_methods=["GET", "POST"],
    allow_headers=["Authorization", "Content-Type"],
)

Think your CORS is fine? vibeAudit checks CORS configuration automatically — paste your URL and find out in 30 seconds. Read-only scan, your app is never modified.

4. SQL Injection via String Interpolation

Claude Code sometimes builds database queries with f-strings instead of parameterized queries. This is SQL injection 101, and it still happens in 2026. We see it most in Python/FastAPI apps with raw SQL.

# Vulnerable — Claude Code writes this more often than you'd think:
@app.get("/api/users/{user_id}")
async def get_user(user_id: str):
    query = f"SELECT * FROM users WHERE id = '{user_id}'"
    result = await db.execute(query)
    return result

# An attacker sends: /api/users/' OR '1'='1
# Your query becomes: SELECT * FROM users WHERE id = '' OR '1'='1'
# Congratulations, you just returned every user in the database.

# Safe — parameterized query:
@app.get("/api/users/{user_id}")
async def get_user(user_id: str):
    query = "SELECT * FROM users WHERE id = :user_id"
    result = await db.execute(query, {"user_id": user_id})
    return result

If you're using an ORM, you're probably safe. If Claude Code wrote any raw SQL in your app, go check it right now. (Supabase users: RLS is your second line of defense)

5. No Authentication Middleware by Default

Claude Code builds what you ask for. You ask for a settings page. It builds a settings page. You don't ask for auth protection, so it doesn't add auth protection. Every route is public until you explicitly say otherwise.

# Claude Code generates public routes by default:
@app.get("/api/admin/users")
async def list_all_users():
    return await db.users.find_all()

# Anyone can hit this. No token check. No role check. Nothing.

# What every protected route needs:
from fastapi import Depends, HTTPException
from auth import get_current_user

@app.get("/api/admin/users")
async def list_all_users(user = Depends(get_current_user)):
    if user.role != "admin":
        raise HTTPException(status_code=403, detail="Forbidden")
    return await db.users.find_all()

We're Not Saying Don't Use Claude Code

We built vibeAudit with it. We use it every day. It's genuinely good at writing code that works. But working code and safe code are different things. Claude Code optimizes for "does it run?" not "can it be exploited?"

That's not a flaw in the tool — it's a gap you need to fill. Scan everything. (Here's the full security testing checklist for AI-built apps)

FAQ

Q: Is Claude Code safe to use?

Claude Code writes functional, clean code. But it doesn't add security features unless you ask. Always review AI-generated code for hardcoded secrets, missing auth, and input validation.

Q: What are the most common Claude Code security mistakes?

Hardcoded API keys, missing input validation, permissive CORS (allow_origins: *), SQL injection via f-strings, and no authentication middleware on routes.

Built with Claude Code? Built with any AI tool? Paste your URL into vibeAudit — we'll catch what the AI missed. 30 seconds, free, no signup. Read-only scan, your app is never modified.

Is your app vulnerable?

Scan your app for free — read-only, no signup, ~30 seconds. vibeAudit runs the tests this post describes.

Scan your app free