Skip to main content
← All articles
AISecurity

Is AI-Generated Code Secure? How to Audit It

By Evertech Digital5 min read

"Is AI-generated code secure?" is the wrong question, and it produces a useless answer — sometimes. The useful version is: what does AI-generated code get wrong, predictably, and how do I check?

Because the failures aren't random. A model writes code shaped like the code it learned from, which is the average of everything published online. That average contains a great deal of working software and a great deal of tutorial-grade shortcuts, and the model cannot tell which is which from context alone. It optimises for this runs, not this survives an attacker.

So the flaws cluster. Which means they're auditable — here's the list, in the order we check it.

1. Authorization on every endpoint that takes an ID

This is the big one, and it's worth stating plainly: an app can pass every functional test and still hand one customer another customer's data.

The pattern is an endpoint that verifies you're logged in but never that the record you asked for is yours. Generated code does this constantly, because in the prompt there was only ever one user.

We covered the mechanics and a 60-second self-test in what breaks at 1,000 users. For an audit, the check is mechanical: list every route that accepts an identifier, and confirm each one filters by the current user or team. Not most. Every one.

2. Secrets on the client

Search your built front-end bundle for anything resembling a key. API keys, service tokens, database URLs, admin credentials — all of it ends up in browser-readable JavaScript with alarming regularity, because the model put the call where the data was needed rather than where the secret was safe.

Two rules: secrets live in server-side environment variables, and any key that has ever been in client code is already compromised and must be rotated, not merely moved.

3. Dependencies — including ones that don't exist

Standard checks first: run your package manager's audit, update anything with a known advisory, and remove packages nothing imports.

Then the newer problem. Models sometimes invent package names — plausible-sounding libraries that were never published. Ordinarily the install just fails. The risk is that attackers watch for these hallucinated names and register them, so a package that didn't exist last month resolves today, and installing it runs their code with your build's privileges.

The defence is straightforward but it has to be deliberate: verify that every dependency you didn't personally choose actually exists and is what it claims to be. Check the repository, the download counts, the publish history. A package with a handful of downloads and a two-week history, sitting in a file you didn't write, deserves suspicion.

4. Injection at the string boundary

Generated database code is usually parameterised, which is good. The exceptions cluster in the same places every time: search, filtering, sorting, and anything with a dynamic column or table name. Those get built by string concatenation because parameterisation doesn't cleanly cover them.

Grep the codebase for query strings assembled with template literals or +. Each one is a candidate. The same reasoning applies to shell commands and file paths built from user input.

5. The defaults nobody changed

Fast, high-value, frequently missed:

  • Rate limiting on login, password reset, signup, and anything expensive. Without it, credential stuffing is free.
  • CORS set to a specific origin, not a wildcard copied from a tutorial.
  • Debug and verbose errors off in production. Stack traces are a map of your application.
  • Default or seeded admin accounts removed.
  • Security headers present — HSTS, X-Content-Type-Options, a sensible referrer policy.
  • File uploads validated by type and size, and stored outside the web root.

6. Does it fail closed?

The subtlest category, and the one that separates a real audit from a checklist.

When a permission check errors, does the request get denied or allowed? When the auth service times out, does the middleware block or wave the request through? Generated error handling tends toward keep the app working, which is exactly backwards for a security control.

Read every catch around an authorization or authentication path and confirm the failure mode is denial.

What none of this means

It does not mean don't use AI to write code. We use it daily, and the productivity gain is real. The claim that AI code is uniquely dangerous is mostly stated by people selling something else.

What it means is that generated code needs a security review the same way any code does — and unlike code written by an engineer who was thinking about attackers, none of that review happened while it was being written. The work wasn't skipped maliciously. It was never requested.

A useful frame: treat AI output like a competent contractor's first draft delivered without a spec. Probably fine. Definitely unreviewed.

Doing the audit

Work top-down through the list above; it's roughly ordered by damage-per-hour. Items 1 and 2 are where the company-ending failures live, and both can usually be checked in an afternoon.

Then make it repeatable: dependency audit in CI, secret scanning on commits, and a review step that specifically asks "who is allowed to do this?" for every new endpoint. Once that habit exists, the origin of the code stops mattering — which is the actual goal.

If you'd rather have someone go through this properly, that's work we do. See how we approach AI-powered applications, or send us the repo and we'll come back with a prioritised list of what to fix first.

Ready to build it? Let's talk about your project.

AI Apps & SEO
Ready to build?

Your next digital product
starts here.

Tell us what you're building. We'll respond within 24 hours with honest advice and a clear path forward.

Start my project →

We use cookies to improve your experience on our website. You can accept or decline non-essential cookies. Privacy Policy