Hello list,

jwt_verify's key argument is documented as holding "a secret or a
public key path." The code never separates the two uses. The
verification algorithm comes solely from the converter's algorithm
argument; on the HMAC branch, the key argument is passed verbatim to
HMAC() as the secret. The one guard — ctx->is_x509, src/jwt.c:271 —
refuses HMAC only when the key string names a certificate. A bare
public-key path has no such protection.

The third component is the documentation's own example, which
extracts the algorithm from the token being verified and passes a
public-key path as the key. A deployment following the vendor's
example has arranged the RS256→HS256 confusion itself — and the
resulting HMAC secret is the key's filesystem path, a string that
appears in configurations and error messages and is secret from
no one.

Proven live on the official haproxy:3.4.7 image, configured with
the documented pattern:

legitimate RS256 token -> accepted
forged HS256, key = "/usr/local/etc/haproxy/rsa-pub.pem" (literal)
-> accepted (bypass)
forged HS256, wrong key -> rejected
no token -> rejected

3.4.7 is the current stable release, and master HEAD carries the
identical construction. Credit where due: 3.4.7 hardened this same
converter against the "none" algorithm, per RFC 8725. The other
door of the same class stayed open. No CVE, vendor advisory, or
public writeup covers this construction; the search behind that
statement is documented in §5 of the paper.

The fix has the shape of the guard that already exists: bind the
algorithm family to the key type, and refuse HMAC verification for
material loaded as an asymmetric key.

Paper: https://archive.org/details/knell-the-path-is-the-key
Mirror: https://github.com/the-knell/The-Path-Is-the-Key
Key: 133883AB9F6EDE5B9BA6B82E27BEDAF0CE152F25 (knell)

Verify: gpg --import knell-public.asc, then
gpg --verify paper-haproxy-FINAL-2026-11-05.md.asc

Any response from the HAProxy developers will be appended to the
paper unedited.

— knell

Reply via email to