
Offline Java tool that scans application jars to determine exposure to 14 Netty codec-http CVEs, identifying the exact patched version (4.1.137.Final/4.2.17.Final) despite inconsistent advisory boundaries.
Offline checker for the 14 io.netty:netty-codec-http CVEs (2025–2026).
Tells you which ones you are actually exposed to — and the single version that clears all of them:
4.1.137.Final / 4.2.17.Final, a number written on none of the fourteen advisories.
CVE-2025-58056 · CVE-2025-67735 · CVE-2026-33870 · CVE-2026-41417 · CVE-2026-42580 · CVE-2026-42581 · CVE-2026-42585 · CVE-2026-42587 · · · · · ·
CVE-2026-50020CVE-2026-56746CVE-2026-59898CVE-2026-59899CVE-2026-59903CVE-2026-59921Single jar, zero runtime dependencies, fully offline, Java 17+.
Every Netty module is released under the same version number, so upgrading feels like a single decision. It isn't. On the 4.1 line the fix versions for these fourteen land on 125 / 129 / 132 / 133 / 135 / 136 / 137 — seven different values. Follow any one advisory and you are still inside the affected range of the others.
netty-codec-http2 declares netty-codec-http as a compile dependency with
<version>${project.version}</version> — the two versions are pinned to each other.
So if you upgraded to 4.1.136.Final because that is the version which clears the seven
netty-codec-http2 CVEs, you now run netty-codec-http 4.1.136.Final too — and
CVE-2026-59903 covers <= 4.1.136.Final. You need 4.1.137.Final. Same story on the
4.2 line: the HTTP/2 answer is 4.2.16.Final, this side needs 4.2.17.Final.
This is not a claim that the HTTP/2 advice was wrong — it answered for a different module. It is a claim that "one version number" hides the fact that you were answering for one module only.
Across these fourteen advisories the upper bound is written <= 16 times and < 12 times.
The same 4.1.136.Final is therefore safe for CVE-2026-56746 (< 4.1.136.Final) and
affected for CVE-2026-59903 (<= 4.1.136.Final).
Normalising the operator either way produces a wrong answer — one direction over-reports, the
other under-reports, and under-reporting is the expensive mistake for a tool like this.
The rule table keeps each operator exactly as the advisory wrote it, and an assertion in
tools/gen_rules.py fails the build if that boundary case ever stops behaving this way.
pom.xml — on purposeIf you use Spring WebFlux, netty-codec-http arrives through
spring-boot-starter-webflux
-> spring-boot-starter-reactor-netty
-> reactor-netty-http
-> netty-codec-http
The artifactId never appears in your pom.xml. A pom-based check answers "I don't use it",
which is wrong. This tool reads the jars that actually ship.
java -jar netty-http-check.jar target/ # scan build output
java -jar netty-http-check.jar myapp.jar # Spring Boot fat-jar / war, nested jars included
java -jar netty-http-check.jar --version-of 4.1.136.Final # judge a version directly
Exit codes: 0 = not affected · 1 = affected · 2 = could not judge.
🔴
2is deliberately not0. "Nothing found" and "clean" must not look the same to a script. A file that is not a readable zip is reported as a read failure, never silently treated as "no netty in here".
netty-codec-http. Other Netty modules have their own CVEs;
a clean result here says nothing about them. If you also run netty-codec-http2, the tool
says so and points at the cross-module problem above, but it does not judge that module.low; they are printed as they are, not rolled up into a scary total.reviewed with full package metadata, so Dependabot and
friends do alert on them. This tool is not "the scanner can't see it" — it is "the alert tells
you a version number per advisory, and you still have to work out which single version ends it."src/main/java/dev/mikko/nettyhttpcheck/RuleTable.java is generated, never hand-edited:
python tools/gen_rules.py --dry # run the assertions only
python tools/gen_rules.py # regenerate the table
Seven assertions must pass or nothing is written — among them: every advisory is still
reviewed; both version lines are present for each; the computed intersection is still
4.1.137.Final / 4.2.17.Final; those versions are actually fetchable from Maven Central
(with a sentinel version that must 404, so a broken probe cannot pass silently); the boundary
case in §3 still flips; and the HTTP/2 answer from §2 still leaves a hole on this side.
Real-jar end-to-end checks live in tools/e2e_real_jars.py — they download actual jars from
Maven Central rather than fixtures, because "works on a hand-built zip, fails on the real jar"
is a real failure mode.
MIT