devansh

HonoJS JWT/JWKS Algorithm Confusion

jpd

Table of Contents


Intro Lore

After spending some time looking for security issues in JS/TS frameworks, I moved on to Hono - fast, clean, and popular enough that small auth footguns can become "big internet problems".

Screenshot 2026-01-13 at 9

This post is about two issues I found in Hono's JWT/JWKS verification path:

  1. a default algorithm footgun in the JWT middleware that can lead to forged tokens if an app is misconfigured
  2. a JWK/JWKS algorithm selection bug where verification could fall back to an untrusted header.alg value

Both were fixed in hono 4.11.4, and GitHub Security Advisories were published on January 13, 2026.

Screenshot 2026-01-13 at 9

Screenshot 2026-01-13 at 9


JWT / JWK / JWKS Primer

If you already have experience with JWT stuff, you can skip this:

The key point here is that, algorithm choice must not be attacker-controlled.


Vulnerabilities

JWT middleware "unsafe default" (HS256)

Hono's JWT helper documents that alg is optional - and defaults to HS256.

That sounds harmless until you combine it with a very common real-world setup:

In that case, the verification path defaults to HS256, treating that public key string as an HMAC secret, and that becomes forgeable because public keys are, well… public.

Why this becomes an auth bypass

If an attacker can generate a token that passes verification, they can mint whatever claims the application trusts (sub, role, isAdmin, etc.) and walk straight into protected routes.

This is the "algorithm confusion" class of bugs, where you think you're doing asymmetric verification, but you're actually doing symmetric verification with a key the attacker knows.

Who is affected?

This is configuration-dependent. The dangerous case is:

The core issue is, Hono defaults to 'HS256', so a public key string can accidentally be used as an HMAC secret, allowing forged tokens and auth bypass.

Advisory / severity

Advisory: GHSA-f67f-6cw9-8mq4

This was classified as High (CVSS 8.2) and maps it to CWE-347 (Improper Verification of Cryptographic Signature).

Affected versions: < 4.11.4
Patched version: 4.11.4


JWK/JWKS middleware header.alg fallback

In the JWK/JWKS verification middleware, Hono could pick the verification algorithm like this:

GitHub's advisory spells it out, when the selected JWK doesn't explicitly define an algorithm, the middleware falls back to using the alg from the unverified JWT header - and since alg in JWK is optional and commonly omitted, this becomes a real-world issue.

If the matching JWKS key lacks alg, verifyWithJwks falls back to token-controlled header.alg, enabling algorithm confusion / downgrade attacks.

Why it matters

"Trusting header.alg" is basically letting the attacker influence how you verify the signature. Depending on surrounding constraints (allowed algorithms, how keys are selected, and how the app uses claims), this can lead to forged tokens being accepted and authz/authn bypass.

Advisory / severity

Advisory: GHSA-3vhc-576x-3qv4

This was classified as High (CVSS 8.2), also CWE-347, with affected versions < 4.11.4 and patched in 4.11.4.


The Fix

Both advisories took the same philosophical stance i.e. Make alg explicit. Don't infer it from attacker-controlled input.

Screenshot 2026-01-13 at 9

Fix for #1 (JWT middleware)

The JWT middleware now requires an explicit alg option — a breaking change that forces callers to pin the algorithm instead of relying on defaults.

Before (vulnerable):

import { jwt } from 'hono/jwt'

app.use('/auth/*', jwt({
  secret: '...',
  // alg was optional
}))

After (patched):

import { jwt } from 'hono/jwt'

app.use('/auth/*', jwt({
  secret: '...',
  alg: 'HS256', // required
}))

(Example configuration shown in the advisory.)

Fix for #2 (JWK/JWKS middleware)

The JWK/JWKS middleware now requires an explicit allowlist of asymmetric algorithms, and it no longer derives the algorithm from untrusted JWT header values. It also explicitly rejects symmetric HS* algorithms in this context.

Before (vulnerable):

import { jwk } from 'hono/jwk'

app.use('/auth/*', jwk({
  jwks_uri: 'https://example.com/.well-known/jwks.json',
  // alg was optional
}))

After (patched):

import { jwk } from 'hono/jwk'

app.use('/auth/*', jwk({
  jwks_uri: 'https://example.com/.well-known/jwks.json',
  alg: ['RS256'], // required: explicit asymmetric algorithm allowlist
}))

(Example configuration shown in the advisory.)


Disclosure Timeline


References