October brought the awareness after all
Cybersecurity Awareness Month 2026 brought PHP2Shell, an exploited BeyondTrust zero-day and two critical Next.js RCEs. My dependency audit stayed green for eight days while the Next.js fix sat on npm, and on a repository I had not patched yet, Dependabot was still silent days after the database caught up.
- security
- webdev
- devops
- cybersecurity

October 2026 is Cybersecurity Awareness Month, and it delivered the awareness in the least comfortable way it could: a month's worth of critical vulnerabilities in a single week. I read most of it as someone else's problem, right up until one of them turned out to be sitting in seven of the projects I run, and the tools I trusted to tell me about exactly that had not said a word.
A week of criticals#
The campaigns this year were about habits. The National Cybersecurity Alliance's theme is "Don't Make It Easy for Them", and one of its four habits is updates. Between 14 and 21 October, the vulnerabilities made the case for that habit better than any poster could:
- PHP2Shell (CVE-2026-109618 and CVE-2026-109619). Two unauthenticated remote code execution flaws in PHP itself, both rated 9.2 under CVSS v4. Details leaked before the coordinated release, Amazon reported exploitation attempts four days before the patches, and when PHP shipped 8.5.12, 8.4.27, 8.3.36 and 8.2.35 on 21 October, its own release notes said there are no complete mitigations and updating is the only safe choice.
- BeyondTrust Remote Support (CVE-2026-102764). An authentication bypass that CISA called critical and added to its Known Exploited Vulnerabilities catalogue on 16 October, with attackers already using it for initial access, persistence, privilege escalation and lateral movement.
- NGINX (CVE-2026-15176, CVE-2026-93479 and CVE-2026-97205, plus two more). Memory-safety bugs that can crash a worker process and might allow code execution, each needing a specific configuration to reach. F5 scores the three worst at 9.2 under CVSS v4, while nginx.org rates every one of them medium, which is a useful reminder that a severity is somebody's opinion of your setup. Fixed in 1.30.6 and 1.31.7 on 14 October.
- Next.js (CVE-2026-96889 and two others). Two unauthenticated remote code execution flaws, one in the Image Optimization API and one in
use cachefunctions, both rated 9.5 critical, plus a denial of service in Server Actions rated 8.2 high. Fixed in 16.4.1, and in 15.5.28 for the 15.x line. - Node.js fixed ten CVEs across its 22.x, 24.x and 26.x lines on 21 October. None is rated critical. The highest are four rated high, one of them an out-of-bounds write in the Brotli decoder that can allow remote code execution.
None of this was mine, I thought#
I do not run PHP, and I have never touched BeyondTrust. So I read PHP2Shell the way you read about a flood in another country: with sympathy, and without going downstairs to check my own basement.
Next.js I do run, on most of what I build. I found out on the morning of 23 October, when pnpm audit on this site came back with two criticals and a high against next@16.3.8. Seven of the projects I run were on vulnerable versions. Dependabot, which was switched on for most of them and exists to tell me about exactly this, had told me nothing.

The timeline#
I went back and pulled the timestamps, expecting to write about a gap of a few hours.

| When (UTC, 2026) | What happened |
|---|---|
| 14 Oct, 17:46 | Next.js publishes the three advisories in its own repository. 16.4.1 ships a minute later, with release notes naming all three. |
| 22 Oct, 14:54 | The last CI run on this site's main branch passes, dependency audit included. |
| 22 Oct, 15:50 to 16:42 | The three advisories enter the GitHub Advisory Database. |
| 23 Oct, 09:22 to 14:46 | I patch three of my projects, 17 to 23 hours after the listing. None of them had received a Dependabot alert for any of the three advisories, in any state, before its fix landed. |
| 23 Oct, 11:16 | I commit the fix for this site, 19 hours and 26 minutes after the first of them was listed. |
| 26 Oct | A fourth project, still on a vulnerable version with Dependabot alerts switched on, has no alert for any of the three, almost four days after the listing. |
The row that matters is not the one I sat down to write about. I came for a 19-hour gap and found an eight-day one: the fix was on npm, with its advisories linked from the release notes, for eight days before any scanner I use could know it existed.
The scanner was right#
It is worth being fair to the tooling here, because the easy version of this post blames it. My CI audit passed at 14:54 on the 22nd because the database it reads did not list those advisories yet. An hour later it did, and the same command against the same lockfile failed. The scanner was right at the moment it looked.
The problem is where it looks. pnpm audit and npm audit ask the npm registry, and Dependabot alerts come from GitHub's advisory database. OSV, which I checked as a second opinion, lists all three advisories with the same publication times as GitHub, to the second. I have not traced how the npm registry is fed, but my audit passed before the GitHub listing and failed after it, which fits one shared upstream. That is four tools that behaved as if they had one source. When that source is eight days behind the vendor, all four are eight days behind with it, and running a fifth that reads the same source would change nothing.

That question has a name#
Which left the question I should have asked a long time ago: when a vulnerability is announced and nothing tells you, what do you do? The answer is old, and it is called defence in depth. No single control is trusted to catch everything, so each one is placed to catch what the one before it misses. I had applied that idea to my code and to my servers. I had never applied it to how I find out.
Here is what would have closed the gap in my case, starting with the one that closes the most of it.

Watch the vendor, not just the database. The 16.4.1 release notes listed all three advisories, by name and by severity, on 14 October. On GitHub, choosing Watch, then Custom, then Releases on the repository of a framework you depend on sends you that announcement the moment it ships, which here would have been eight days before any scanner. It gets noisy for a project that releases often, but a mail filter on the word "security" handles most of that.
Scan on a schedule, not only when you push. My audit ran on pull requests and pushes, which meant a quiet repository was an unassessed one. The slightly embarrassing part is that the CI/CD lab I wrote this month already runs its scans weekly for exactly this reason, and this site's own audit job did not. It now runs daily, and a failed scheduled run emails me. That does not touch the eight days, because it reads the same database, but it turns "whenever I next push" into a check every day, so a listing should be caught within about a day, not guaranteed to be. It earned its place on its first morning. The first scheduled run, at 10:23 UTC on 24 October, failed on three high advisories that had been listed or updated the evening before, one of them against a direct dependency, and it failed again on the 25th and 26th until I patched them. Dependabot raised nothing for those either.

Put something in front of the app that can be patched faster than the app. This is the job a WAF does. During PHP2Shell, Cloudflare had rules blocking exploit attempts from 03:02 UTC on 21 October, which it says was about nine hours ahead of public disclosure, switched on for every plan including the free one, while still telling customers to patch. That is the shape of the help a WAF gives: it buys time, and then the patch still has to arrive. I could not find a vendor rule for these Next.js flaws, so here the nearest equivalent sits in the app itself. Limiting images.remotePatterns to hosts you trust narrows who can hand the image optimiser a file, and images.unoptimized removes it where you do not need it. When I measured what my own WAF actually blocked, the conclusion was the same: a compensating control, not a fix.
Make the alert reach a person. An alert that lands in a dashboard nobody opens is a log entry. Whatever you add should arrive where you already look, whether that is email from a failed scheduled run, a Slack message, or a webhook from a tool that watches your dependency list for you.
What this does not show#
On this site, as far as I can tell, none of the three flaws was reachable. The image flaw lives in an upstream library, and the advisory's own workaround is sharp 0.35.5 or later, which this site's lockfile already resolved. I do not use use cache, and there are no Server Actions. That is luck rather than design, and I patched anyway, partly because the use cache advisory tells you to upgrade even if you have no such functions.
I also do not know why the advisories took eight days to reach the global database, or why Dependabot stayed silent after they did. The GitHub Advisory Database holds two kinds of entry: reviewed advisories, which GitHub curates, and unreviewed ones, which are published automatically from the National Vulnerability Database. Dependabot alerts are generated from the reviewed ones, and I cannot see the review queue. Dependabot also does not raise an alert once the vulnerable version is already gone from the default branch, so the three projects I patched within a day tell you the alert had not arrived by then, not that it never would. The fourth project is the clearer evidence, and it is one repository, not a survey. And I have not compared commercial feeds such as Snyk or Socket on this incident, so I cannot tell you whether they were faster. If you have that data, I would like to see it.
Check yours in two minutes#
This example uses the Next.js advisory GHSA-2464-fhr4-w9xw, so the queries below are hard-coded to it. To check your own stack, swap in the repository and advisory ID of a framework you depend on. The commands compare when the vendor published the advisory with when your scanner could first have known:
# when the vendor published it
gh api repos/vercel/next.js/security-advisories/GHSA-2464-fhr4-w9xw --jq .published_at
# when the database your scanner reads listed it
gh api advisories/GHSA-2464-fhr4-w9xw --jq .published_at
# what you are actually running
pnpm why next # or: npm ls next
For this advisory the first command prints 2026-10-14T17:46:48Z and the second prints 2026-10-22T15:52:53Z. The distance between your two dates is a lower bound on the lag between the vendor and the database. It is not the exact period every scanner was blind, because how quickly each scanner refreshed or ingested the listing is something this post did not measure. If your scanner and your Dependabot agree with each other, that is not two opinions. It is one, quoted twice.
Likes are saved in your browser only.
Keep reading
- 14 September 202611 minWhat my WAF actually blocked, and what my app already stops on its ownI ran 26 attack payloads against a production app with ModSecurity on and off. It blocked 12: five my app already rejected, and seven it stopped a step earlier than the app's own defences. The class behind most real breaches never showed up in either column.
- 21 October 20269 minThe three questions you get asked at 2amWhen something goes wrong, three questions get asked and most teams cannot answer any of them. Why application logs are not an audit trail, how the record becomes a liability, and five questions a non-technical leader can ask about it.
- 12 October 20269 minYour coding agent runs as you. So does everything you install into it.A skill passed every scanner, and the scanners were right at the moment they looked. Why the artefact you review and the instruction that runs are two different objects, and how to inventory what your coding agent can actually reach.