Java vulnerabilities of the week: 19 to 25 August 2026

The previous post covered 12 to 18 August, this one covers 19 to 25. The format is unchanged: five things worth knowing, then two of them taken apart properly, because a CVE number and a CVSS score tell you nothing you can reuse and the mechanism usually does.

This week the interesting thing is not any single bug, its the volume. Four Java projects published multi-CVE batches inside five days, and three of them landed on the same day: Thursday 20 August brought the Spring advisories, six in Netty’s OHTTP incubator codec and nine in Apache InLong, then Camel added eight on the Monday. Depending on how you count the Spring batch, that is somewhere between fifty and a hundred and twenty CVEs in one working week, in one language ecosystem.

So I want to write about two things: one bug class that shows up four times inside a single project, and what happens to all this output once it leaves the advisory and tries to reach the people running the code. The second turned out to be the harder problem. Several of this week’s fixes are published, real, correct, and still will not reach you by upgrading.

The week in five

Honourable mention to Sakai on 24 August, CVE-2026-54049 (stored XSS in Conversations, High) and CVE-2026-54050 (IDOR on profile image deletion, Moderate). And a pointer rather than an item: as I write this on the 26th, Apache Tomcat has just published ten, including a security constraint bypass and an incomplete fix for CVE-2026-32990. Outside this window, so next week’s post.

Apache Camel: the header map is a control plane

I did the remediation work on all eight of these and I am the finder on CVE-2026-63621, so read this as an insider’s account rather than an independent assessment.

Camel’s central abstraction is the Exchange, carrying a message with a body and a map of headers, and those headers are how components talk to each other. The HTTP producer reads CamelHttpUri to decide where to send a request, the file producer reads CamelFileName to name the file it writes, and the SQL producer will take the statement to execute from a header when one is present. So the header map is not a bag of metadata. It is a control plane, and it shares one namespace with the data plane, because a message that arrived from outside has headers too and they land in the same map.

What keeps the two apart is HeaderFilterStrategy, applied by each component when it maps wire headers onto a Camel message, dropping anything in the Camel-internal namespace so an inbound header cannot become an instruction. All four header CVEs are that strategy failing to apply, in four genuinely different ways.

In camel-mail, it was never applied. With headersInline enabled, the MimeMultipart unmarshal path enumerated every MIME header except the three it generates itself and called setHeader for each, with no strategy in the path at all. A MIME part carrying a header named after a Camel control header became that control header.

In camel-knative, it was applied on one path and not the other. In binary content mode the CloudEvent attributes are HTTP headers and KnativeHttpConsumer runs them through KnativeHttpHeaderFilterStrategy. In structured mode the whole event is JSON in the body, and AbstractCloudEventProcessor mapped every remaining field, the extension attributes whose names the sender chooses, straight onto the message headers with no filtering. And because Camel’s header map is case-insensitive, an extension named camelhttpuri, a perfectly legal CloudEvent extension name, collides with CamelHttpUri.

In camel-atmosphere-websocket, the filter ran correctly and did not cover the headers that mattered. HttpHeaderFilterStrategy filters the Camel and camel prefixes, but the websocket producer’s dispatch headers are websocket.connectionKey.list, websocket.sendToAll and friends: dotted, lowercase, outside the namespace the filter knows about. In a bridged route an external sender could supply the list header and take over which clients receive the message.

In camel-undertow, the right strategy was constructed and then thrown away. UndertowEndpoint instantiated an UndertowHeaderFilterStrategy and then handed the base HttpHeaderFilterStrategy to UndertowHttpBinding instead, so the Undertow-specific logic that rejects legacy websocket prefixes never ran on endpoint-configured routes.

Why I find it interesting

Because the bug class has an old and well-studied name that nobody thinks to apply to a message header map: in-band signalling.

It is the blue box. The phone network carried control tones on the same channel as your voice, so a 2600 Hz whistle down the line was indistinguishable from the switch’s own signalling and you got a free call. Same as SQL injection, where query structure and data travel in one string. Same as HTTP response splitting and SMTP header injection. Every one of them is a control channel and a data channel sharing a medium, and a receiver deciding which is which by looking at the content.

Camel’s header map is exactly that. CamelHttpUri means “send it here” if the framework put it there and “some bytes arrived from the internet” if a component copied it in, and by the time a downstream producer reads the map there is nothing left to tell them apart. Seen that way the four variants stop being four separate bugs and become one design property that has to be guarded everywhere external data enters the map, and the question stops being “did we filter here” and becomes “how many places copy into this map, and do we have a list”.

For Camel that list was not written down anywhere, which is what a boundary enforced by convention tends to look like in a project with several hundred components. The structural answer is not more careful review, it is making the unfiltered path harder to write than the filtered one, and that is a better thing to take from this batch than a count of CVEs.

The near-term advice is duller and still worth doing. If a route takes input from outside and hands it to a header-driven component, put removeHeaders("Camel*") at the boundary rather then relying on every component upstream having got this right, add removeHeaders("websocket.*") if you bridge into a websocket producer, and set bridgeEndpoint on HTTP producers that should not be redirectable.

Spring: ninety one CVEs, and where the fixes actually go

The Spring batch is the bigger story of the week and almost none of it is about a specific bug. Start with the volume. Sonatype’s writeup reports that Spring historically averaged about 6.5 new security reports a month. In March 2026 that was 55. In April it was 482 across 65 scanned projects, 370 of them from Spring’s own scanning and 112 from the community. That is not a doubling, its two orders of magnitude in about six weeks.

The cause is AI-assisted vulnerability discovery, and the reporting here needs care. Sonatype notes that Spring has pointed to Anthropic’s Mythos research as an example of models finding bugs at greater scale, and is explicit that it has not confirmed Mythos found the 91 CVEs in this batch. SecurityWeek attributes the surge to “Broadcom’s use of AI” without saying how individual flaws were found. So: the volume is documented, the mechanism is AI-assisted scanning, and the attribution of any specific CVE to any specific system is not established by anything I read.

It is not only Spring. This week’s CVE-2026-59230 in Camel is credited, in the advisory’s own words, to “Atuin - Automated Vulnerability Discovery Engine, anciety of Tencent Xuanwu Lab”. A named machine in the credits line of an Apache advisory, which is worth noticing as a plain fact about how this work will arrive from now on.

Now the part that matters to somebody running Spring. CVE-2026-59270 affects 5.7.0 all the way up to 7.1.0. The OSS fixes are 7.1.1 and 7.0.7. The fixes for 6.5.x, 6.4.x, 5.8.x and 5.7.x are, in the advisory’s own words, Enterprise Support only. The same split appears on CVE-2026-59318 in Spring AI, where 2.0.1 is OSS and 1.1.9 and 1.0.10 are commercial.

I want to be fair about this. Somebody has to pay for maintaining a six year old branch, commercial LTS is a legitimate way to fund that, and the disclosure itself is public, detailed and free. None of that is the complaint. The complaint is narrower: for a large slice of the installed base, the vulnerability became public knowledge on 20 August and the patch did not. Publishing the flaw and withholding the fix, even for good reasons, hands attackers a diff and hands defenders an invoice.

Why I find it interesting

Because there is a much milder version of the same shape in this week’s Camel batch, and I only noticed it by looking at Spring first.

CVE-2026-66908 is the JWT issue: no jwtIssuer or jwtAudience, buildJwtOptions returns null, claim validation silently skipped, server starts clean with no warning. The fix in 4.22.0 is fail-closed: assertIssuerOrAudienceConfigured runs at the top of both configureAuthentication overloads and throws IllegalArgumentException if a JWT keystore has neither claim configured, and if you genuinely want signature-and-expiry-only you set jwtAllowMissingIssuerAndAudience explicitly. That is the right shape of fix, and it is the one from last week’s post: a control that fails open and reports nothing, converted into one that refuses to start.

But that enforcement only exists in 4.22.0. The backports to 4.14.9 and 4.18.4 add the options and deliberately change no existing behaviour, because a maintenance release that suddenly refuses to start on a configuration which worked yesterday is its own kind of outage. I think that is the right call for an LTS line. It does mean the upgrade on its own leaves claim checking off until you set one of the two properties yourself.

Two projects, two very different reasons, one shared consequence: a version number can move without the security posture moving with it. Spring’s reason is commercial and Camel’s is compatibility, and I do not think those deserve the same verdict at all. But a scanner reading a version string cannot tell either of them from a real fix, and that is the gap I would worry about more than any single bug this week. I have no clean answer beyond reading the remediation section for your own LTS lines rather then trusting the fixed-in field.

What I take away from this week

Three threads.

The first is that the volume problem is now a consumption problem, not a discovery problem. Roughly 114 CVEs across four Java projects in five days, if you take Broadcom’s count for Spring, and Sonatype puts over 200,000 software components in scope for that batch alone. Nobody’s Tuesday has room for it, and the failure mode will not be a missed critical, it will be triage fatigue: 91 advisories arrive, 90 do not apply to your configuration, and the one that does gets the same thirty seconds as the rest.

The second is that the plumbing between disclosure and your scanner is creaking under it, and GeoTools is the evidence. A 9.8 unauthenticated SQL injection, disclosed somewhere between 12 and 15 August depending on which source you believe, already being probed in the wild, and the GitHub and GitLab records landed on the 21st. Six to nine days when the exploit was public and your dependency scanner said nothing, in the same week four projects dumped over a hundred CVEs into the same pipes. I do not think that is a coincidence, and I do not think a weekly blog post is a fix for it either.

The third is practical. Most people will pick up the header fixes through a BOM bump in Spring Boot, Quarkus or Camel itself and never think about them again, which is the system working. The ones who need to move deliberately: anyone on camel-platform-http-main with JWT from a keystore, who should set jwtIssuer or jwtAudience themselves since the LTS backports leave that choice to you, anyone on netty-incubator-codec-ohttp (0.0.23, and go grep your logs for CVE-2026-61798), anyone running InLong below 2.4.0, and anyone with GeoServer or GeoTools in front of a PostGIS database, who should stop reading and patch that one first.

References