From 9d2064bccc0ac60884906d9dd89ace589f9f8281 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Padilla?= Date: Mon, 3 Aug 2026 03:22:08 -0700 Subject: [PATCH] algorithms: reject raw JWK documents as HMAC secrets Reject JSON Web Key documents passed directly to HMACAlgorithm.prepare_key. Public asymmetric JWK data must not be accepted as an HMAC secret when callers permit mixed algorithm families. CVE: CVE-2026-48526 Upstream-Status: Backport [https://github.com/jpadilla/pyjwt/commit/95791b1759b8aa4f2203575d344d5c78564cdc81] Backport Changes: - Extracted only the CVE-2026-48526 raw-JWK rejection and regression tests from the bundled upstream 2.13.0 commit. The other requested CVE fixes are carried as separate patches. - Adapted the hunk and test locations to PyJWT 2.8.0, renamed the upstream local variable `jwk_obj` to `jwk`, and omitted explanatory comments; the validation logic and assertions are unchanged. - Excluded the separate empty-HMAC-key hardening bundled in the same file. - Omitted the 2.13.0 version and changelog updates, CVE-2026-48523 (which does not affect 2.8.0), and unrelated hardening from the bundled commit. (cherry picked from commit 95791b1759b8aa4f2203575d344d5c78564cdc81) Signed-off-by: Hetvi Thakar --- jwt/algorithms.py | 13 +++++++++++++ tests/test_algorithms.py | 22 ++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/jwt/algorithms.py b/jwt/algorithms.py index ed18715..b6303ed 100644 --- a/jwt/algorithms.py +++ b/jwt/algorithms.py @@ -270,6 +270,19 @@ class HMACAlgorithm(Algorithm): " should not be used as an HMAC secret." ) + stripped = key_bytes.lstrip() + if stripped.startswith(b"{"): + try: + jwk = json.loads(key_bytes) + except ValueError: + jwk = None + if isinstance(jwk, dict) and "kty" in jwk: + raise InvalidKeyError( + "The specified key looks like a JWK and should not be " + "used directly as an HMAC secret. Load it via " + "PyJWK / HMACAlgorithm.from_jwk first." + ) + return key_bytes @overload diff --git a/tests/test_algorithms.py b/tests/test_algorithms.py index 1a39552..e5220c6 100644 --- a/tests/test_algorithms.py +++ b/tests/test_algorithms.py @@ -108,6 +108,28 @@ class TestAlgorithms: with pytest.raises(InvalidKeyError): algo.from_jwk(keyfile.read()) + @pytest.mark.parametrize( + "jwk_file", + [ + "jwk_rsa_pub.json", + "jwk_ec_pub_P-256.json", + "jwk_okp_pub_Ed25519.json", + "jwk_hmac.json", + ], + ) + def test_hmac_prepare_key_rejects_jwk_json(self, jwk_file: str) -> None: + algo = HMACAlgorithm(HMACAlgorithm.SHA256) + + with open(key_path(jwk_file)) as keyfile: + with pytest.raises(InvalidKeyError, match="looks like a JWK"): + algo.prepare_key(keyfile.read()) + + def test_hmac_prepare_key_accepts_json_without_kty(self) -> None: + algo = HMACAlgorithm(HMACAlgorithm.SHA256) + + key = algo.prepare_key('{"this": "is just a json-shaped secret"}') + assert key == b'{"this": "is just a json-shaped secret"}' + @crypto_required def test_rsa_should_parse_pem_public_key(self): algo = RSAAlgorithm(RSAAlgorithm.SHA256)