On October 21st, the PHP project shipped fixes for CVE-2026-109618 and CVE-2026-109619 — each scored 9.2 under CVSS v4.0, both unauthenticated remote code execution. The internet calls the pair PHP2Shell. The detail I keep turning over isn’t the score; it’s this: on the worst-affected setups, the malicious request wins before it ever reaches your application. The published analysis shows the exploit corrupting memory while PHP is still cleaning up between requests; a hello-world script is a sufficient victim. Your code could have been flawless. The vulnerable part was never your code — it was the plumbing your code lives inside.

Every PHP serving mode takes its turn: mod_php worst, then FPM, CGI, FrankenPHP, LiteSpeed. The fixes are 8.2.35, 8.3.36, 8.4.27 and 8.5.12; end-of-life branches got a “minimally-scoped” backport, which is vendor for “this hole is still closing.” The published mitigations reduce the odds and close nothing. And the CERT tracker’s affected-product list isn’t just blogs: it runs to antivirus management servers, storage management interfaces, conferencing gear. PHP is on devices you were told to firewall “just in case.” This month’s “just in case” is load-bearing.

Then the timing. Researchers tracking the wild reported attempted exploitation as early as October 17th — four days before the patch existed and the vendor said a word. Weaponization used to be slow, expensive, human-paced. That assumption is what the last few years have been killing, and PHP2Shell is the month it became obvious.

The flood is the premise

What an agentic world changes, stated plainly: the bottleneck was never clever attackers, it was effort. Finding a bug, reverse-engineering a public patch into a working exploit, and pointing it at every host still answering — that used to be a chain of expensive humans. It is now a chain of cheap, tireless agents. When a security advisory ships, the diff itself is homework for every exploit agent on earth, done in minutes. Lab agents like Google’s Big Sleep are already filing real production bugs. So plan from the premise: over the next few years, published CVEs climb sharply, and the gap between “bug becomes known” and “bug is being exploited” collapses toward the publish time of the advisory.

Most organizations’ posture against that premise is reaction speed: scan, triage, patch within a sprint. That is a bet that you can outrun a machine reading the advisory the moment it’s public. You can’t — the patch itself was four days late to its own disclosure. The only posture that survives is pre-action: architect so the bug doesn’t have somewhere to live.

The CMS was a bad trade. The flood made it indefensible.

Apply that to the database-backed PHP CMS — WordPress and its cousins, still under something like four in ten web pages. Stand up everything one visitor touches on one request: a general-purpose interpreter parsing attacker-controlled input; plugin and theme code nobody on staff has audited, fed from an update feed you don’t control; a database and its credentials on disk; a login page, an XML-RPC endpoint, installer debris; a cron running inside the web root; and, per this month, the interpreter itself — vulnerable even when all the above is perfect.

For what? Pages that change once a month, a contact form, an admin who wants to click “Publish.” The honest old answer to “why not static?” was cost: you bought the CMS so non-programmers could click buttons. Nobody priced the carrying cost of the inventory above. And the cost side of the trade died too: content pipelines are now nearly free.

A CMS, PER REQUEST what one visitor touches, every visit ! Interpreter parsing attacker-controlled input ! Plugin + theme code nobody here audited ! Database — credentials sitting in config ! Login page, XML-RPC, update feeds, cron × PHP2Shell: the interpreter itself — hello-world pops every box must be patched correctly — forever, while exploit agents read each advisory A STATIC SITE, PER REQUEST what the same visitor touches ! One web server, handing out finished bytes ! A file, written at build time — never at request ! No interpreter, no database, no login in the path ! Attacker menu for the month: files publish = copy files out OFFLINE PIPELINE: author text → generate (Jekyll / Hugo / Eleventy / agent-built) build box never meets the internet
Same internet, same traffic. One side has five things to patch correctly, forever; the other ships bytes.

The pipeline: Jekyll was the proof this works

Plain-text content in, whole website out: Jekyll proved the shape in 2008, the thing that ran GitHub Pages for a decade. The family grew — Hugo if you want one binary that builds in milliseconds, Eleventy if JavaScript people are in the room, Pelican for Python shops, MkDocs for docs, Astro when you want sprinkles of interactivity. The shape is identical in all of them: you author text and images; a generator renders templates, feeds, archives, pagination into finished HTML; a deploy step copies bytes to a server that does nothing but hand bytes out.

Now the security reading, which is the whole point. The build box holds your tools, drafts and deploy keys — and never faces the internet. The serving box faces the internet — and holds nothing but finished files: no interpreter parsing the visitor, no credentials in the path, no login to spray, no plugin feed to poison. Only output files cross between them, one direction, at publish time. The attacker’s entire menu on the serving box is: files. Same one-command publish, roughly an order of magnitude fewer things that can be popped.

If the last objection is “I’d have to build the pipeline myself,” that objection died with the price of software: pointing a Jekyll-style template at a coding agent for an evening is now a rounding error. I’ve written about running the whole private-AI stack at home and about keeping agents in the lab where they belong; a static pipeline is one of the safest first custom builds, because its worst failure mode is a bad-looking page.

Fine, I need dynamic parts

Comments, a search box, a contact form. First, be honest: for most brochure sites and blogs the CMS’s real dynamism is roughly none, and its “dynamic” pages are aggressively cached approximations of files anyway. For what you genuinely need, buy it feature by feature: a compiled Go binary with a single job behind auth or rate limiting, a hosted function from someone who staffs a security team, an embed whose provider now carries the risk with their SOC. Those carry supply-chain and privacy costs; that’s a trade to make deliberately, not an architecture by default. The inversion is the point: a CMS spends your security budget escaping dynamism you never asked for; a pipeline buys the minimum you need, at full price.

Removal beats reaction

The posture I’d argue every owner of a public web property adopts now, before the flood, not after one more 9.2 lands on a stack you don’t control:

Count the interpreters in the request path. For anything that is mostly content — sites, blogs, docs, portfolios — the target is zero. Every interpreter parsing stranger input at request time is a PHP2Shell not yet published.

Rank your controls: removal, then isolation, then reaction. If the bug can’t live there, patching is a formality. Every dashboard celebrating your patch turnaround is quietly conceding you own surface you shouldn’t.

Keep the build private and the serve dumb. The internet gets the files; it never gets a path to the keys.

Stop treating “just put WordPress on it” as free. It’s a permanent security payroll on a supply chain you don’t control, and the invoices are arriving as CVEs faster than any sprint can absorb them. If you need software, make it the cheap custom kind: agent in a lab, human holding the deploy.

The winners of the next few years won’t be the shops with the best security tooling. They’ll be the boring ones: static files where dynamism isn’t required, one or two honest little APIs where it is, and nothing in the request path that can be talked into running a shell. The flood can’t flood a parking lot.

And before you wave this off as theory: the page you’re reading is the argument. Text file, generator, one copy command. Nothing on that server executed a line of application code to hand you this sentence; if the CVE count triples next year, it still has nothing to find. “Nothing to exploit” beats “promptly patched” every single time.

— Kris