Home/Journal/Pattern
Patternai-generated-codesecure-codingsupabase

Why AI-Generated Code Ships Insecure by Default

AI writes most startup code now, and it reproduces the insecure patterns in its training data at machine speed. Here are the recurring flaws and the fixes.

Ajay Kumar··9 min read

What this is

AI-generated code is insecure by default because AI coding tools reproduce the patterns most common in their training data, and the most common patterns on the public internet include insecure defaults. The code runs, matches the request, and passes the demo. Whether it resists an attacker is a separate question the model was never asked. So the scaffold ships with row-level security off, secrets in the browser, and access control enforced client-side, and it ships faster than any human review cadence can catch.

This is not a knock on the tools or the people using them. It is a structural property of how the code is produced. And it is now the single most common root cause we see, because AI writes or accelerates most of the code we test.

Why is AI-generated code insecure?

A model that writes code optimizes for one thing: output that satisfies the prompt. “Build me a dashboard where users can see their orders” produces a dashboard where users can see their orders. It does not produce a dashboard that stops user A from reading user B’s orders, because the prompt never mentioned user B, and the training examples that showed this pattern working mostly did not show the control that makes it safe. Security is invisible in a working demo. A correctly locked-down app and a wide-open one look identical on screen.

Three forces compound this. First, the training data. Public tutorials, starter templates, and Stack Overflow answers optimize for brevity and “it works,” and the fastest way to make something work is to skip the auth. The model learned from a corpus where the insecure version is the common version. Second, the prompt. Founders describe features, not threat models. Nobody prompts “and make sure the paywall cannot be bypassed by editing the JavaScript,” so the model does not add that. Third, and this is the part that changes the stakes, is speed. AI does not just write insecure code. It writes it at machine speed and ships it before any human review cadence runs.

That speed matters because the window to exploit a flaw has collapsed. According to the Zero Day Clock, the median time from a vulnerability being disclosed to it being exploited in the wild fell from 771 days in 2018 to 6 days in 2023 to roughly 4 hours in 2024. When a scaffold ships an insecure default on Monday, the assumption that “we will harden it later” is a bet against a clock that now runs in hours. Later is often after.

What security mistakes does AI-generated code make?

Five insecure defaults recur across nearly every AI-scaffolded stack we test. They are worth naming precisely, because each one is a specific control that was skipped, not a vague “weakness.”

  1. Row-level security left off on the database. Managed Postgres backends like Supabase ship with tables that are readable and writable by any authenticated client until row-level security (RLS) is explicitly turned on and a policy is written. The scaffold creates the tables. It rarely writes the policies. The result is a database where any logged-in user can read every row in every table, because the only thing standing between them and the data was a control nobody enabled.

  2. Secrets baked into the client bundle. An LLM API key, a third-party service key, a database service-role key: the model needs to call something, so it puts the key where the code that calls it lives, which is the frontend. Anyone who opens the browser developer tools or reads the shipped JavaScript bundle can extract it. A key in the client is a key in the public.

  3. Access control and paywalls enforced client-side only. The model builds a paywall by hiding the premium content with a conditional in the browser, or gating a route with a check that runs in React. Both are enforced on the attacker’s own machine. Deleting the conditional, flipping the flag in the developer console, or calling the underlying API directly bypasses the entire wall. The check ran in the one place the attacker controls.

  4. Endpoints that trust client-supplied input for identity or authority. The scaffold writes an endpoint that reads the user ID, the account ID, or the role from the request body or a query parameter, and trusts it. Change the ID in the request and you are someone else. The server never asked who you actually are, because the token that would answer that was more work than reading the field the client sent.

  5. Over-granted database functions that bypass row-level security. This is the subtle one, and it defeats teams who did the first item right. RLS is on, the policies are correct, the tables are locked. But the scaffold also created database functions (stored procedures, RPCs) that run with elevated privilege to “just get the work done,” and those functions read and write the tables without going through the policies. The lock is on the front door and the service entrance is propped open.

What it looks like in the wild

We have found these defaults across very different products, which is what makes them a pattern and not an anecdote.

The consistent theme across all three: the code worked. The dating app matched, the hospital system stored records, the fintech registered users. Working was never the question.

How to tell if you are exposed

A short self-check for any AI-scaffolded stack:

  1. Test row-level security with a second account. Create two users. Log in as the first, note a record ID that belongs to it, then, as the second user, try to read and write that record directly through the API. If it succeeds, RLS is off or the policy is wrong. Do this before you trust any dashboard that “only shows your own data.”
  2. Grep the shipped client bundle for secrets. Open your production JavaScript bundle and search it for key, secret, token, and your service providers’ names. Anything that looks like a credential is a credential the public has.
  3. Bypass your own paywall. Open the developer console, flip the flag that gates premium content, or call the premium API route directly with a free account. If you get in, the wall is client-side.
  4. Change the ID in a request. Take any authenticated request that includes a user ID, account ID, or role, change it to another value, and replay it. If the server honors it, it is trusting client-supplied identity.
  5. List your privileged database functions. Enumerate every stored procedure and RPC, and for each one ask whether it runs with elevated privilege and whether it re-checks the caller’s access. If a function bypasses RLS, it is a hole in RLS.

How to fix it

The durable fix is a change in posture, not a patch. Treat every AI-generated scaffold as a draft that has not yet been secured, and give it a dedicated security pass before production. Concretely:

  1. Turn row-level security ON, and test it with a second account. On by default, deny by default, and a written policy for every table. Verified by the two-account test above, not by inspection.
  2. Move every secret to the server. No API key, service key, or token in the client bundle. The browser calls your server, and your server holds the credentials and calls the third party.
  3. Enforce authorization and payment on the server. Every access decision and every paywall check runs in code the attacker cannot edit. The client can hide a button for convenience, but the server must be the one that says no.
  4. Audit database functions for over-granted privilege. Every function that runs elevated must re-check the caller’s access itself. If RLS is your control, nothing may bypass it, including your own procedures.
  5. Never trust a client-supplied field for identity or role. Derive identity from the authenticated session or a validated token, on the server, every time. The role a user has is something your server decides, never something the signup form sends.

The takeaway

AI-generated code is a draft that runs, not a product that is safe. The tool optimized for output that matches the prompt, and safety was never in the prompt. Give every scaffold a dedicated security pass before it ships, because the clock from insecure default to exploited flaw now runs in hours, not years.

Frequently asked

Is AI-generated code secure?
Not by default. AI coding tools reproduce the patterns most common in their training data, and the most common patterns on the public internet include insecure defaults: row-level security left off, secrets in the client bundle, and access control enforced only in the browser. The code usually works, which is different from being safe. Every AI-generated scaffold needs a dedicated security pass before it reaches production.
What security mistakes does AI-generated code make most often?
Five recur: row-level security left disabled on the database so any client can read any row; API keys and secrets baked into the client bundle where anyone can extract them; access control and paywalls enforced only in the browser; endpoints that trust client-supplied fields like user ID or role; and over-granted database functions that run with elevated privilege and bypass the very row-level security the tables have.
How do I make AI-generated code secure?
Turn row-level security on and test it with a second account, move every secret to the server, enforce authorization and payment server-side, audit database functions for over-granted privilege, and never trust a client-supplied field for identity or role. Treat the scaffold as a draft that has not yet been secured, not as finished code.
Why does AI write insecure code if it knows better?
An AI model optimizes for code that runs and matches the request, not for code that resists an attacker who was never described in the prompt. Security controls are usually invisible in a working demo, so they are under-represented in the examples the model learned from and absent from the prompts it is given. The result is code that passes the demo and fails the pentest.

This is one finding from a harness that runs continuously. See how Greywatch finds, proves, and fixes them.

How it works →