Every time AI-generated code comes up, somebody says the same thing.
"I won't ship code I didn't review."
I understand the instinct. I had it too. Then one afternoon I got curious and went and looked at the vendor directory on a project I had shipped and been proud of. Forty-something packages. Each of those pulling in their own dependencies. Somewhere north of four hundred thousand lines of code, and I had personally reviewed none of it.
Not skimmed. Not spot-checked. None.
I pulled it in because a lot of other people were using it and the star count on GitHub made me feel good about the decision.
That is not trust in code. That is trust in a crowd.
The defense of that is supposed to be that thousands of other people reviewed it. Fine. Name one of them. Name anybody who has read the package three levels down in your dependency tree, the one with forty stars that your logging library dragged in, the one nobody has touched since 2021. It is running in production with the same permissions as the code you argued about for an hour in a pull request.
We got a good look at what that crowd is worth in 2024. Somebody spent two years being helpful on the xz project, earned commit access the honest way, and put a backdoor in the release tarballs. It got caught because a Postgres developer noticed his SSH logins were running about half a second slow and would not let it go. Thousands of eyes on that dependency, and the thing that saved everybody was one guy being annoyed at latency.
So I think it is worth asking what that trust actually costs, because I do not think most of us have ever added it up.
What the crowd costs you
Here is what happens when a WordPress vulnerability drops.
The advisory goes public. Within hours somebody has turned it into a script and put the script on GitHub. Now every kid with a laptop and a grudge has a working exploit, and the only thing standing between them and a few hundred thousand sites is how fast the world can patch. There is a race, and every single WordPress site on the internet is entered in it whether the owner knows about it or not.
Now look at what that script actually does. It fingerprints. It goes looking for /wp-content/, for /wp-json/, for a generator meta tag, for a readme.html with a version number in it. Then it fires a very specific request at a very specific code path. It is not attacking your site. It is attacking a shape it recognizes, and your site happens to be that shape.
Run that same script against something bespoke and it gets a 404. Not a weaker attack. Not a partial compromise. Nothing. The exploit does not transfer, because there is nothing on the other end for it to land on.
That is not security through obscurity, and I want to be clear about that distinction because it is the one people reach for first.
Obscurity is moving wp-admin/ to you-cant-touch-this/ and hoping that stops the attack. The vulnerable code is still sitting right there, same code path, same everything, just behind a door with a different sign on it. All you have done is make the attacker spend twenty extra seconds finding the door, and the scanner that finds it does not get tired.
What I am describing is different. The attack itself does not apply. The kid running the script has no next step, and the next step, whatever it is, costs him time he was never planning to spend on you.
And it is not just WordPress. WordPress is the loudest example because the plugin ecosystem is enormous, but the shape of the problem is the volume of shared third-party code in your stack that has a public fingerprint. A well-maintained framework core is a much harder target than a random WordPress plugin, and I would never claim otherwise. But that core plus forty Composer packages you have never opened is creeping right back toward the same place.
The part that changed this year
Now, the obvious objection. It is 2026. Reading source code to find a vulnerability used to be weeks of a skilled human's life. It is not anymore. Hand a repo to a frontier model and ask it to find the holes and it will go find some.
True. Completely true. And it is much worse news for the framework than it is for you.
Think about where a rational attacker with an agent points that thing. Not at my static site generator. He points it at WordPress core, or at a plugin with two million installs, or at a Composer package that half the PHP world has in its lock file. The source is public, he needs nobody's permission, and anything he finds pays off across every install on earth.
AI collapsed the cost of vulnerability research. That collapse pays out in direct proportion to how many targets share the code. My bespoke site has an audience of one. The framework has an audience of millions. The threat model got worse for all of us this year, but it got worse for the monoculture at about a million to one.
So the question is not whether somebody could find a bug in my code. Of course they could. The question is why they would bother when the same effort aimed at shared code pays a million times better.
What bespoke does not buy you
I want to be honest about the limits here, because this argument has a real hole in it and you should hear it from me instead of from the comments.
Bespoke code does not give you bespoke bug classes. SQL injection is SQL injection. So is cross-site scripting, so is path traversal, so is broken access control. There are generic scanners that hunt for those without knowing or caring what wrote your application, and sqlmap does not need to know your framework to ruin your afternoon.
What you eliminate is the known-artifact attack, which is the overwhelming majority of what actually hits a small site. What is left is the generic stuff, the bugs that exist in any application regardless of what wrote it, and you handle those the way you always did.
Bespoke moves you out of the bot population. It does not excuse you from doing the work.
Terry's ogre
Back at ZendCon in 2007, Terry Chay gave a talk called "The Internet is an Ogre." His point was that security on the internet is layers, like an onion, like an ogre, and that every layer you add is one more thing an attacker has to get through. It has stuck with me for almost twenty years, which is more than I can say for most conference talks including some of mine.
That is the frame for everything above. Bespoke code is not a wall. It is a layer, and a good one, because it takes you out of the population that automated attacks are aimed at. The verification stack is a layer. Prepared statements and output encoding are layers. None of them are the answer by themselves and none of them have to be.
Keeping your repo private is another one. It means that finding a bug in my code requires probing my running site instead of reading my source, and probing is slower and noisier and more likely to trip something.
It is a layer. It is not a wall, and you should not build like it is. Source escapes through channels that have nothing to do with anybody hacking your box. An exposed .git directory in the webroot. A composer.lock sitting where the web server will happily serve it. An .env.bak from a deploy that went sideways at eleven at night. Verbose stack traces left on in production because you were debugging something in March and forgot. I have done at least one of those. Possibly two, and I am not going to tell you which.
So keep the repo private, and then build like it is going to leak anyway.
The part where I show my work
I built StaticForge, my own static site generator, in PHP. Then I moved almost all of my WordPress sites onto it.
It was not hard. That is the part I did not expect. And I got exactly what I wanted, which is a thing you almost never get from a general purpose tool, because a general purpose tool is built for the average of everybody's needs and not for mine specifically. There is no upgrade treadmill. When a WordPress CVE lands in my feed now, I read it the way I read the weather in a city I do not live in.
And yes, I know what you are about to say. A static site generator is the easy case. No auth, no sessions, no uploads, no user input at all, output is flat files. You are right, and I am not going to pretend otherwise.
But the WordPress sites I replaced did not need any of that either. They had it because WordPress comes with it. I was carrying the full attack surface of a platform that runs a large slice of the web so that I could publish blog posts, which is a trade-off I made without ever once thinking about it as a trade-off.
Nobody is reviewing this code
The obvious response to all of this is that I am now shipping code that nobody checked.
I wrote about that one on its own a couple of weeks ago in Nobody Is Reviewing This Code, so I am not going to re-argue it here. The short version is that I stopped reading the diffs back in March. I do not review the code. I verify it. Playwright drives the browser, PHPUnit covers the units, PHPStan catches what the types catch, a security scanner watches for injection and leaked secrets and auth holes, and at the end an actual human clicks through the actual app. On this kind of work I add a second model, usually Grok, to go over what Claude wrote, because a different model did not make the original decisions and has no affection for them. A GitHub Action enforces the whole thing on every merge, whether I am paying attention that day or not.
So here is the question I would put back to you. Count the gates that the last Composer package you installed went through.
Because I can tell you exactly what I did the last time I added a dependency. I read the README. I glanced at the date of the last commit. I typed the install command. That is it. That is the entire review process, and I have been doing a version of that for forty years and calling it engineering.
My hand-rolled user management gets more scrutiny than anything in your vendor directory has ever gotten. That is not something I could have said three years ago, and it is the reason this whole argument is worth having now.
The honest limit is this. All of that machinery is very good at pattern bugs. Injection, encoding, known bad calls, dependency CVEs, secrets committed at midnight. It is much weaker on the thing bespoke code actually gets wrong, which is ownership.
Your endpoint checks that the user is logged in. It checks that the user is allowed to view invoices. Both pass, and it hands invoice 4173 to the guy who owns 4172. Neither check knows which invoices are his, because that lives in your schema and nowhere else. No scanner is going to catch that, and it is the most common serious bug in hand-rolled code.
That one stays yours. Write the ownership rules down before you ask for the code, then write tests that try to break them.
Same goes for the auth surface. Name it up front and you get it. Session fixation. Reset token entropy and expiry. Timing-safe comparison. Rate limiting on login. Account enumeration on login and on reset. Remember-me tokens that do not quietly turn into permanent skeleton keys. Ask for those explicitly and they show up. Stay quiet and you will get about half of them.
The other cost I will own: you are the maintainer now, forever. For my own sites, fine, I like the work. For a client, that is a real conversation about what happens to them if I get hit by a bus, and I am not going to hand-wave it.
So
"I won't ship code I didn't review" is a good instinct pointed in exactly the wrong direction.
The AI-written code is code you specified, that a second model reviewed, that a scanner went over, that a pipeline gates on every merge, and that has no dependency tree hanging off it. The framework is code you did not write, did not read, cannot vouch for, and share with a few million other people who are all in the same patch race you are.
I am not telling you to go delete your composer.json. I am telling you that build versus buy is a live question again for the first time in twenty years, and most of us are still answering it with a reflex we picked up when building was expensive. Building is not expensive anymore.
Go look at your vendor directory. Actually look at it. Then ask yourself how much of that you would miss.
Make something awesome.