TL;DR

python-jose — the from jose import jwt library that a generation of FastAPI auth tutorials and generated backends still use — has a critical, unpatched algorithm-confusion bug. Its guard against "public key used as HMAC secret" (added for CVE-2024-33663) checks only PEM armor and OpenSSH prefixes, so a DER-encoded public key passes and is used as the HS256 secret; an attacker who has the service's public key (which is public) can forge any token against a verifier that does not pin algorithms=. CVE-2026-85394 (VulnCheck CNA, CVSS 4.0 9.3 / 3.1 9.1) was published 2026-09-03 after the reporter emailed the maintainer on 2026-06-04 without response and opened issue #414 on 07-06; a community fix (PR #422, 2026-09-16) is still open with no maintainer reply as of 2026-10-07, and pip index versions python-jose shows 3.5.0 as the latest release. Separately, AnyIO < 4.14.2 — the async layer under Starlette, FastAPI and httpx — validated TLS host names after the standard library's IDNA-2003 conversion, so a hijacked connection to a non-ASCII domain could present a legitimate certificate for a differently-encoded name (CVE-2026-63374, 9.3, fixed 4.14.2 on 2026-07-07). Pin algorithms=[...] everywhere you call jwt.decode, migrate off python-jose, and let anyio float to ≥ 4.14.2.

What happened

python-jose — CVE-2026-85394 (critical, no fix)

The 2024 fix for CVE-2024-33663 taught python-jose to refuse an HMAC key that looks like an asymmetric key. The check is textual: is_pem_format() and is_ssh_key(). George Chen (geo-chen) reported on 2026-07-06 in issue #414 — after an email on 2026-06-04 that, per the issue, got no response — that "a DER-encoded (binary SubjectPublicKeyInfo) public key matches neither … so it passes the guard." VulnCheck, as CNA, published CVE-2026-85394 on 2026-09-03: "python-jose through 3.5.0 fails to properly validate asymmetric keys in HMAC initialization, accepting DER-encoded public keys that lack PEM armor or SSH prefixes. Attackers holding the service's public key can forge HS256 tokens that pass verification when algorithms are not explicitly restricted. This is an incomplete fix for CVE-2024-33663." Scores: CVSS 4.0 9.3 (AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:N), CVSS 3.1 9.1 — both VulnCheck's; GitHub's database copy (GHSA-3qf3-8w2g-rqmx) lists "Patched versions: None."

Fix status, 2026-10-07. A contributor PR, #422 "Reject DER-encoded asymmetric keys as HMAC secrets (CVE-2026-85394)", opened 2026-09-16, adds ASN.1-structural DER detection to both the native and cryptography backends; the only activity is a community "Any updates on this fix?" on 2026-10-07. The repository is not archived (1.8k stars, open issues and PRs, CI configured), but there is no maintainer response on the issue or the PR, and no release since 3.5.0. This repo's status for the library is therefore active: a known-exploitable, publicly documented bypass with no fix available.

Who is actually exposed — read before panicking. The precondition is in the CVE text: when algorithms are not explicitly restricted. The attack needs a verifier that (a) is given an asymmetric public key to verify RS256/ES256 tokens and (b) calls jwt.decode(token, public_key) without algorithms=["RS256"], so the library honours the token's own alg: HS256 header and treats the public key bytes as the HMAC secret. Two common FastAPI shapes are not affected: an app that signs and verifies HS256 with a shared secret (the key is not an asymmetric key, so there is nothing to confuse), and an app that verifies an identity provider's RS256 tokens with algorithms pinned. FastAPI's current security tutorial installs PyJWT, not python-jose, and shows algorithms=[ALGORITHM] — but older tutorials, Stack Overflow answers and a large fraction of agent-generated auth.py files still from jose import JWTError, jwt, and the algorithms argument is the line most often dropped. That precondition, and the fact that it is a configuration many apps do get right, is why this advisory is high rather than the CNA's critical: the 9.3 is accurate for the vulnerable configuration and does not describe every python-jose user.

AnyIO — CVE-2026-63374 (critical score, fixed)

GHSA-82r6-8w77-94w6 (vendor advisory, 2026-07-07; CVE record by GitHub-as-CNA 2026-09-22; CVSS 4.0 9.3): "Prior to 4.14.2, connect_tcp() and TLSStream.wrap() can validate internationalized host names after the standard library converts them with IDNA 2003 instead of IDNA 2008. When a connection to a non-ASCII domain is hijacked or redirected, an attacker can obtain a legitimate certificate" for the IDNA-2003 encoding of the name and have it pass validation. Fixed in 4.14.2 (PR #1208); pip index versions anyio shows 4.15.1 current. AnyIO sits under Starlette, FastAPI's test client, httpx and the MCP Python SDK, so most FastAPI projects carry it transitively; the exposure needs the target to be an internationalised domain and the attacker on-path, which is why the CNA's 9.3 overstates it for a typical API that talks to ASCII hostnames. It is in this file because it reached the reviewed-critical pip listing (database 2026-09-18) two months after the fix and had never been logged here — the same backfill shape as PyJWT and tinypool.

Why these two are one advisory

Both are primitives: nobody chooses them, a framework or a tutorial does, and neither shows up in an "AI tools CVE" search. Both carry a 9.3 that is right for one configuration and wrong for most. And together with PyJWT's September batch (CVE-2026-102268: the same asymmetric-key-as-HMAC guard, bypassed by whitespace mutation, fixed 2.14.0) they show that the JWT algorithm-confusion guard is being re-broken across every Python JOSE library at once — and only one of the two libraries has a maintainer answering.

Am I affected?

# which JWT library is installed, and is anyio current?
pip show python-jose anyio 2>/dev/null | grep -E '^(Name|Version)'
grep -rn "from jose\|import jose" --include=*.py . | head
# the dangerous call shape: decode with a key but no algorithms= pin
grep -rnE "jwt\.decode\([^)]*\)" --include=*.py . | grep -v "algorithms=" 
  • python-jose present and a jwt.decode(...) without algorithms= and the key passed is a public key / JWKS → forgeable today; there is no upgrade that fixes it.
  • python-jose present with algorithms=["HS256"] and a shared secret → not this bug; still migrate (unmaintained).
  • anyio < 4.14.2 → pip install -U anyio; only matters if you connect to IDN hostnames, but the upgrade is free.

If you are affected

  1. Pin algorithms on every decode today — jwt.decode(token, key, algorithms=["RS256"]) closes CVE-2026-85394 regardless of library version; PyJWT ≥ 2.0 already makes algorithms mandatory.
  2. Migrate to PyJWT ≥ 2.14.0 (pyjwt[crypto]), the library FastAPI's own tutorial now installs; the API is one import away.
  3. If a verifier ran unpinned against a public key on an internet-facing service, assume tokens may have been forged: rotate the signing key pair (invalidating all tokens), review for sessions whose claims don't match a real login, and follow if-your-webapp-was-compromised.
  4. Agent-generated backend → auditing-a-vibe-coded-repo; auth code is where generators copy the oldest tutorial.

Prevention

  • credential-hygiene — treat "which algorithms may this verifier accept" as configuration you set, never something the token announces.
  • package-vetting-checklist — a security-critical dependency whose last release predates its open critical CVE, with an unanswered fix PR, fails the checklist whatever its download count.

Sources