AI coding tools often produce insecure defaults when the prompt and the checks that pass it reward visible function but omit the trust boundary. A feature that trusts the browser and one that verifies payment on the server can render the same pixels, pass the same click-through, and return the same 200 OK. The demo cannot distinguish them. Six holes recur: secrets that reach the browser (CWE-798), authorization without an ownership check (CWE-639), injection and abuse of unfiltered input (CWE-89, CWE-20, CWE-918), prompt injection (CWE-1427), denial-of-wallet (CWE-400), and packages the model invented or left stale.
That explanation is supported by controlled generation benchmarks and by recurring patterns in real codebases, but the two evidence sets answer different questions. A benchmark can attribute output to a model under a defined prompt. The AxonBuild corpus can show what existed in 26 selected apps; it cannot prove which tool, prompt, later human edit, or deployment decision caused each finding.
Why AI coding tools ship security holes
AI coding tools ship security holes when security requirements are absent from the task and the visible result can succeed without them. This is a recurring default, not a universal outcome. Explicit constraints, secure framework primitives, tests, and review can change what ships.
The 2025 Veracode GenAI Code Security Report tested more than 100 models on 80 function-completion tasks across four languages and four vulnerability classes. The prompts gave no security-specific guidance. Across models and tasks, 45% of generations chose an implementation the study’s static analysis classified as insecure. Veracode’s Spring 2026 update expanded the longitudinal set to more than 150 models: overall security pass rate remained 55%, while syntax pass rates had risen from about 50% in 2023 to above 95%.
Those are controlled function-completion results, not rates for complete products, autonomous coding agents, or deployed applications. A separate 2026 study of real vibe-coded applications used agent-assisted auditing with human validation and reported recurring problems such as placeholder logic, unfiltered input, and secret exposure. Its authors associated the patterns with memory loss, locally optimized objectives, and insufficient security knowledge, while noting that stronger models and prompting reduce risk without eliminating it (Deng, Fan, and Meng, 2026).
The AxonBuild corpus adds a smaller, descriptive view of working codebases. Across a fixed June to July 2026 cohort of 26 real AI-built apps, 22 carried at least one confirmed critical finding and none landed in the historical green band. The automated audit engine produced candidates; final findings were verified against the code. The apps were neither random nor representative, so these numbers do not estimate how often all AI-built apps are insecure.
Four reasons the check goes missing
Four mechanisms sit underneath the missing control, and they stack.
- It repeats insecure patterns from its training data. Public code is full of string-concatenated SQL and unescaped template output. When those patterns are common, the most probable next token is the unsafe one.
- It optimizes for the visible result the prompt asked for. “Let users download their invoice” is satisfied by a working download. Nothing in that sentence asks whose invoice.
- It omits controls nobody named. Rate limits, ownership checks and token caps are not things a demo misses, so they are not things a prompt usually mentions.
- It writes a subtly wrong check rather than no check. A role equality test where a membership test was needed passes a glance-level review:
user.role === 'admin'silently fails the user who holds admin alongside another role, and the inverse mistake grants access to a role string that merely contains the word.
The shared condition underneath all four is an unnamed trust boundary. If the task never says which inputs are untrusted or where a decision must be enforced, the model has no way to know which side of the wire the check belongs on.
When the controls drift
A control that was correct in month one can be gone by month six. Each AI edit rewrites a file with only partial memory of why a line was there. A middleware check gets rewritten out of a route, a policy gets dropped when a table is recreated, a server-only variable moves into a client component. Nothing fails visibly, because the happy path still works. The slow accumulation is what security teams call security debt, and its shape here is architectural drift: the app still has the architecture you designed, minus the parts no test pins down. The fix is boring. Write one failing test per control, so the control has to survive the next edit.
The security risks of AI coding tools: lab rate vs. shipped rate
The security risk of AI-generated code shows up in different measurements. Veracode measured secure choices on controlled function-completion tasks. AxonBuild recorded confirmed findings in a selected set of working applications. The first supports claims about model output under a defined prompt; the second describes what a fixed cohort contained after product development.
Veracode’s study scores a single generated function with static analysis. It does not test a complete application’s authentication, authorization, deployment, or business rules. Whether an insecure choice survives into a working product also depends on later prompts, framework defaults, tests, human edits, and review.
The 2025 report’s per-class spread is useful. SQL-injection tasks passed about 80% of the time, while cross-site-scripting and log-injection tasks passed about 14% and 12%. The report explains that the latter classes require context about which data is untrusted, context deliberately missing from its single-function tasks. This supports a narrower conclusion: incomplete context makes some security decisions harder for a model than a well-known local code pattern.
The fixed corpus provides an app-level descriptive view with explicit denominators. The full vibe coded apps security statistics owns those historical aggregates and their limitations.
| External study | AxonBuild fixed cohort, June–July 2026 |
|---|---|
| 45% of Veracode function-completion tasks chose an insecure implementation (100+ models, 2025) | 22 of 26 apps had at least one confirmed critical finding; 0 of 26 landed green under the historical method |
| Tenzai found 69 vulnerabilities in 15 apps produced by five agents from matched prompts (December 2025) | 7 of 21 third-party apps let a logged-in user read or write another customer's data |
| Veracode measured four code-level vulnerability classes | 6 of 21 third-party apps shipped a real secret, while secrets still had the strongest historical area average |
| OWASP documents prompt injection and unbounded consumption in LLM applications | 12 of 14 third-party AI apps had a denial-of-wallet path; 8 of 14 had a prompt-injection path |
The two columns should not be combined into one rate. They do point to the same practical testing gap: local patterns such as a literal key can be found with a search, while authorization and business rules require the missing identity and workflow context to be supplied deliberately.
Which languages and stacks come out worst
Java comes out worst in the lab, Python comes out worst in real repositories, and the two rankings measure different things. Veracode’s 2025 report splits its security pass rates by language. A separate study scanned real public repositories instead. Schreiber and Tippe ran CodeQL over 7,703 AI-attributed files from public GitHub repositories and found 4,241 CWE instances across 77 distinct weakness types, with 87.9% of files carrying no CWE-mapped finding at all (Schreiber and Tippe, 2025).
| Language | Veracode pass rate, single functions (higher is better) | Public repo files with a CWE finding (lower is better) |
|---|---|---|
| Python | 62% | 16.18% to 18.50% |
| JavaScript | 57% | 8.66% to 8.99% |
| C# | 55% | not reported |
| Java | 29% | not reported |
| TypeScript | not reported | 2.50% to 7.14% |
The columns run in opposite directions, so do not read them as one ranking. The left column is about how hard a language makes a given security decision under a fixed prompt. The right column also reflects what people actually build in each language, and its file attribution is lopsided: ChatGPT accounted for 91.52% of the analyzed files, GitHub Copilot 7.50%, Amazon CodeWhisperer 0.52%, Tabnine 0.46%. What GitHub Copilot’s plan settings do and do not cover is the per-tool version of the same question.
For the stack most readers here ship, TypeScript’s low file rate is not reassurance. A type system does not know which user owns a row. Cross-tenant reads, missing rate limits and denial-of-wallet are all type-correct.
Why the headline numbers disagree: 45%, 62%, 87.9%
The three numbers do not contradict each other. Each one counts a different unit, and the unit is the whole story.
- 45% is the share of Veracode’s single-function completions where the model chose an implementation static analysis rated insecure. The unit is one generated function, judged against a defined vulnerability class.
- 62% is the most quoted and the most misread. It is usually repeated as the share of AI-generated code containing design flaws or known vulnerabilities. In the source, BaxBench, 62% is the best model’s correctness score across 392 backend application tasks, not a vulnerability rate. The security result in that paper is separate and worse: the authors report successfully executing exploits against around half of the functionally correct programs each model produced (Vero et al., 2025). The unit is a whole backend application, attacked rather than scanned. (Python’s 62% in the table above is a different 62%, and a coincidence.)
- 87.9% is the share of AI-attributed public repository files where CodeQL flagged nothing. The unit is one file in a real repository, judged by one scanner’s rule set.
Line them up and they tell one story. Most individual files are clean, because most files do nothing security-relevant. Any single security decision is close to a coin flip. A whole application, attacked instead of scanned, fails about half the time. The number gets worse as the unit gets closer to a shipped product, which is the unit you actually care about.
The trust boundary the browser never draws
A permission rule has to be enforced at a trusted boundary. Depending on the architecture, that may be an application server, a database policy, or a provider-side verification step. The browser is controlled by the user, so a browser-only rule cannot protect paid access or another customer’s records.
// This runs in the browser, where anyone can edit it:
if (session.paid) unlock("pro");
That line can make the interface look correct while letting a user change the local value. The protected version derives entitlement from a trusted payment record or verified provider event. Both versions can produce the same screenshot.
| Looks fine | Actually fine |
|---|---|
| The feature works and the demo runs clean | The check that gates it runs at a trusted server, database, or provider boundary |
| The API key is in an environment variable | That variable is server-only: it never ships in a client-exposed prefix such as NEXT_PUBLIC_ or in a client file |
| The endpoint returns the right data for me | A second account, logged in separately, can’t read the first account’s rows by changing an ID |
The guarded version of a feature and the open version can render identical pixels. The difference sits at the trusted boundary, outside a screenshot of the happy path.
The six holes AI coding tools ship by default
Six classes recur across the controlled studies and the AxonBuild cohort. They are grouped by mechanism rather than ranked. Each can leave the expected interface intact, which is why a happy-path walkthrough alone provides weak evidence. Vibe coding security across the historical twelve areas owns the cohort-level risk ranking.
Secrets that reach the browser
Secrets averaged 84.4 out of 100 across the 21 third-party apps, the strongest historical area in this cohort. Six of the 21 still shipped a real secret, including three left in git history. Removing a current file does not revoke a credential already exposed; it must be rotated. Supabase explicitly says its legacy service_role and current secret keys must stay on a trusted backend because they can bypass row-level security (Supabase data-security guidance).
Authorization without an ownership check
Authorization depends on identity, ownership, role, and the requested action. A generated endpoint may authenticate the user correctly and still omit the ownership decision. In the fixed cohort, 7 of 21 third-party apps let a logged-in user read or write another customer’s data.
The standard names matter here, because they are what a scanner rule or a pentest report will call it. The category is broken access control, ranked first on the OWASP Top 10 for 2025. Its most common shape is an insecure direct object reference, IDOR: the record ID in the URL is the only thing deciding what comes back. MITRE catalogs that as CWE-639, authorization bypass through user-controlled key, and the broader parent as CWE-284, improper access control.
The finding that stuck with me came from an AI cloud coding workspace. Its row-level security was genuinely correct; I checked the policies and they held. Then I read the database helper functions. Seven of them were declared SECURITY DEFINER, so they ran with the function owner’s privileges. In this database, that owner could bypass row-level security entirely. Several functions took a user ID as a plain argument without ever comparing it to the caller’s real identity. The shape looks like this:
-- In this audited repo, the function owner bypassed RLS.
create function get_project_chats(p_project uuid, p_user uuid)
returns setof chats
language sql
security definer
as $$
select * from chats where project_id = p_project;
-- p_user is never compared to auth.uid().
-- The caller decides who they are.
$$;
Any free signup could read another customer’s private AI chats or soft-delete projects. The correct policies were routed around, and a static linter would have to understand the missing identity comparison to flag the actual authorization failure.
In December 2025, Tenzai gave five coding agents (Claude Code, Codex, Cursor, Replit, and Devin) matched prompts and inspected 15 resulting apps, a study covered by CSO Online. The vendor reported 69 vulnerabilities, with recurring failures in API authorization and business rules. Tenzai used its own testing agent, so the study is useful direct evidence with a commercial-source limitation rather than an independent population estimate.
Injection and abuse
Input and abuse controls require failure-path tests. The root cause has a standard name, missing input validation (CWE-20), and it is the class most of the others in this section descend from. A query concatenated from user text can create SQL injection (CWE-89), unescaped text rendered back into a page can create cross-site scripting (CWE-79), and a bare fetch(userUrl) can produce server-side request forgery (CWE-918) if the caller controls the destination. In the fixed cohort, 13 of 21 third-party apps had no rate limit on their most expensive endpoint, and 11 of 21 had at least one unauthenticated endpoint doing privileged work, which is CWE-306, missing authentication for a critical function. Happy-path testing would not exercise those attacker-controlled inputs.
Prompt injection
If an app mixes untrusted text with model instructions, a user may be able to steer the model away from the intended policy. OWASP’s 2025 LLM risk list places prompt injection first. Eight of the 14 third-party corpus apps with an AI surface had a live prompt-injection path, including a compliance tool whose output a visitor could manipulate into declaring content compliant.
Denial-of-wallet
An LLM endpoint with no trustworthy caller limit, usage quota, or provider cap can expose a metered API to repeated spending. OWASP documents this class as unbounded consumption, and MITRE as CWE-400, uncontrolled resource consumption. In the fixed third-party cohort, 12 of 14 apps with an AI surface had a confirmed denial-of-wallet path. The OWASP AI Top 10 guide maps those provider-neutral risk labels to practical checks without replacing the primary OWASP definitions.
Packages the model invented or left stale
The first five holes are all first-party code. This one is not, and it is the class a founder is least likely to look at. Three separate failures live here.
Packages that do not exist. A model can confidently import a package name it made up. Spracklen and colleagues generated 576,000 code samples across 16 code-generating models and found hallucinated package references at a rate of at least 5.2% for commercial models and 21.7% for open-source ones, producing 205,474 unique fake package names (Spracklen et al., 2024). The rate has come down on newer models but has not gone away: a 2026 re-run across five frontier models released between October 2025 and March 2026 measured 4.62% to 6.10%, and found 127 package names that all five models invented identically, 53 of which were still free for anyone to register (Churilov, 2026). That last number is the attack. Register the name the models keep inventing, and you own a dependency in every app that trusts the suggestion. The technique is called slopsquatting, the AI-shaped version of typosquatting.
Packages pinned to a version with a known advisory. A model’s knowledge stops at its training cutoff. It will happily pin the version it learned, which may have picked up a CVE since. MITRE catalogs the result as CWE-1395, dependency on a vulnerable third-party component, and the abandoned-library variant as CWE-1104, use of unmaintained third-party components. Nothing in the build warns you, because the pin is valid.
More dependencies than the feature needs. Asked for a small feature, a model often reaches for a library rather than ten lines. Each one adds a transitive tree you did not choose and will not read. Every added package is another maintainer who can be compromised. Whether the app needs it depends on where the code imports and uses it, not merely on its presence in a manifest.
None of these are visible from the interface. package.json shows direct declarations. The lockfile shows the resolved direct and transitive versions. Imports and the built bundle show whether the app uses a package. Registry ownership and version history provide provenance signals, while an advisory scan checks known vulnerabilities. Each artifact answers a different part of the dependency question.
AI-generated code security vulnerabilities, by CWE class
Fourteen CWE entries cover the AI-generated code security vulnerabilities named in this post. The mapping is not one-to-one: injection alone spans five classes.
Every hole above has a standard identifier. Security tools, pentest reports and compliance questionnaires speak in those identifiers, not in the plain-English names used in this post, so here is the mapping. The names are MITRE’s official CWE entry names.
| Hole in this post | CWE | Official CWE name | What it looks like in an AI-built app |
|---|---|---|---|
| Secrets that reach the browser | CWE-798 | Use of Hard-coded Credentials | A service_role or sk_live_ key in the client bundle or in git history |
| Authorization without an ownership check | CWE-639 | Authorization Bypass Through User-Controlled Key | Changing the ID in /api/invoices/123 returns someone else’s invoice |
| Authorization without an ownership check | CWE-284 | Improper Access Control | The parent class, first on the OWASP Top 10 for 2025 |
| Unauthenticated privileged endpoint | CWE-306 | Missing Authentication for Critical Function | An admin or billing route with no session check at all |
| Injection and abuse | CWE-89 | Improper Neutralization of Special Elements used in an SQL Command (‘SQL Injection’) | A query built by string concatenation from user text |
| Injection and abuse | CWE-79 | Improper Neutralization of Input During Web Page Generation (‘Cross-site Scripting’) | User-supplied text rendered back into a page unescaped |
| Injection and abuse | CWE-20 | Improper Input Validation | The root cause under most of this row: nothing checks the input’s shape |
| Injection and abuse | CWE-918 | Server-Side Request Forgery (SSRF) | fetch(userUrl) where the caller picks the destination |
| Injection and abuse | CWE-117 | Improper Output Neutralization for Logs | Raw user text written into logs, the worst-performing class in Veracode’s benchmark |
| Weak crypto | CWE-327 | Use of a Broken or Risky Cryptographic Algorithm | A hand-rolled hash or an outdated algorithm for passwords or tokens |
| Prompt injection | CWE-1427 | Improper Neutralization of Input Used for LLM Prompting | Untrusted text concatenated into the system prompt |
| Denial-of-wallet | CWE-400 | Uncontrolled Resource Consumption | A metered AI endpoint with no per-user cap |
| Packages the model invented or left stale | CWE-1395 | Dependency on Vulnerable Third-Party Component | A pinned version that picked up a CVE after the training cutoff |
| Packages the model invented or left stale | CWE-1104 | Use of Unmaintained Third Party Components | A library the model learned in 2024 that nobody maintains now |
Two of these deserve a note. CWE-918 was folded into broken access control in the OWASP Top 10 for 2025, so a report may file an SSRF finding under A01 rather than on its own. And CWE-1427 is recent, so an older scanner may have no rule for prompt injection at all.
Does your platform’s security scan already cover this?
Partly, and which part depends on the scanner category. A platform scanner can confirm that a protection exists: row-level security enabled, an auth check present, a policy attached. Whether a logged-in customer can read another customer’s invoices is a question about behavior, and the direct test is to log in as two people and try. Four tool categories cover this ground, and each has a structural blind spot rather than a quality problem.
| Category | What it sees | What it structurally cannot see |
|---|---|---|
| SAST (static analysis: CodeQL and similar source scanners) | Dangerous patterns in source: concatenated SQL, unescaped output, a hard-coded key, an unsafe crypto call | Whether the ownership check that is missing should have been there. It has no model of who owns which row |
| SCA (dependency and supply-chain scanning) | Direct and transitive packages matched against advisory databases, plus license issues | A package that does not exist yet on the registry, and whether the app needed the dependency at all |
| Secret scanning | Credential-shaped strings in the repo, in git history and often in the built bundle | Whether an exposed key was ever rotated. Deleting the file does not revoke the credential |
| DAST and active testing | Behavior of the running app: injection payloads, exposed endpoints, missing auth on a route | Anything requiring two real accounts and a business rule, unless you give it those accounts and tell it what “wrong” means |
Read down the right-hand column and the same three gaps appear in every row: cross-tenant reads, business rules, and denial-of-wallet. All three need identity and intent supplied from outside the tool. A credible review tests relevant trust boundaries with separate accounts instead of reading the configuration and calling it covered.
The gap has also appeared in public disclosure. In a scan completed on March 21, 2025, Matt Palmer and Kody Low analyzed 1,645 projects listed on Lovable Launched. Their homepage-only method found 303 endpoints across 170 projects with inadequate row-level-security settings. The scope did not include login-protected areas or deeper crawling, so the result applies to that listed-project sample and method. The CVE record describes unauthenticated database access and records Lovable’s dispute over platform responsibility.
What to check today
Five focused checks can expose serious gaps, though passing them cannot clear the rest of an app. Use a non-production environment or disposable test data for destructive cases. This is one slice of whether your AI-built app is ready to launch. The same trust-boundary logic runs underneath the data loss bugs in an AI-built app and where your checkout leaks money.
-
Search your built bundle for
service_role,sk_live_, and any provider API key. Anything that appears there is public. Revoke or rotate it immediately. Remove it from client-delivered code. Add a build check that fails if a provider key appears in the client bundle. -
Pick one endpoint that costs you money (an AI call, an email send, a document generation) and confirm it rejects a request with no logged-in session.
-
Create two accounts. Log in as the first and try to read the second one’s data by changing an ID in a URL or an API call. If it returns rows, that’s the leak.
-
On any feature that calls an LLM, confirm there’s a per-user rate limit and a token cap, so a loop can’t run up the bill.
-
Open
package.jsonand the lockfile. Check direct declarations, resolved transitive versions, registry ownership and version history, and whether the code actually imports each direct package. Then runnpm auditfor known advisories. A new package or one with little download history deserves closer provenance review; neither signal proves slopsquatting by itself.
Each check needs a trusted enforcement point and an observed failure path. For isolation, use a second account and confirm both reads and writes are denied. For paid integrations, test the unauthenticated and over-quota cases. Generated code can include these controls, but their presence has to be verified in the deployed path.
Common questions about AI coding tools and security
Do AI coding tools write insecure code?
Yes. Veracode’s defined benchmark found an insecure implementation in 45% of function-completion tasks across more than 100 models. That result applies to its prompts, languages, and four tested vulnerability classes. In the fixed AxonBuild cohort, recurring findings included authorization and AI-cost abuse, but the cohort cannot attribute each finding to the coding tool alone.
Is AI generated code secure?
AI-generated code can be secure, but a successful demo does not establish that. Under Veracode’s no-security-guidance benchmark, 45% of generated functions chose an insecure implementation. In the fixed AxonBuild cohort, 22 of 26 apps carried at least one confirmed critical finding. Neither number is a universal probability for a new app.
What percentage of AI-generated code has security vulnerabilities?
There is no single percentage, because the published studies count different units. Veracode found an insecure implementation in 45% of single-function completion tasks, a CodeQL scan of 7,703 AI-attributed public repository files found 87.9% of files carried no CWE-mapped finding, and the BaxBench authors successfully exploited around half of the functionally correct backend applications the models produced. The closer the unit gets to a whole shipped application, the worse the rate looks.
Which security vulnerabilities are most common in AI-generated code?
The available studies do not establish one universal prevalence order because they test different populations, units, vulnerability sets, and denominators. In AxonBuild’s fixed cohort, 7 of 21 third-party apps let one logged-in customer read another customer’s data. That supports checking object ownership in that cohort; it does not rank broken access control against hard-coded credentials, missing authentication, injection, prompt injection, or uncontrolled spending across all AI-generated code. Evaluate each named class against the app’s actual data and privileged paths.
Does AI-generated code pull in fake or outdated dependencies?
Yes, both. Across 576,000 code samples from 16 code-generating models, researchers found hallucinated package references at a rate of at least 5.2% for commercial models and 21.7% for open-source ones, and a 2026 re-run across five frontier models still measured 4.62% to 6.10%. Models also pin the versions they learned before their training cutoff, so a valid-looking pin can carry a CVE published since. Attackers register the invented names in advance, a technique called slopsquatting.
Is AI-generated code less secure than human-written code?
Not inherently. The difference is volume against a fixed review capacity: Veracode’s longitudinal set shows syntax pass rates rising from about 50% in 2023 to above 95% while the security pass rate stayed at 55%, so far more code ships per hour and the same share of it carries a flaw. What does look distinctive in AI-built apps is the business-logic and authorization bug, the kind no scanner flags and no demo shows.
Which AI coding tool is most secure?
The AxonBuild corpus cannot answer that because per-tool attribution is not coded and later edits are mixed into the repositories. Veracode’s study compares models on function-completion tasks rather than end-to-end coding products. Tenzai compares five agents on 15 matched builds, but the small vendor-run benchmark is not enough for a durable universal winner. Choose against your own stack and workflow, then test the resulting application.
Is JavaScript or Python worse for AI-generated code security?
It depends which measurement you use, and the two available ones disagree. In Veracode’s single-function benchmark, Python scored best of the four languages tested with a 62% security pass rate, JavaScript 57%, C# 55% and Java worst at 29%. In a CodeQL scan of real public repositories the order flips: 16.18% to 18.50% of Python files carried a CWE finding, against 8.66% to 8.99% of JavaScript files and 2.50% to 7.14% of TypeScript files. Neither ranking makes a language safe, because cross-tenant reads and missing rate limits are type-correct in all of them.
Will a security scanner catch these holes?
Some of them. A static scanner is strong on presence: a key in the bundle, a table with row-level security switched off, or a dependency with a known advisory. Cross-tenant reads and business-rule failures require behavior tests. An active testing tool may run those tests when it has suitable accounts and authorization; a package or source-pattern scan alone cannot.
How do you secure AI-generated code?
Test the failure paths instead of the demo. Log in as a second account and try to read the first account’s data, call every expensive endpoint with no session, search the built bundle for real secrets, put a per-user rate limit and a token cap on anything that calls an LLM, and confirm every package in package.json exists on the registry and carries no known advisory. Then pin each control with a failing test, so months of later AI edits cannot quietly remove it.
A general answer cannot judge your own app.
See how we confirm what an app actually allows before deciding whether something needs to change.