You patched PHP. The old image is still in your cluster's future.
On 21 October PHP shipped fixes for two critical bugs in the engine itself, CVE-2026-109618 and CVE-2026-109619, both CVSS 9.2, both reachable without authentication, both already being exploited by the time most people
On 21 October PHP shipped fixes for two critical bugs in the engine itself, CVE-2026-109618 and CVE-2026-109619, both CVSS 9.2, both reachable without authentication, both already being exploited by the time most people read the advisory. The community name is PHP2Shell. Fixed versions are 8.5.12, 8.4.27, 8.3.36 and 8.2.35. CISA and the UK NCSC both published "patch now" notices the same day.
I am not going to explain the bugs; Wiz, watchTowr and Qualys have done that well. I want to write about what happened in Kubernetes teams in the forty-eight hours after, because I watched it happen and because the second half of it is the part nobody schedules.
How most teams found out
From the news. Then someone opened a spreadsheet and typed "PHP" into the first column.
This is not a criticism. It is what a platform team does when the only inventory of what runs in production is the memory of the people who deployed it. The questions that followed were always the same three:
- Which clusters run PHP at all?
- Which images, which versions?
- Who gets to say "we are done"?
The third question is the one that comes back in March, when an auditor asks what you knew on 22 October and what you did about it.
How fast the scanners caught up
I measured this on 23 October, two days after the advisory, with Trivy 0.72.0 and whatever vulnerability database it pulled that afternoon. You can repeat every step.
The database build was stamped 13:13 UTC on 23 October. Both CVEs were in it, with the Debian and Alpine advisories attached:
$ grep -a -o CVE-2026-109618 ~/.cache/trivy/db/trivy.db | wc -l
1247
$ grep -a -o CVE-2026-109619 ~/.cache/trivy/db/trivy.db | wc -l
1253
Debian's security tracker already listed php8.2 as resolved in 8.2.35-1~deb12u1 for bookworm and php8.4 in 8.4.27-1~deb13u1 for trixie. Alpine's secdb for 3.22 carried both CVEs. A debian:bookworm-slim image with php8.2-cli installed from the archive that day came out as 8.2.35, already fixed; a alpine:3.22 image with php84 came out as 8.4.27, also fixed.
So the honest answer to "did the open-source scanners keep up" is yes, within two days, for PHP installed as a distribution package. That is good news and it is the ordinary case for a lot of internal images.
Where the scanners stay blind
Now the uncomfortable part.
$ trivy image --scanners vuln --severity CRITICAL,HIGH php:8.3-fpm
126 CRITICAL and HIGH findings. Not one of them about PHP.
The official php:* images build PHP from source. There is no php8.3 package in the image for a package scanner to look at, so Trivy, and every scanner that works from the package database, has nothing to match the advisory against. The 126 findings are the Debian base. The thing you actually care about is invisible.
This is not a Trivy bug. It is what package-based scanning is. But it means that if your PHP workloads run on the official image, or on any image that compiles PHP, then a quiet scanner is not a clean bill. It is silence.
What you can do about it this week, in order of effort:
- Ask the binary.
docker run --rm <your-image> php -vtells you the truth in one line. Do it for every PHP image tag you run, not the ones you think you run. - Rebuild on a base that was pushed after 21 October, pin the digest, and write the digest down somewhere an auditor can read.
- If you can, move to distribution packages for the runtime. It costs you a newer PHP, it buys you a scanner that can see it.
Patching is half the job
Here is the part that gets skipped. You rebuild the image, you roll the Deployment, the pods come up on 8.3.36, everyone exhales.
The old image did not go anywhere. It is still in the registry. It is still in helm history. It is the image someone will roll back to at 03:00 when the new build has a problem that has nothing to do with PHP. It is in the CI cache, in the staging cluster nobody looks at, in the Job that runs once a month.
Remediation has two halves:
- Run the fixed image.
- Make it impossible for the vulnerable image to run again, and keep a record that proves both.
Most teams do the first half and call it done. The second half is what an admission controller is for.
What this looks like with an admission controller in the path
This is where I talk about what we built, so take the next paragraphs for what they are. Attestkeep is an admission controller for Kubernetes that scans, verifies signatures, decides at admission, and keeps the record. Here is exactly what it did, and did not do, for PHP2Shell.
The database refreshes itself. The scanner pulls a fresh vulnerability database every six hours. Trivy publishes one every six hours too, so the lag between an advisory landing upstream and the cluster knowing about it is measured in hours, not in someone's calendar.
Every admitted image is rescanned. Once a day by default (scan.rescanInterval in the chart, if you want it shorter), every image the cluster has ever admitted is scanned again against the current database. An image that was clean on Monday and carries a CVE on Thursday shows up as not compliant on Thursday, with the CVE named. You do not open a spreadsheet; the inventory is the admission ledger.
It tells you. A scan that finds critical vulnerabilities raises a critical_finding event, which goes to whichever channel you configured: email, Slack, Teams, a webhook, Jira, GitLab. When a running image flips from "would be admitted" to "would be denied", that is a second, separate message: "Running image now fails policy". That is the message an operator actually needs.
New pods with the image are refused. If your policy blocks critical findings, the vulnerable image cannot be admitted again. The rollback at 03:00 fails with a reason, the Job that runs monthly fails with a reason, the staging cluster with the same policy refuses it too. The old image is still in the registry. It is no longer in your cluster's future.
Running pods are not killed. This is deliberate. Attestkeep does not evict a workload because a database update changed its verdict; that is an operator's decision, with the blast radius in front of them. What it does is make sure the next decision, any next decision, goes the right way, and that the running population is reconciled against the ledger on a schedule so drift is visible.
If the exploit lands first, there is a second line. PHP2Shell ends in a shell. If Falco is connected, a shell spawned inside a container is a runtime event, and a runtime policy can turn that event into a notification, an issue, or stopping the pod. That is not prevention, it is containment, and it is worth having when the window between advisory and rebuild is days.
Every decision is signed. The admission ledger records the image digest, the policy revision that ran, the verdict and the reason. When the auditor asks in March what you knew on 22 October, the answer is a record with a signature, not a recollection.
And the blind spot above applies to us exactly as it applies to Trivy, because Trivy is the scanner inside. If you run the official php:* image, Attestkeep's rescan will not name CVE-2026-109618 either. It will show you the digest you admitted, when, and under which policy. The php -v step is still yours. I would rather tell you that here than have you find it out from a pentest report.
The checklist, tool-agnostic
For this week, whatever you run:
- List every image whose name or Dockerfile mentions PHP. Not from memory; from the cluster.
- For each, run
php -vinside the image. Trust that over the scanner. - Rebuild on a post-21-October base, pin digests.
- Add a rule that refuses the old digests at admission, so a rollback cannot resurrect them.
- Write down what you found, what you changed, and when, in a form that survives until the audit.
If step 4 is a policy in an admission controller and step 5 is produced automatically, this is a Tuesday. If step 4 is a Slack message and step 5 is a Confluence page, it is a project, and it will be a project again at the next CVE.
Closing
The lesson of PHP2Shell for platform teams is not "patch faster". Everyone patched. The lesson is that the news is a bad way to learn what is in your cluster, and that a patched Deployment with the old image still admissible is not a remediated cluster.
Let the cluster do the checking. Keep the record. Make the old image a thing that cannot come back.
Sources: CISA alert, 21 October 2026 ยท NCSC advisory ยท watchTowr FAQ ยท Qualys ยท Google Cloud response. Scanner measurements are mine, 23 October 2026, Trivy 0.72.0; the commands are in the text.
Attestkeep runs inside the cluster it protects; every security feature is in the free edition. attestkeep.com ยท docs.attestkeep.com
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes โ full credit and traffic to the original publisher.