Somewhere in your codebase there is a function nobody has ever read. It was generated, it passed the tests, it shipped. Multiply that by a year of fast delivery and you have a system that works, that nobody understands, and whose security properties nobody has ever assessed. That is not a hypothetical failure mode, it is the ordinary result of shipping faster than you review.
The point is not that generated code is bad. Plenty of it beats what a rushed junior writes at two in the morning. The point is that unreviewed code of any origin is unowned, and generated code arrives in volumes that make the review gap enormous rather than annoying. Here is what goes wrong, and how to audit and remediate a codebase you did not write.
What "AI-generated" really changes about your risk
It changes volume, uniformity and ownership, not the vulnerability classes. The bugs are the same ones in the OWASP Top 10 that human teams have shipped for twenty years. What is different is that they arrive faster, they repeat identically across files, and no engineer in the building can explain why any given line exists.
- ▸Volume outruns review. Generation capacity is elastic, human review capacity is not, and a bottleneck under delivery pressure becomes a rubber stamp.
- ▸Errors are correlated, not scattered. A human makes idiosyncratic mistakes. A model repeats the same flawed pattern in every similar file, so one weak validation idiom becomes forty of them.
- ▸Plausibility is not correctness. Generated code is optimised to look like working code. Reviewers relax when code reads well, which is precisely the wrong reflex.
- ▸Ownership evaporates. When something breaks at 3am, the question "why does this do that" has no answer, and the cost lands on incident response rather than on the sprint that saved the time.
The failure modes that show up most often
The recurring ones are boring and dangerous: authorisation checked in the wrong place, secrets pasted into source, input validated for shape but not for authority, and error handling that returns internals to the caller. Generated code is confident about happy paths and vague about adversarial ones.
- ▸Missing object-level authorisation. The code checks that you are logged in, then trusts the identifier in your request. This is the top item in the OWASP API Security Top 10 and it is the single most common finding in generated backends.
- ▸Hardcoded secrets and permissive defaults. API keys in source, debug flags left true, wildcard CORS, storage buckets opened for a demo that became production.
- ▸Injection through convenience. String interpolation into SQL, shell commands or templates, because concatenation is the shortest path to a working example.
- ▸Cryptography by vibes. Recognisable algorithm names used with the wrong mode, a static initialisation vector, or a fast hash where a password hash belongs.
Package hallucination is a supply chain attack surface
Generated code sometimes imports packages that do not exist. The name looks right, the API looks right, the package is fiction. That would be a harmless build failure, except attackers watch for those invented names, register them, and wait for the next developer to install the suggestion without checking.
- ▸The attack is cheap and patient. Register a plausible name, publish something functional, wait. The victim's own tooling supplies the recommendation.
- ▸Typosquatting works the same way. Names that differ by a hyphen, a plural or a transposed letter sit next to the real package in search results and in autocomplete.
- ▸Install scripts run with your privileges. A malicious package does its work at install time, on a laptop or a build runner, both of which hold credentials worth stealing.
- ▸Verify before you install. Check the registry entry, the source repository, the release history and whether anything real depends on it. No history plus a very recent first publish is a red flag, not a lucky find.
- ▸Pin and lock everything. Exact versions, committed lockfiles, integrity hashes. Then scan continuously with something like OWASP Dependency-Check so a newly disclosed flaw in a pinned version reaches you.
Dependency confusion, and why your internal names are not yours
If your build resolves package names from a public registry and a private one at the same time, an attacker who guesses an internal package name can publish it publicly with a higher version number. Many toolchains prefer the higher version. Your build then pulls the attacker's code into your production artefact.
- ▸Internal names leak constantly. They appear in job adverts, stack traces, public repositories, container images and support tickets. Assume they are known.
- ▸Scope your private packages. Use a registry namespace or organisation scope that only your registry can serve, so a public name collision is impossible by construction.
- ▸Configure one resolution path per scope. Never let a single install command consult both registries for the same name. Pin the source, not just the version.
- ▸Proxy the public registry. Route everything through an internal mirror of approved versions, which also gives you one chokepoint for blocking a compromised release.
- ▸Watch the build, not just the app. Build runners with registry credentials and cloud tokens are the real prize. A compromised build produces signed, trusted, malicious output.
If the code drives an LLM, prompt injection is now your problem
The moment your application sends untrusted text to a model whose output triggers actions, you have a new class of vulnerability. Content in a document, an email, a support ticket or a web page can carry instructions the model follows. The model cannot reliably distinguish your instructions from the attacker's.
- ▸Treat every model output as untrusted input. Validate, escape and constrain it exactly as you would a form field from the public internet.
- ▸Never grant the model your privileges. The tools you expose should be narrow, individually authorised and scoped to the acting user, not to a service account with broad rights.
- ▸Put a human in front of consequences. Sending mail, moving money, deleting records and changing permissions need a confirmation a person actually gives.
- ▸Log the whole exchange. Prompts, retrieved context, tool calls and outputs. Without that trail, an incident cannot be reconstructed.
- ▸Work through the OWASP Top 10 for LLM Applications as a design checklist before launch, not as a post-incident reading list.
How to audit a codebase you did not write
Start with an inventory, not with the code. You cannot review what you cannot enumerate, and in a generated codebase the surprises are usually structural: endpoints nobody documented, dependencies nobody chose, and configuration nobody set deliberately. Two weeks of mapping saves two months of scattered fixing.
- ▸Generate a software bill of materials. Every component, every version, every licence, in a machine-readable format emitted by the build. CISA guidance on SBOM explains why buyers increasingly ask for one.
- ▸Enumerate the real attack surface. Every route, every queue consumer, every scheduled job, every file upload, every outbound call. Compare that list to the documentation and expect a gap.
- ▸Map trust boundaries. Where does user-controlled data enter, and what is the first line that trusts it? That question finds most of the serious defects.
- ▸Run the automated pass first. Static analysis, dependency scanning, secret scanning across full history. It is noisy, but it is cheap and it triages the manual effort.
- ▸Then read the dangerous parts by hand. Authentication, authorisation, payments, file handling, anything touching personal data. Tools do not find missing authorisation, only humans who understand the domain do.
The remediation order that actually reduces risk
Fix by exploitability, not by scanner severity. Secrets first, because they are already leaked. Then authorisation, then injection on internet-facing routes, then dependencies, then everything else. Resist the urge to rewrite, because a rewrite delays every one of those fixes.
- ▸Rotate every secret you find, immediately. Removing a key from source does not remove it from history, from forks or from whatever already copied it. Assume compromise and rotate.
- ▸Centralise authorisation. One enforcement point, called by every route, tested independently. Fixing forty inconsistent checks one by one guarantees you miss some.
- ▸Remove the dependencies you do not use. The fastest reduction in supply chain exposure is deleting packages nobody imports.
- ▸Add tests as you fix. A regression test for each vulnerability is what stops the next generated patch from reintroducing it.
- ▸Decide honestly what to keep. Some modules are cheaper to replace than to understand. Our guide to migrating off SaaS to custom software covers the same replace-or-repair judgement.
Rebuild the process so it does not happen again
Keep the tooling, change the pipeline. Generated code is fine when a named engineer signs off, an automated gate blocks the known failure classes, and the codebase stays small enough that a human can still hold it in their head. The discipline goes in the pipeline, because discipline that lives in good intentions does not survive a deadline.
- ▸A human owner per merge, no exceptions. Someone puts their name on it and can explain what it does next year.
- ▸Automated gates on every pull request. Static analysis, dependency and licence checks, secret scanning, and a rule that fails a build introducing a package that is not in your allowlist.
- ▸Regenerate the SBOM on every build. An inventory produced once is a document. Produced on every build, it is a control.
- ▸Design for the regulators you now have. The EU Cyber Resilience Act places obligations on products with digital elements sold into the EU, including vulnerability handling and component transparency. "The tool wrote it" is not a defence.
- ▸Keep the system small. Fewer services, fewer dependencies, fewer clever abstractions. Our notes on what website maintenance should actually include and on leaving WordPress without losing your SEO both come back to the same principle: you can only secure what you can still explain.
How TuniCyberLabs helps
We audit and remediate codebases that outgrew their review process, generated or not: an SBOM and a real attack surface map, findings ranked by exploitability rather than by scanner colour, hands-on remediation of authorisation, secrets and dependency risk, and a build pipeline with gates that hold after we leave. We work across the EU and North Africa, and we hand the system back documented.
Send us the repository and we will tell you what is actually in it: see how we work.
