Severity Daily

IT and AI security incidents, checked against the primary source

Tag: python-jose

  • python-jose still lets a public key serve as an HMAC secret, because the 2024 fix checked the key format instead of the key

    python-jose still lets a public key serve as an HMAC secret, because the 2024 fix checked the key format instead of the key

    VulnCheck published CVE-2026-85394 against the python-jose JOSE library on September 3, 2026, at 7:17 p.m. UTC. The record describes an algorithm-confusion bypass, and its last sentence is the story: “This is an incomplete fix for CVE-2024-33663.” The 2024 fix tried to stop a public key from being used as an HMAC secret by checking whether the key looked like a PEM or SSH key. A DER-encoded public key looks like neither. There is no patched release, the GitHub issue has been open since July, and python-jose has not shipped a version since May 2025.

    What happened

    The full NVD description, verbatim: “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.”

    The weakness is CWE-347, improper verification of cryptographic signature. VulnCheck scored it 9.1 under CVSS v3.1 with the vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N, and 9.3 under CVSS v4.0. The record’s vulnStatus was “Received” at the time of writing.

    Algorithm confusion is an old and well-understood class. A JSON Web Token carries its own algorithm identifier in the header. A service that verifies RS256 tokens holds an RSA public key, which is not a secret — it is typically published at a JWKS endpoint so that anyone can verify the service’s tokens. If an attacker can get the verification routine to treat that public key as an HMAC shared secret instead, the attacker can mint HS256 tokens signed with the key everybody already has, and the library will accept them. The canonical instance is CVE-2022-29217 in PyJWT.

    python-jose took this in April 2024 as CVE-2024-33663, filed on GitHub as GHSA-6c5p-j8vq-pqhj, titled “python-jose algorithm confusion with OpenSSH ECDSA keys,” affecting versions before 3.4.0 and patched in 3.4.0. The patch added a guard. In the library’s native backend the check reduces to this shape:

    if is_pem_format(key) or is_ssh_key(key):
        raise JWKError

    That is a formatting test, not a type test. is_pem_format looks for the -----BEGIN armor. is_ssh_key looks for the prefix that opens an OpenSSH public key line. Both are string patterns wrapped around the same underlying object: a DER-encoded key. PEM is base64-encoded DER with a header and footer glued on. Strip the armor and base64-decode it and you have the identical key material with none of the strings the guard is looking for.

    That is what GitHub issue 414 against mpdavis/python-jose reports. It is titled “Algorithm-confusion guard bypassed by DER-encoded public keys (incomplete CVE-2024-33663 fix),” was opened on July 6, 2026 by the account geo-chen, and is still open with no maintainer response recorded. The reporter demonstrates forging an HS256 token using the DER form of the service’s public key and having it verify successfully on decode. The recommended fix is to stop pattern-matching entirely: attempt to load the supplied HMAC secret as an asymmetric key and reject it if it parses. The reporter also recommends requiring explicit algorithm allowlists on verification.

    The release history is the other half. On PyPI, python-jose 3.3.0 was uploaded on June 5, 2021. Version 3.4.0 — the release carrying the incomplete fix — was uploaded on February 18, 2025. Version 3.5.0, the current release and the one the CVE names as vulnerable, was uploaded on May 28, 2025. Nothing since. The issue reporting the bypass has been open for roughly two months, and the CVE is now public.

    Why it matters

    The interesting failure here is not that a library had an algorithm-confusion bug. It is how the fix failed, and that failure mode generalizes past this library.

    The 2024 patch answered the question “is this key text that looks like an asymmetric public key?” The question it needed to answer was “is this key an asymmetric public key?” Those diverge the moment the same value can be spelled more than one way, and cryptographic key material can always be spelled more than one way: PEM, DER, JWK, OpenSSH, base64 of any of them. A validator built on string prefixes has to enumerate the spellings. A validator built on parsing has to enumerate nothing — if the bytes load as a public key, they are a public key, whatever they look like. The reporter’s recommendation is the structurally correct one, and it is also the one that does not need to be revisited when a sixth encoding shows up.

    This is the same shape as findings this publication has covered on other projects: a denylist bypassed because it enumerated bad inputs instead of defining good ones, an escape routine defeated because the grammar it escaped for was not the grammar the value ended up in. Guards that describe the attack rather than the property are the recurring cause. What makes this instance sharper is that the guard was written specifically as a security fix, carried a CVE number, and shipped in a release whose whole purpose was to close the hole.

    The second condition in the CVE — “when algorithms are not explicitly restricted” — is where an operator has leverage, and it is also where most of the real-world exposure sits. Passing an explicit allowlist to the decode call removes the attack entirely, because the token’s own header no longer gets a vote. Code that omits the allowlist is common, partly because omitting it works fine in testing and fails only against an attacker.

    The maintenance picture is what turns this from a bug into a decision. There is no patched version to move to. The last release predates the report by more than a year, the issue has drawn no maintainer response in two months, and the CVE is now published with the current version named as affected. That is not an accusation — python-jose is a volunteer project and nobody is owed a release — but it is the fact a team has to plan around. If your dependency graph contains python-jose, the realistic options are a mitigation you apply yourself, a fork, or a different library. Waiting is not one of them right now.

    Worth stating plainly: there is no reported exploitation, no KEV listing, and no federal deadline attached to this. The urgency here comes from the absence of a patch, not from anyone attacking it.

    What to do

    • Pass an explicit algorithms allowlist on every verification call. This is the mitigation, and it is complete. A decode that is told to accept only RS256 will not fall through to HMAC no matter what the token header claims or what encoding the key arrived in. Grep your codebase for decode calls that omit the parameter.
    • Audit what you hand in as the key. The attack needs the verification routine to be initialized with the public key as an HMAC secret. Code paths that pass a single key variable through to both asymmetric and symmetric verification are the ones to look at.
    • Treat your public key as public, because it is. The CVE’s precondition — “attackers holding the service’s public key” — is met by default for any service publishing a JWKS endpoint. Do not count that as a barrier.
    • Decide about the dependency. python-jose 3.5.0 is the current release and is the affected version. If you cannot guarantee the allowlist across every call site, including transitive callers, plan a migration rather than a wait.
    • Track GitHub issue 414. It is the only place a fix will surface first, and it is where a maintainer response, if one comes, will appear.

    Sourcing note

    Checked: the NVD record for CVE-2026-85394, retrieved from the NIST CVE API on September 4, 2026, from which the description, scores, vectors, and CWE are taken verbatim; GitHub issue 414 on mpdavis/python-jose, which supplied the guard shape, the report date, and the recommended fix; the GitHub Security Advisory GHSA-6c5p-j8vq-pqhj for the prior CVE-2024-33663, which supplied the affected and patched versions; the PyPI JSON metadata for python-jose, which supplied the upload dates for 3.3.0, 3.4.0, and 3.5.0.

    Unresolved: whether the maintainers have responded privately. The GitHub issue shows no public maintainer comment, which is not the same as no contact. Whether a fix is in progress. The CVE was assigned by VulnCheck as CNA rather than by the project, and VulnCheck’s advisory does not describe a coordination timeline; the report and the CVE come from two different parties, roughly two months apart. No exploitation has been reported by anyone, and no second party has independently reproduced the DER bypass in public.