Skip to main content

Blog / AI security

Vibe coding security: a checklist for shipping AI-written code safely

AI assistants write working code fast, and they repeat the same security mistakes: exposed keys, missing authorisation checks, insecure defaults and made-up packages. Here are the patterns to look for and a checklist for every stage from prompt to production.

Key takeaways

  • Vibe coding moves the bottleneck from writing code to reviewing it. Security problems come from accepting changes nobody read closely.
  • AI-written code fails in predictable ways: secrets in client code, missing authorisation checks, string-built queries, permissive defaults, and dependencies that are outdated or do not exist.
  • Most of these are cheap to catch if you look at the right moment: before you accept a change, before you merge, and before you deploy.
  • Automate what is pattern-shaped (injection, secrets, vulnerable or suspicious packages, misconfiguration) and spend human review on what is not (who is allowed to do what).
  • Your coding agent is part of your attack surface too. Review the MCP servers and tools it can use, and do not let it run commands unattended.

What vibe coding changes about security

"Vibe coding", a phrase popularised by Andrej Karpathy in early 2025, describes building software by describing what you want to an AI assistant and accepting what it produces, often without reading every line. Tools such as Cursor, Claude Code, GitHub Copilot and Windsurf have made it the default way many prototypes, and a growing share of production code, get written.

The security problem is not that AI writes uniquely bad code. It is that AI writes plausible code quickly, and plausible code passes a quick glance. The assistant optimises for "it works when I try it", which is exactly the test that does not exercise authorisation, hostile input or a misconfigured deploy. The result is a familiar set of flaws, produced faster than review keeps up.

The flaws AI-written code repeats

1. Secrets in the wrong place

Assistants make the error go away by putting the key where the code can see it: hardcoded in a file, committed in an example .env, or in a browser-exposed environment variable. In Next.js, any variable prefixed NEXT_PUBLIC_ is compiled into the JavaScript every visitor downloads; Vite's VITE_ prefix does the same.

Vulnerable: a secret key in a browser-exposed variable
# .env.local
NEXT_PUBLIC_AI_API_KEY=<secret key>   # shipped to every visitor's browser
Better: the key stays on the server, behind your own route
// app/api/summarise/route.ts runs on the server; the key never reaches the browser.
// Add authentication and a rate limit before shipping it.
export async function POST(req: Request) {
  const { text } = await req.json();
  const res = await fetch("https://api.example-ai.com/v1/summarise", {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.AI_API_KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({ text: String(text).slice(0, 8000) }),
  });
  return Response.json(await res.json());
}

If a key was ever committed, removing it in a later commit is not enough: it is still in the git history. Rotate it.

2. Authentication without authorisation

Ask for "an endpoint to get an invoice" and you will usually get one that checks the user is signed in, but not that the invoice belongs to them. This is an insecure direct object reference (IDOR), and it is the most common serious flaw in AI-built apps because the happy-path test passes.

Vulnerable: any signed-in user can read any invoice by changing the ID
@app.get("/invoices/{invoice_id}")
def get_invoice(invoice_id: int, user: User = Depends(current_user)):
    return db.get(Invoice, invoice_id)
Better: check ownership on every object you load
@app.get("/invoices/{invoice_id}")
def get_invoice(invoice_id: int, user: User = Depends(current_user)):
    invoice = db.get(Invoice, invoice_id)
    if invoice is None or invoice.account_id != user.account_id:
        raise HTTPException(status_code=404)   # same answer as "not found"
    return invoice

The same applies to updates and deletes, to admin-only actions, and to database-as-a-service setups where the assistant turned off row-level security to get a query working.

3. Injection from string-built queries and commands

Assistants still produce f-strings and template literals for SQL, shell commands and file paths, especially in quick scripts that later become endpoints.

Vulnerable and fixed: a login lookup
# Vulnerable: the email is spliced into SQL
cur.execute(f"SELECT id, password_hash FROM users WHERE email = '{email}'")

# Better: a parameterised query
cur.execute("SELECT id, password_hash FROM users WHERE email = %s", (email,))

4. Insecure defaults that make the demo work

When something fails in development, the fastest fix is often to open it up: allow every origin, turn on debug, make the storage bucket public, disable certificate verification, give the service account admin. Those changes rarely get reverted.

Vulnerable and fixed: CORS
# Vulnerable: every website may call the API, with the user's cookies
# (depending on the framework, credentials plus a wildcard reflects any origin)
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True,
                   allow_methods=["*"], allow_headers=["*"])

# Better: name the origins you serve
app.add_middleware(CORSMiddleware, allow_origins=["https://app.example.com"],
                   allow_credentials=True, allow_methods=["GET", "POST"],
                   allow_headers=["Content-Type", "Authorization"])

Infrastructure written by an assistant (Dockerfiles, Terraform, Kubernetes manifests) has the same habit: containers running as root, security groups open to 0.0.0.0/0, storage without encryption or with public access.

5. Outdated, vulnerable or non-existent dependencies

Models learned from code written before their training cut-off, so they suggest library versions that were current then and may have known vulnerabilities now. They also sometimes suggest packages that do not exist, with plausible names. Attackers register those names and wait; this is sometimes called slopsquatting. A one-line npm install or pip install of a hallucinated package runs the attacker's code on your machine.

  • Check that every new package exists, is the one you meant, has a real maintainer and history, and is not a near-miss of a popular name.
  • Install the latest supported version, not the one the assistant remembered, and commit the lockfile.
  • Scan dependencies for known vulnerabilities and malicious-package advisories on every change.

6. Validation and limits that only exist in the browser

Generated front ends often validate input carefully while the API behind them accepts anything. Login, signup, password reset and AI endpoints frequently ship with no rate limit, which invites credential stuffing and very large model bills.

  • Validate every input on the server with a schema, whatever the front end does.
  • Rate-limit authentication and AI endpoints per user and per IP.
  • Return generic errors; log the detail on the server instead of sending stack traces to the client.

7. An over-trusted coding agent

The assistant itself has access: to your files, your terminal, your credentials and the MCP servers you connect. A prompt injection in a README, an issue or a web page the agent reads can steer it. Treat agent permissions like any other privileged access: do not auto-approve shell commands, pin and review the MCP servers you add, and keep production credentials out of the environment the agent runs in. Our MCP server security guide covers the details.

The vibe coding security checklist

Use it at five moments. Each check takes minutes, and most can be automated.

Before you prompt
CheckWhy
Tell the assistant your security constraints up front (auth model, tenant isolation, validation library)It follows explicit constraints far better than implied ones
Keep a project rules file with those constraints, and review it like codeRules files steer every later change
Run the agent without production credentials or broad cloud accessLimits the damage of a bad or injected command
Before you accept a change
CheckWhy
Read every route or handler that was added or changedAuthorisation bugs hide in code that works
Every object load checks ownership or roleStops IDOR, the most common serious flaw
No secrets in code, examples or NEXT_PUBLIC_ / VITE_ variablesBrowser bundles and git history are public enough
Queries, commands and paths are parameterised, not string-builtPrevents injection
Every new package exists, is intended and is currentPrevents slopsquatting and known CVEs
Before you merge
CheckWhy
Static analysis (SAST) runs on the pull requestCatches injection and unsafe patterns automatically
Dependency scanning (SCA) runs, including malicious-package checksCatches vulnerable and suspicious packages
Secret scanning covers the diff and historyA key removed later is still exposed
Tests cover the unhappy path: another user, no permission, hostile inputGenerated tests usually only cover success
A human reviews authorisation, data access and anything touching moneyScanners do not know your business rules
Before you deploy
CheckWhy
No wildcard CORS with credentials; debug mode offCommon demo-only settings
Storage buckets, databases and admin panels are not publicGenerated infrastructure defaults open
Containers do not run as root; security groups are narrowLimits blast radius
Rate limits on login, signup, reset and AI endpointsStops credential stuffing and runaway costs
Security headers and HTTPS everywhereBasic browser protections
After you ship
CheckWhy
Rescan on every change to the default branchNew code, new findings
Watch dependencies for new advisoriesYesterday's safe version can become vulnerable
Keep an inventory of the AI providers, models and MCP servers in useYou cannot secure what you do not know about

Automate the pattern-shaped checks

Half of the checklist is pattern recognition, which is what scanners are good at. Put them on every pull request so the assistant's speed does not outrun review:

  • SAST for injection, unsafe deserialisation, weak cryptography and similar patterns, mapped to CWE identifiers.
  • SCA for vulnerable dependencies, prioritised by exploit signals (CISA KEV and EPSS) rather than severity alone, plus checks for known malicious packages and typosquat-style names.
  • Secret detection across the code and its git history.
  • IaC checks for Terraform, Kubernetes, CloudFormation and Dockerfiles.
  • AI pull request review that combines those deterministic findings with AI-assisted review and a quality gate you can require in branch protection.

CodeStax runs all of these in one scan for GitHub, GitLab and Bitbucket repositories, and its AI security scanning also inventories the AI providers, models, agents and MCP servers your code uses.

Common questions

What is vibe coding?

Vibe coding is building software by describing what you want to an AI assistant and accepting the code it generates, often without reading all of it. The phrase was popularised by Andrej Karpathy in early 2025.

Is AI-generated code secure?

Not by default. It often works on the happy path while missing authorisation checks, input validation and secure configuration, and it can suggest outdated or non-existent packages. Review it and scan it like any other code, with extra attention to access control.

What is slopsquatting?

Slopsquatting is registering package names that AI assistants hallucinate, so that developers who install a suggested but non-existent package get the attacker's code instead. Verify every new dependency before installing it.

What are the most common security flaws in AI-written code?

Secrets in client code or git history, missing authorisation checks (IDOR), string-built SQL and shell commands, permissive defaults such as wildcard CORS or public storage, vulnerable or hallucinated dependencies, and validation or rate limits that exist only in the browser.

How do I secure code written with Cursor, Claude Code or Copilot?

Give the assistant explicit security constraints, read every changed route, run SAST, dependency and secret scanning on each pull request, test the unhappy paths, and limit what the coding agent itself can access and run.

Your next step

Scan the code your assistant wrote this week.

Connect a repository on the Free plan and get SAST, dependency, secret and IaC findings in one scan.

Get started free