You Cannot Patch What You Cannot See

Two critical flaws in one day, and only one of them was new

On 21 October 2026, two critical vulnerabilities were patched within hours of each other. PHP2Shell and Node2Shell. Both scored CVSS 9.2. Both are now in CISA’s Known Exploited Vulnerabilities catalog with confirmed exploitation in the wild.

PHP2ShellNode2Shell
CVECVE-2026-109618, CVE-2026-109619CVE-2026-111497
CVSS9.29.2
Fixed inPHP 8.5.12, 8.4.27, 8.3.36, 8.2.35Node.js 22.23.4, 24.22.1, 26.12.1
Patch released21 Oct 202621 Oct 2026
Added to CISA KEV26 Oct 202627 Oct 2026
US federal remediation deadline29 Oct 202610 Nov 2026

PHP2Shell is a memory-corruption pair in PHP core, reachable where session.upload_progress is enabled (the default) and triggerable through file-upload endpoints, including endpoints that do not actually work. Details leaked ahead of the coordinated disclosure date, so the patch, the announcement and the first exploitation attempts all arrived inside one week.

That was the easy case, and it got the attention precisely because it was easy. The technology was in the headline, so everyone running PHP knew within seconds that they were in scope. The only question was how fast they could roll.

Node2Shell is the one worth your time, and almost everyone read it wrong.

Node2Shell is not a Node.js bug

The flaw is an out-of-bounds write in the Brotli decoder that Node.js bundles. A crafted Brotli stream containing an invalid block switch was reported as “needs more input” rather than a permanent format error, so the streaming decoder kept appending input into an eight-byte internal buffer.

The vulnerable code originates in the Brotli project itself, and that is where the real fix shipped: Brotli 1.0.10, 1.1.1 and 1.2.1. Node.js picked it up across all supported branches in a release that closed ten issues. The same underlying bug forced emergency patches in Microsoft .NET, and any HTTP server or client carrying a vulnerable Brotli build is in scope.

One upstream C library, several unrelated ecosystems, and a component that appears in nobody’s package.json.

You could read every line of every pull request your team merged this quarter and never once see the word “brotli”. Nobody chose it. Nobody reviewed it. Nobody has an opinion about it. It arrived as a dependency of a dependency of your runtime, and on 21 October it became the most interesting thing in your estate.

That is the real shape of supply-chain risk. Not a scary CVE with a logo, but a component three levels below anything a human ever approved.

Now ask how your pipeline would have told you

What follows sounds like an argument against AI-assisted development, and it is not. I build with AI assistance every working day and I am not giving it back. But look honestly at the mechanics.

A model adds a package to satisfy a prompt. It resolves. It compiles. It works. Lint passes, type-check passes, unit and integration and end-to-end all pass. The pipeline is green top to bottom, and for a perfectly good reason: every one of those stages verifies behaviour you specified. Nothing you specified says “and do not also exfiltrate the CI token”.

Meanwhile the volume of change per engineer is up and the review attention per line of change is down. Code review still works fine for the job it was designed for. It reviews a diff. The risk is not in the diff. Endor Labs’ research team puts roughly 95% of vulnerable dependencies in the transitive set, pulled in indirectly rather than chosen by a developer. Treat the exact figure as directional, but the shape of it is not really in dispute.

So name your stages out loud: lint, type-check, unit, integration, e2e, maybe a dependency scan at build time. Which of those would have told you about Brotli? Every one is a point-in-time check against intended behaviour. None of them is watching what you already shipped.

This stopped being optional about a decade ago

Registry-level supply-chain attacks are not news. Shai-Hulud was a self-replicating worm in the npm ecosystem, abusing post-install scripts to spread with no further human action. The first wave in mid-September 2025 hit over 180 packages and harvested cloud, registry and CI credentials. A much wider wave later that year reached roughly 640 packages and tens of thousands of GitHub repositories. Initial access is believed to have started with a phishing campaign spoofing npm.

That is a year old, and the technique was established well before it. Dependency inventory stopped being a nice-to-have roughly when registry attacks became a popular vector, about a decade ago. So the honest question is not whether it was already mandatory. It was. The question is what it becomes now that the volume of code being produced has an amplifier bolted onto it.

The danger did not grow much. The noise grew enormously.

Here is where most vendor writing goes lazy, with a paragraph about teams drowning in CVEs. The data says something sharper, and more useful.

Published CVE volume for January to September 2026 reached 75,242, against 48,244 for all of 2025. Call it plus 56%. Confirmed exploitation is essentially flat: 249 CISA KEV additions in 2026 against 245 in 2025. In Q3 2026, 186 of 37,940 published CVEs showed weaponisation, around 0.5%.

Part of that rise is a change in counting. A “one vulnerability, one ID” rule became mandatory for automated disclosure pipelines in June 2026, which moved the unit of measurement mid-series. Automated discovery and issuance multiply it further. Software did not suddenly get 56% worse.

What did get worse is the free triage help. In Q3 2026 the NVD logged 36,970 records, of which only 8.4% carried a CVSS v3 severity, down from 37.3% a year earlier. The backlog passed 27,000 unprocessed entries in June 2026, up from around 13,000 in February 2024.

Put those together: the danger is broadly stable, the noise has roughly doubled, and the people who used to score it for you have stopped. The scarce resource is not patching capacity, and it never really was. It is attention.

PHP2Shell and Node2Shell were in that 0.5%. That is only useful to you if you can separate the 0.5% from the rest in the first hour rather than the first week. An estate that cannot do that is not merely inefficient. It is carrying a business-ending risk on an unbounded clock, and for a smaller company one bad week is the whole story.

What is actually missing from the SDLC

Not better testing. A stage that keeps watching after the build is finished. Five things, concretely.

An inventory generated by the build, not by a survey. An SBOM produced in CI from the artifact that actually ships, not from the source tree and not from what a developer believes is installed. CycloneDX or SPDX, machine-readable, and crucially the full transitive closure. Top level alone would not have shown you Brotli.

SBOM is the acronym everyone knows, but it is one member of a family. The same CycloneDX standard also defines a cryptography bill of materials for the algorithms and key lengths you depend on, a hardware bill of materials, and a machine-learning bill of materials for model and training-data provenance. Where any of those apply to your estate, the same discipline applies: generate the inventory from what actually shipped, not from a document someone filled in by hand.

A map of which version runs where. An SBOM in a drawer is compliance theatre. The question you need answered in minutes is “does anything in production contain Brotli below 1.1.1, which services, which environments, which tenants”. That means linking artifact to image to environment to tenant, and keeping the link current. The map is the asset; the document is a row in it.

Continuous re-evaluation instead of a scan at merge time. Nothing in your code changed on 21 October. Your exposure changed. Any control that only fires on commit is structurally incapable of catching this class of event. The scan has to re-run against the standing inventory whenever the vulnerability data changes.

Filtering that produces an alert somebody will read. Reachability and deployment context are what turn 75,000 CVEs into the handful that need a human tonight. A rule that pages on every new critical is functionally identical to no alerting at all, because it gets muted in week two.

A sanity floor on the inventory itself. This one is subtle and I have watched it bite. An inventory pipeline that silently produces a nearly empty document still uploads cleanly and still reports the component as monitored. Nothing is being scanned and the dashboard is green. An inventory that fails open is worse than none, because it manufactures confidence. Assert a plausible floor on component count and fail the build below it.

The EU has already put a clock on this

Under the Cyber Resilience Act, manufacturers’ reporting obligations took effect on 11 September 2026. Actively exploited vulnerabilities and severe incidents now carry a 24-hour initial notification to ENISA via the CRA Single Reporting Platform, addressed to the coordinating CSIRT of the member state of main establishment. It applies to products already on the market.

The main body of obligations, including the explicit SBOM requirement, applies from 11 December 2027. Annex I asks for a machine-readable SBOM in a commonly used format covering at least the top-level dependencies.

Two observations for anyone selling software into the EU. A 24-hour reporting clock is a continuous monitoring requirement wearing different clothes: you cannot report inside 24 hours what takes you three weeks to inventory, and that deadline started over a month ago. And “at least the top-level dependencies” is a legal floor, which would not have shown you Brotli. Compliance and knowing your exposure are two different projects, and only one of them keeps you online.

Monday morning

  • Time yourself answering one question: which services run Brotli below 1.1.1, in which environments, without asking a developer. However long that takes is your real incident response time.
  • Generate the SBOM in CI from the shipped artifact, with the full transitive closure, and fail the build if the component count looks implausibly small.
  • Store inventories per deployment, per environment, per tenant. Build the mapping first and argue about document formats later.
  • Re-scan the standing inventory on vulnerability-feed updates, not on commits. This is the single change that would have caught Node2Shell.
  • Tune alerting on deployment context and reachability before you switch it on. A channel that gets muted is worse than silence, because it looks like coverage.
  • Check your end-of-life exposure. Around PHP2Shell, Qualys observed roughly 167 million PHP installations, 73% on internet-facing assets, and some 110 million of those (71%) on end-of-life versions from 5.4 to 8.1 that will never get a vendor patch. Where you are past end of life, “patch promptly” is not a strategy, and you need a different plan.

One half of the exposure, not all of it

Everything above is exposure you did not choose. A package, a runtime, an operating system version sitting in your estate because a dependency tree put it there, not because anyone decided it belonged. That gap is real and underserved, and it is what this piece is about.

It is not the only gap. A form that trusts a client-side check it should never have trusted. A discount rule that holds for every test case anyone thought to write and fails for the one nobody did. An access control decision that is correct for each feature on its own and wrong the moment two of them compose. None of that will ever show up in an SBOM, a CVE feed, or an OS patch cycle, because the code is doing exactly what it was told. The defect is in the logic, not the library.

That is where we are taking the next piece: extending testing past dependencies and infrastructure into the functional and logical layer, with the OWASP tooling we actually run ourselves, OWASP ZAP among it, rather than the OWASP Top 10 read only as a checklist.

Where we come into this

We have been building and operating this kind of monitoring for our own multi-project, multi-tenant estate since well before supply-chain security became a topic people write about. Not because we predicted anything, but because operating other people’s production makes the gap obvious quickly.

If this is on your list, we can help three ways: choosing tooling that fits your actual estate rather than a reference architecture, building the inventory and alerting pipelines with your team, or walking you through how ours works in a complex multi-project environment. A working system you can poke at, not a slide.

No drama, just an honest picture of what you would know, and how fast, the next time one of these lands.

Reference