AI-written code is safe in production when an engineer reads every line before it ships, and measurably unsafe when nobody does. Veracode's 2025 GenAI Code Security Report tested more than 100 models across 80 coding tasks and found that 45% of generated samples introduced a weakness from the OWASP Top 10, with AI-produced code carrying roughly 2.7 times more vulnerabilities than human-written code. In the same dataset, syntax pass rates climbed from about 50% in 2023 to around 95%, while security pass rates stayed flat between 45% and 55%. Models learned to write code that runs. They did not learn to write code that holds.
I sell AI-assisted builds in Dubai, so read this as an interested party being specific rather than as a neutral survey. What follows is the review we run before a build touches customer data, written out so you can apply it yourself or use it to interrogate whoever is building for you, us included.
What the measurements say about AI-written code
Veracode's 2025 report is the widest sample. Across Java, JavaScript, Python and C#, 45% of samples failed security testing, with Java worst at a 72% failure rate. Cross-site scripting was the standout: the models failed to defend against CWE-80 in 86% of relevant samples. SQL injection, weak cryptography and log injection followed.
Georgetown's Center for Security and Emerging Technology published its brief on the cybersecurity risks of AI-generated code in November 2024. Evaluating five large language models, CSET found close to half of the code snippets produced contained bugs that were often impactful and open to exploitation.
For code that reached real repositories rather than test harnesses, there is the empirical study of Copilot-generated code (arXiv 2310.02059, later in ACM TOSEM). Static analysis over 435 Copilot-generated snippets pulled from public GitHub projects found 35.8% carried a CWE instance across 42 distinct weakness types, eight of them in the 2023 CWE Top 25. A small share carried hard-coded credentials, which sounds minor until you remember one leaked key is enough.
Maintainability degrades separately and no security scanner reports it. GitClear analysed 211 million changed lines from 2020 to 2024 across private repositories and 25 large open-source projects. During 2024 they tracked an eight-fold increase in blocks of five or more lines duplicating adjacent code, while refactored or moved code fell from about 25% of changed lines in 2021 to under 10%. Duplication is where a security fix gets applied in three places and missed in the fourth.
Supply chain sits alongside that. Endor Labs, in its 2025 State of Dependency Management report, found 34% of dependencies suggested by AI coding assistants were hallucinations that exist in no public registry, and around 80% carried some form of risk. The technique has a name now, slopsquatting: register the package the model keeps inventing, then wait for someone to run install.
AI-generated code fails security tests at roughly the same rate it did in 2023. Veracode's 2025 GenAI Code Security Report puts 45% of samples in breach of the OWASP Top 10 and about 2.7 times more vulnerabilities than in human-written code. Georgetown's CSET brief (November 2024) found close to half the snippets from five LLMs contained exploitable bugs. The mitigation that works is line-by-line human review before deployment, not a better prompt.
Developers are not fooled in aggregate. Stack Overflow's 2025 Developer Survey put AI usage at 84% of respondents, up from 76% the previous year, while trust in the accuracy of AI output fell to 29% and 46% said they actively distrust it. Two thirds reported spending more time fixing code that is almost right. Snyk's developer research points the other way on one specific question: most developers believe AI-generated code is more secure than the measurements show. High usage, low general trust, and an optimism bias about security. That combination is what produces an unreviewed deployment.
Why models write holes
A model completes the most plausible continuation of your prompt. Your prompt asked for a feature, not for a control. So you get a working endpoint that returns the invoice, and nothing anywhere decides whether the person asking is allowed to see that invoice. The code is correct against the request and wrong against the business.
Training data is public code, and public code carries decades of insecure patterns that ran fine at the time: string-concatenated SQL, permissive CORS, credentials in a config file that was never meant to leave a laptop. The model cannot know which of those examples was patched later.
Models also have no view of your runtime. They do not know your table has four million rows, that the endpoint is public, or that this field holds a passport number. Each of those facts changes what correct means, and none of them are in the prompt. So the happy path gets handled and the unhappy path gets guessed: ask for error handling and you frequently get a catch block that logs and continues, turning a failed payment write into a silent success. When the model does not know a library, it confidently names one that sounds right, because a plausible package name is a plausible continuation.
The defect classes you can see with your eyes
You do not need a security background to catch most of what we strike out.
Hard-coded secrets. An API key or database password sitting in the source. It works, tests pass, and the key now lives in your git history forever, including in every clone.
Missing validation at the boundary. A quantity field accepting negative numbers, an upload accepting a 400MB file. Anything arriving from a browser, a webhook or a third-party API is untrusted until checked.
SQL injection and cross-site scripting, both decades old and both still the most common weaknesses in AI output. Look for any query built by joining strings, and any place where user-supplied text lands in a page without escaping.
Insecure defaults. CORS allowing every origin, debug mode left on in production, a storage bucket made public because that fixed the upload, an admin role assigned by default because that made the test pass.
Dependencies that do not exist, or exist and are not what the model thought. Given the Endor Labs finding, every new package name in a diff needs verifying against the real registry.
Swallowed errors. A try block wrapping a payment write with an empty catch. The customer sees a confirmation, your database has no record, and the gap surfaces at month end when someone reconciles by hand.
Missing limits and pagination. A list endpoint with no page size, a query with no LIMIT, a job with no rate cap. That is a denial-of-service surface and an infrastructure bill at once: a query scanning a whole table on every page load is invisible at 500 rows and expensive at 500,000.
Personal data in logs. Full request bodies written to the log stream because it helped during debugging. Phone numbers, ID numbers and card fragments end up in a log aggregator that the compliance conversation never covered.
The review checklist, layer by layer
We run these nine layers on every build, in this order, because each one assumes the layer above it is clean.
| Layer | What the human checks | Typical defect | Cost in production | |---|---|---|---| | Secrets and keys | No credentials in source or git history; everything from environment variables; keys scoped and rotatable | Key committed during a quick fix | Full credential rotation, plus whatever was accessed | | Authentication and authorisation | Every endpoint checks who owns the record before returning it; roles enforced server-side | Any logged-in user reads any record by changing an ID | Data exposure with no log trail | | Input validation | Types, ranges, lengths and file limits at every boundary; parameterised queries; output escaping | Negative quantity, oversized upload, injected script | Injection, corrupt records, crash | | Error handling | Failures raise, get logged with context and stop the flow; no empty catch blocks | Silent write failure on a payment path | Money taken with no record of it | | Dependencies | Every package exists, is the intended one, has a maintained release and a pinned version | Hallucinated or typosquatted package | Malicious code inside your build | | Database migrations | Every migration has a tested rollback; no destructive drops without a backfill | Irreversible migration run against live data | Data loss and downtime | | Limits, pagination and cost | Page sizes capped, queries bounded, indexes present, rate limits on public endpoints | Unbounded list query on a growing table | Slow app, then a cloud bill nobody modelled | | Logging and personal data | What is logged, where it is stored, how long it is kept, who can read it | Full request bodies containing personal data | Breach exposure and a compliance problem | | Tests | Critical paths covered, including the negative case | Only the success case is tested | The failure everyone assumed could not happen |
Two of those deserve more than a table row.
Authorisation is where AI code fails most consistently, and no test suite catches it by accident. A normal test asserts that the correct user succeeds. The vulnerability lives in the case nobody wrote a test for, which is whether the wrong user fails. If your review does one thing, make it this: take every endpoint, name the roles that should reach it, and read the code enforcing that. When a vendor says they used AI to build the thing, ask which line rejects the wrong user. A vague answer means the check does not exist.
Migrations get skipped because they are boring. A migration that drops a column, changes a type or renames a table is a one-way door if nobody wrote the reverse. Models write the forward step well and the rollback inconsistently. Run every migration against a copy of real data before it goes near the live database, and confirm you can go back.
What automation catches, and what only a person catches
Scanners are worth running and they will not save you. Secret scanning finds credentials matching known key formats, dependency scanners find known CVEs and packages that fail to resolve, static analysis with taint tracking finds a fair share of injection paths, and linters catch swallowed exceptions. All of that is cheap and belongs on every commit.
What the machine cannot decide is whether the rule is the right rule. A scanner sees an authorisation check and marks the endpoint protected. It cannot know that in your business a branch manager may see their own branch and not another one, because that fact lives in your head and in nobody's code comments. It cannot judge whether a field holding an Emirates ID should be stored at all, or that a migration is technically reversible and operationally catastrophic on a Thursday evening. Tooling handles the known-pattern half and a person handles the context half.
How long review takes, and what a miss costs
On our builds, review runs at about 20% to 30% of build time. On a two-week project that is two to three working days, spread through the build rather than saved for the end, because a design flaw found on day nine costs more than the same flaw found on day two. A change of around 300 lines takes 30 to 60 minutes to read properly. Anyone claiming to review a large diff in ten minutes is skimming, and skimming finds formatting problems rather than access control problems.
Remediation prices the alternative. Rescue and hardening work in Dubai runs at reported contractor rates of AED 300 to 500 an hour, and a security cleanup on a small application typically needs 20 to 40 hours, so AED 6,000 to 20,000. That figure assumes nothing has happened yet. It buys making a shipped-unreviewed build safe after the fact, with no incident to clean up.
The quieter failures cost more because they run for weeks before anyone notices. On our own studio system a synchronisation gap between the payment side and the CRM left about AED 15,000 of received payments unmatched to their deals for a month. Nothing was hacked and no data leaked. A silent failure on a write path simply meant the numbers we were making decisions on were wrong. That sits in the error-handling row above, and it is why empty catch blocks get struck out on sight.
Then there is the bill nobody budgets. An unbounded query against a growing table is fine in testing and expensive in month four, when the same page load reads a hundred times more rows.
The UAE layer: who carries the liability
This gets skipped in most vendor conversations and it belongs in the contract.
The UAE Personal Data Protection Law, Federal Decree-Law No. 45 of 2021, has been in force since January 2022, with implementing regulations issued since and enforcement sitting with the UAE Data Office. If your entity is licensed inside DIFC you fall under DIFC Data Protection Law No. 5 of 2020 and its Commissioner instead. ADGM runs its own Data Protection Regulations 2021 with its own Office of Data Protection. Three regimes, one city, and which applies depends on where your entity is licensed rather than where your server sits.
Practical consequences for a build: know your jurisdiction before you design the data model; name the cloud region in writing so everyone knows where the data physically lives; check whether your sector adds a residency expectation on top of the general law, as health data does. Then write into the contract who is controller and who is processor, what the vendor may access after handover, what happens to that access when the project closes, and who notifies whom after an incident.
The blunt part: liability for a leak sits with the entity controlling the data, which is you. Every major model vendor disclaims responsibility for generated code in its terms of use, and no regulator has accepted "the AI wrote it" as a defence. That is why the review step is a poor thing to negotiate away for a lower quote. None of this is legal advice, so check your specific obligations with a data protection lawyer before you handle sensitive categories of data.
How we run it
Scope is written as a list of screens, actions and integrations, and frozen before anything is built. One engineer drives the model, and that same engineer reads every line before it touches data or customers. The nine layers above are the gate, run against the diff rather than against a memory of what was intended.
Prices are published rather than quoted on request. An internal tool or prototype starts at AED 9,000 and takes around two weeks. An MVP starts at AED 18,000. The breakdown of what sits inside each tier is on the fixed-scope build page and in the walkthrough of what a two-week MVP build actually covers, so I will not repeat the tables here.
Handover decides your cost in year two. The repository lives inside your organisation account from day one rather than moving over at the end. You receive the code, the deployment, the environment variable list with your own keys, a data model note, a deployment walkthrough, and a runbook covering how to restart the thing and what to do when a specific part breaks. Two weeks of fixes are included after delivery, then you either maintain it in-house, common for internal tools, or keep a monthly arrangement. If it becomes an ongoing operational process rather than a piece of software, the automation format covers it from AED 6,000 setup and AED 1,200 a month, listed with everything else on the pricing page.
We run this on our own business first. SkyLight's studio bookings are handled by a WhatsApp agent we built and maintain ourselves, taking enquiries, issuing quotes and payment links, and writing into the CRM with no person in the loop. It takes money from strangers at three in the morning. That is the only portfolio claim I find worth making, and it explains the shape of the checklist above: every row corresponds to something that either broke on us or came close.
If you want the review applied to a build you already own rather than to a hypothetical, a small application takes about a day to produce a written list of what is wrong.