On October 21, 2026, the PHP project shipped an emergency security release outside its normal schedule. Two flaws in PHP’s engine - quickly nicknamed php2shell - were rated 9.2 out of 10, and they affect every version of PHP released since 2012. PHP runs roughly seven out of ten websites on the internet. The details had leaked the day before, so the release went out early, while hosting providers and security vendors scrambled to put temporary protections in front of their customers.

That day, my former colleagues at Quarkslab sent me the write-up from the team that had found it. I knew the code and I knew it well. Two years earlier, in the first external audit of PHP’s core in years, I had reported two problems in exactly that part of the engine. One of them was serious enough to be assigned a CVE identifier in 2024. That identifier was still unpublished on the day php2shell shipped.

This is a story about a two-year gap that was survivable in 2024 and is not survivable now.

Two years of silence, then three independent discoveries in six weeks

A report that waited two years

In 2024 the Open Source Technology Improvement Fund (OSTIF), with public funding from the Sovereign Tech Agency, commissioned Quarkslab, my employer before Tolmo, to audit the heart of PHP ahead of a major release. Over 57 days, five of us went through various parts of the engine that face the internet most directly, including the code that runs whenever anything is uploaded to a PHP site.

Two of the findings went into the report. You can read that report today and learn nothing about them, as both of their pages were blanked before publication and replaced with one line - details to be shared after fixes are applied; fixes are complex and in progress. A CVE identifier was assigned to the more serious of the two. It was never published, so for two years there was a number with nothing behind it. Nothing a hosting provider, a distribution or a corporate security team could see, act on, or ask about.

The PHP Foundation has since explained what happened inside the project. While a core developer was fixing one of the issues we had reported, they found the first signs of the deeper problem that became php2shell. An attack was considered theoretically possible, but nobody could build one. Every candidate fix broke something else. Pragmatically, issues with immediate and demonstrable impact were fixed first. The team works by consensus, and consensus did not arrive. In the Foundation’s own words: “the delay was a structural failure.”

The easy version of this story, that someone ignored a warning, is false. A small group of people, many of them volunteers maintaining software that most of the web runs on, made a defensible triage call with the information they had. The information they had was that nobody could turn the problem into a working attack.

We had our hands on it, and we called it a crash

Here is the part that is uncomfortable to write.

Our two findings were in the same class of defect as php2shell, in the same part of the engine. We found them the slow way, before LLMs could help speed up the process, reading the code line by line and hand-building requests to watch how it behaved when something was wrong with them. We did not fuzz it, did not point an automated tool at it to generate malformed inputs by the million, and our own report says why, in a sentence I did not expect to be re-reading two years later:

“due to the time constraints of the audit, automated fuzzing was not utilized, as setting up the necessary environment proved too complex within the given timeframe.”

What we did by hand was enough to break it. We reproduced the crash reliably, and we suspected there was more behind it. A crash in that position, before the site’s own code has even started running, is the kind of thing that usually has a second story. We wrote that suspicion down and moved on.

Two things sent us in a different direction.

The first was our test environment. We ran everything on PHP-FPM, the setup behind most Nginx-hosted sites and the one our audit scope named. It is also, as I now know, the one of the two common setups that hides this particular damage: it reacts to the crash by killing the process outright, so there is nothing left to go wrong. The same request on the other common setup leaves the process alive and walking into the mess. We were not looking at a different bug, but the same one through the window that shows the harmless half of it.

The second was the question we chose to pursue. A crash you can trigger at will is already a reportable problem, so we followed it in the direction of impact we could measure, which is how reliably can an attacker knock the service over and what does that cost the operator. We wrote it up as a denial of service, which it genuinely is. The question we did not pursue is the other one a crash raises — what is left behind when the work stops halfway, and who picks that up afterwards? That unasked question is the entire difference between the 2024 report and the 2026 emergency.

For this piece, I went back and measured the distance, on the exact 2024 software, with the exact 2024 test case. Three things separated what we reported from the serious version of the same defect:

  1. a three-character change to our trigger input;
  2. 923 extra characters of filler, so that the leftover state lands somewhere consequential instead of on blank memory;
  3. the other server setup, the one outside our scope.

Three characters, a rounding error of extra data, and a different test machine.

How close the 2024 report was, and what still stood between it and a working attack

And yet this matters. Doing the obvious follow-up in 2024 would not have saved us. If we had made the three-character change and re-run the test on the same setup, we would have seen the same harmless crash and filed the same verdict with more confidence. The damage only becomes visible under a particular combination that nobody stumbles into by accident. What was missing was not diligence, but a different reflex, the one an exploit developer has and an auditor on a deadline usually does not.

Everything beyond that point, turning the damage into a working remote takeover, was a serious engineering effort. Three teams did it in 2026, independently, with far better tooling than we had in those ancient times. I take no credit for their work, and that distinction is the point of this article rather than a disclaimer at the end of it: finding the flaw and proving what it is worth used to be the same expensive activity. They have come apart.

Then three teams found it in six weeks

In August 2026, one firm reported a fully working attack built on that underlying problem. A second organisation reported the same root cause the same day, independently. A third followed two weeks later, and more reports arrived in September.

Nothing in the code had changed since 2024. What changed was the cost of looking at it.

A finding parked in a queue is not sitting still. It is waiting for the next person to look in the same place, and the number of people (aided by machines) capable of looking has risen sharply.

The acceleration is already in the public record

This is not a forecast about AI.

In late 2024, Google’s Project Zero described an AI agent that read code and found a previously unknown, exploitable flaw in widely used open-source software (Big Sleep). By mid-2025 the same system found a flaw that attackers were preparing to use. In late 2025 OpenAI introduced an autonomous security research agent that reads a codebase, reasons about it, confirms the problem and proposes a fix, and they had already found real vulnerabilities in open-source projects.

The volume figures tell the same story from the other end. Vulnerability submissions rose 263% between 2020 and 2025. The US National Vulnerability Database, which annotates reports so defenders can act on them, built a backlog of more than 20,000 unprocessed entries, and in April 2026 NIST stopped trying to process them all, moving to a risk-based model in which most reports are never enriched. The intake pipe of global vulnerability management burst before AI-assisted discovery reached any kind of scale.

My own re-examination is a small version of the same effect. Rebuilding a two-year-old test environment, re-reading unfamiliar code, running dozens of variations of an experiment and interpreting the results. In 2024, that was a week I did not have, which is exactly why I did not do it. With current AI tooling helping me, it took a day.

That is the shape of the change. The questions that get skipped are the ones that are expensive relative to the deadline, and those questions are getting cheap.

Five assumptions that no longer hold

Coordinated disclosure, embargoes, severity triage and patch windows were all designed around a scarcity that is ending.

“While we hold a finding, it is ours.” Embargoes assume rediscovery is rare. Here, two organisations reported the same root cause on the same day, after it had sat unfixed for 26 months. Assume anything you can find will be found again this quarter and that the next finder may not be someone who sends you an email.

“No known exploit” describes the bug. It does not. It describes how much effort the world has spent so far, and that effort is getting cheaper every quarter. Deferring on “nobody can exploit this” prices in today’s cost of attack and quietly assumes it holds for years.

“We control the disclosure date.” The php2shell release shipped a day early, in emergency conditions, because the identifiers leaked. The longer a fix sits in preparation, the more people know, and the more likely it is that the timeline gets decided for you.

“An identifier means defenders are informed.” A number was assigned to my 2024 finding and never published. For two years, nobody outside the room could know it existed. Tracking a bug privately is not the same as warning anyone.

“The bottleneck is finding problems.” Audits, bug bounties and now AI agents all push on discovery. None of them fix anything. PHP’s problem in 2024 was not a shortage of findings, but a shortage of maintainer time, funded remediation capacity, and a decision procedure that terminates. Money spent finding flaws in critical infrastructure, without matching money for fixing them, buys a longer queue.

What to change

For the people who fund, govern, and run this work:

  1. Pay for the fix, not just the finding. If you sponsor an audit of critical open-source software, budget maintainer time to remediate what it turns up. An unfunded finding is a liability with a timer on it.
  2. Give every deferral an expiry date and a named owner. “Complex, in progress” is a status, not a plan. If no decision is reached by the deadline, it should escalate automatically instead of quietly continuing.
  3. Plan the release path for speed, not for comfort. Pre-stage patches, shorten embargoes, rehearse the emergency release. The question is no longer whether someone else finds it, but whether you are ready when they do.
  4. Publish the identifier even when you withhold the details. Defenders can act on “there is an unfixed issue in this component” far better than on silence.
  5. Have a stopgap, and verify it rather than trust it. On disclosure day the only thing in front of most websites was a filtering rule at their hosting or CDN provider. Those rules worked — we checked from the outside and published the checker — but “the vendor says it is deployed” and “it is blocking on my site” are different statements, and only one of them is evidence.
  6. Point the acceleration at defence as well. The same tooling that makes discovery cheap makes triage, patch review and regression testing cheaper. Maintainers are the people most starved of time, and they are the last to be handed the new tools.

The gap, not the bug

The uncomfortable part of this story is not that a serious flaw lived in PHP for more than a decade. Software has flaws; that is the premise of the whole industry.

The uncomfortable part is the shape of the two years. A report arrived. It was read, believed, numbered, and then it entered a process whose natural speed is months to years, at the exact moment when the speed of discovery was about to collapse to days. When the two speeds met, the result was an emergency release for most of the web, shipped early because the secret had already escaped.

We are not going to slow discovery down, and we should not want to. The honest response is to accept that finding problems is now the cheap part, and to fund, staff, and design for the expensive part of fixing them on a clock we no longer control.


Mihail Kirov is a security researcher at Tolmo. In 2024 he worked on the OSTIF-commissioned Quarkslab audit of PHP’s core, funded by the Sovereign Tech Agency. The two findings described here were reported through coordinated disclosure and appear in the published report with their contents removed. The PHP Foundation’s account of the October 2026 release and of the delay is “Today’s PHP security release, and how we’re strengthening PHP security” (2026-10-21).