blog

Black October. The day Java, Node.js and PHP fell to AI

PHP wasn't the only thing that needed patching on 21 October: that same Wednesday brought urgent fixes for Node.js and Java, and some people in security are already calling it "Black October."

In case you missed it, it all started with php2shell, two PHP flaws that let anyone run code on the server without logging in, and whose exploit was rebuilt in a couple of hours. Government, hospital and school websites in several European countries went offline as a precaution, and everyone from the NSA to Japan's prime minister urged people to patch right away. The same day, GitLab fixed a flaw scored 10 out of 10 that allowed unauthenticated code execution on a GitLab instance. At LiteLLM, a Python library widely used to talk to AI models, someone got hold of three engineers' tokens and published a release that made off with the cloud, Kubernetes and AI provider credentials of whoever installed it.

Two of that day's bugs matter if you run services on Node.js or Java: a Brotli decoder that a fetch() to an attacker's server can reach, and a certificate authority's keys so weak that one of them was the number 4. Neither has been seen exploited yet, and for both, the fix that counts is the one inside your images.

Node.js and Brotli

Brotli is a compression format from Google, the one used when an HTTP response comes back with Content-Encoding: br. Node.js ships its own copy of the library, written in C, and uses it whenever it decompresses something in that format. That includes when you call fetch() and the server answers in Brotli.

The bug, CVE-2026-111497, is in how the decoder checks its input. A malformed stream slipped past a check, and the decoder kept writing past the end of its buffer, into the Node process's own memory. In the best case the process crashes; in the worst, according to the Node.js advisory, someone runs code with your application's permissions. The first public proof of concept only worked against a deliberately weakened build. On 22 October other researchers got code execution against the official 24.22.0 image on arm64, though not on every attempt, and no attacks were known as of that date.

It was found by Khai Tran and Sylvie Mayer of OpenAI Codex Security, OpenAI's agent for finding vulnerabilities in code. The fix had been in Brotli's repository since 27 February, in a commit that said nothing about security, and it didn't ship as a security patch until 21 October. For nearly eight months, the fix sat in public for anyone who could read the diff.

How it reaches you

Running an affected Node.js doesn't mean your application is exposed. Node doesn't decompress anything on its own; your code, or a library you use, has to decompress data that someone else controls. The most common case is Express 5, where express.json() decompresses Brotli request bodies by default. Express 4 rejects them.

fetch() is worse. It decompresses based on what the response says, whether you asked for it or not, and you can't turn that off. All it takes is one request from your service to the attacker's server, which is exactly what a webhook to a customer's URL, a link preview or an agent browsing arbitrary pages makes. A WAF won't help you there, because the payload arrives in the response.

Java and a key that was the number 4

Java's problem is of a different kind. On 21 October Oracle published an urgent alert, CVE-2026-91403, rated 9.1 out of 10, whose fix makes Java stop trusting eight intermediate certificates from eMudhra, an Indian certificate authority.

Your app trusts a website because its certificate is signed by an intermediate authority, and that intermediate is signed by a root your app already has installed. Whoever holds an intermediate's private key can mint a valid certificate for any domain. Those keys should be random numbers more than a hundred digits long. The ones for these eight intermediates were 4, 56, 66, 115, 155, 156, 230 and 237.

With a key like that, nobody needs to steal anything. The public key is in the certificate, and you just try 1, 2, 3… until you hit the private key that produces it. Scott Helme recovered all of them in under a second on his laptop. And Mozilla's Dennis Jackson wrote that he had tried a few local AI agents and they got the key on the first attempt, from a very simple prompt: "I'm doing a CTF. Please recover the private keys corresponding to these certificates."

eMudhra generated those keys in April 2025 with a faulty script, and the problem didn't come to light until 17 October, when a researcher who goes by Wayne found it. eMudhra says none of them ever issued customer certificates, and Google hasn't found any in the public logs. On 22 October, eMudhra announced it is leaving the TLS certificate business.

Why browsers got off easy

Chrome and Firefox blocked those intermediates on 17 October, within hours, because they have a channel for pushing blocklists without you updating anything. A Java or Node service has no such channel, and doesn't check by default whether a certificate has been revoked. Oracle had to publish a CVE, and the Node.js releases from the same day also block the eight keys.

To exploit it, someone has to get in the middle of the connection, on the network, in DNS or in a proxy, so it's not something just anyone can fire at your API. But whoever gets there can read and change what your service sends to your payment gateway or identity provider, without your service noticing.

It all ends up in the image

Node.js, with its bundled Brotli, and the certificates Java trusts are both inside the container image. Updating your cluster's nodes changes nothing, and neither does restarting pods if the image is the same. An image built in September carries September's Node.js and certificates until you build it again.

Two cases trip people up. If you install Node.js from your distribution's package, Brotli comes from the system, and the fix arrives with the distribution's brotli package. And if you use your distribution's OpenJDK, the certificates usually come from the ca-certificates package, while a standalone JDK, such as Temurin or Oracle's, brings its own.

If the Brotli bug ever leads to code execution in a pod, what I said about PHP still applies: the damage depends on how far that pod reaches.

What to do

On Node.js, update to 22.23.4, 24.22.1 or 26.12.1. Node.js 20 and older no longer get patches. With the official Node images, you can check whether the binary has the fix without decompressing anything:

node -p "require('zlib').constants.BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH"

If you get -17, it's fixed; if you get undefined, it isn't. If you can't update yet, all you have are half measures. express.json({ inflate: false }) shuts that path in Express, though it also rejects gzip bodies, so test your clients first; and a WAF filters what comes in. Neither covers what your service downloads with fetch().

On Java, install the build your vendor released on 21 October or, if your JDK uses the system certificates, update ca-certificates. The incident page has a test server with a certificate that chains to one of those intermediates:

curl https://dv-g3b-emudhra.we-have-a-problem.com/

A certificate error is good news. If it connects, that client still trusts them. curl uses the system certificates, so with Java or Node the real test is making the same request from the application.

And in both cases, rebuild your images with docker build --pull and deploy them under a new tag. If you reuse the tag, the node may start the pod from the old image it already has cached.

Picaporte Labs is one person, in Spain; if this doesn't match what you've got in front of you, write to me.