Repository navigation
jwt.decode() accepts unsupported critical header extensions #406
Description
Summary
On python-jose commit 018b310ddb8b50dcfd09a0c152117835a21dd656,
jose.jwt.decode() accepts a valid HS256 JWT whose protected header declares
an unsupported critical extension:
{
"alg": "HS256",
"typ": "JWT",
"crit": ["x-custom-policy"],
"x-custom-policy": "require-mfa"
}The same replay accepts the matched no-crit control token and returns the
expected claims. The witness token also verifies successfully and returns the
same claims instead of failing closed on the unsupported critical extension.
As a same-host control, PyJWT 2.12.1 rejects the witness token with
InvalidTokenError: Unsupported critical extension: x-custom-policy.
Environment
- Host OS: Ubuntu 24.04.4 LTS
- Architecture: x86_64
- Toolchain: Python 3.14.2
- Build mode: direct source-tree import via
PYTHONPATH; no local source modifications
Affected Commit
- reproduced commit:
018b310ddb8b50dcfd09a0c152117835a21dd656
Reproduction
Prerequisites:
- Git
- Python 3
python3 -m venv- internet access to install
python-joseruntime dependencies into a fresh virtualenv
Build:
mkdir python-jose-crit-repro
cd python-jose-crit-repro
git clone https://github.com/mpdavis/python-jose.git python-jose
git clone https://github.com/jpadilla/pyjwt.git pyjwt
cd python-jose
git checkout 018b310ddb8b50dcfd09a0c152117835a21dd656
cd ../pyjwt
git checkout dcc27a9d3182a2349c30b160758785c6ce7a6508
cd ..
python3 -m venv .venv
./.venv/bin/pip install cryptography ecdsa rsa pyasn1Use these exact JWTs:
Control token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJpYXQiOjE3MDAwMDAwMDB9.bY5fWpkENiXrTv6SNLjfocPxGkSD3NSSFTg2SurXt8U
Witness token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImNyaXQiOlsieC1jdXN0b20tcG9saWN5Il0sIngtY3VzdG9tLXBvbGljeSI6InJlcXVpcmUtbWZhIn0.eyJzdWIiOiIxMjMiLCJpYXQiOjE3MDAwMDAwMDB9.izNblYLSgSQ1aJBq1iCtZ-xvMJd_HFnvWgj6Q_yM5eU
Run:
PYTHONPATH="$PWD/python-jose" ./.venv/bin/python -c 'from jose import jwt; control="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJpYXQiOjE3MDAwMDAwMDB9.bY5fWpkENiXrTv6SNLjfocPxGkSD3NSSFTg2SurXt8U"; witness="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImNyaXQiOlsieC1jdXN0b20tcG9saWN5Il0sIngtY3VzdG9tLXBvbGljeSI6InJlcXVpcmUtbWZhIn0.eyJzdWIiOiIxMjMiLCJpYXQiOjE3MDAwMDAwMDB9.izNblYLSgSQ1aJBq1iCtZ-xvMJd_HFnvWgj6Q_yM5eU"; print(jwt.decode(control, "secret", algorithms=["HS256"])); print(jwt.decode(witness, "secret", algorithms=["HS256"]))'
PYTHONPATH="$PWD/pyjwt" ./.venv/bin/python -c 'import jwt; witness="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImNyaXQiOlsieC1jdXN0b20tcG9saWN5Il0sIngtY3VzdG9tLXBvbGljeSI6InJlcXVpcmUtbWZhIn0.eyJzdWIiOiIxMjMiLCJpYXQiOjE3MDAwMDAwMDB9.izNblYLSgSQ1aJBq1iCtZ-xvMJd_HFnvWgj6Q_yM5eU"; print(jwt.__version__); print(jwt.decode(witness, "secret", algorithms=["HS256"]))'Observed Result
python-jose accepts both tokens and returns verified claims:
{'sub': '123', 'iat': 1700000000}
{'sub': '123', 'iat': 1700000000}
PyJWT rejects the witness token:
2.12.1
Traceback (most recent call last):
...
jwt.exceptions.InvalidTokenError: Unsupported critical extension: x-custom-policy
Controls:
- the no-
critcontrol token still verifies successfully withpython-jose - the witness token differs only by the protected-header
critdeclaration and
x-custom-policyvalue
Why This Looks Like A Real Bug
- The exercised surface is the public
jose.jwt.decode()verification API
documented in the current README. - The witness token has a valid HS256 signature; the bug is not signature
breakage but missing must-understand enforcement on the protected header. - The current
jose.jwt/jose.jwsverification path never inspectscrit
before returning verified claims. - A same-host PyJWT control fails closed on the same witness token, which
strengthens the interpretation that the missingcritcheck is the relevant
differential.
Duplicate Check / Prior Art
- I checked the current target history with
git log --oneline --decorate --grep='crit' --alland found no matching fix
commit inpython-jose. - I checked public GitHub issues and PRs in
mpdavis/python-josefor
crit,critical extension,x-custom-policy, and
Unsupported critical extensionand did not find an exact prior public
match for this witness.
Suggested Fix Direction
I do not have a tested patch yet.
- Inspect the protected header after parsing and before returning verified
claims. - Reject malformed
critvalues, such as non-array or non-string entries. - Reject any declared critical extension that the library does not explicitly
support.
Scope Note
This report is limited to the fresh current-head witness above: a public JWT
verification path accepts a token whose protected header declares an
unsupported critical extension and still returns verified claims.
Activity