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-jose runtime 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 pyasn1
Use 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-
crit control token still verifies successfully with python-jose
- the witness token differs only by the protected-header
crit declaration and
x-custom-policy value
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.jws verification path never inspects crit
before returning verified claims.
- A same-host PyJWT control fails closed on the same witness token, which
strengthens the interpretation that the missing crit check is the relevant
differential.
Duplicate Check / Prior Art
- I checked the current target history with
git log --oneline --decorate --grep='crit' --all and found no matching fix
commit in python-jose.
- I checked public GitHub issues and PRs in
mpdavis/python-jose for
crit, critical extension, x-custom-policy, and
Unsupported critical extension and 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
crit values, 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.
Summary
On
python-josecommit018b310ddb8b50dcfd09a0c152117835a21dd656,jose.jwt.decode()accepts a valid HS256 JWT whose protected header declaresan unsupported critical extension:
{ "alg": "HS256", "typ": "JWT", "crit": ["x-custom-policy"], "x-custom-policy": "require-mfa" }The same replay accepts the matched no-
critcontrol token and returns theexpected 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
PYTHONPATH; no local source modificationsAffected Commit
018b310ddb8b50dcfd09a0c152117835a21dd656Reproduction
Prerequisites:
python3 -m venvpython-joseruntime dependencies into a fresh virtualenvBuild:
Use these exact JWTs:
Control token:
Witness token:
Run:
Observed Result
python-joseaccepts both tokens and returns verified claims:PyJWT rejects the witness token:
Controls:
critcontrol token still verifies successfully withpython-josecritdeclaration andx-custom-policyvalueWhy This Looks Like A Real Bug
jose.jwt.decode()verification APIdocumented in the current README.
breakage but missing must-understand enforcement on the protected header.
jose.jwt/jose.jwsverification path never inspectscritbefore returning verified claims.
strengthens the interpretation that the missing
critcheck is the relevantdifferential.
Duplicate Check / Prior Art
git log --oneline --decorate --grep='crit' --alland found no matching fixcommit in
python-jose.mpdavis/python-joseforcrit,critical extension,x-custom-policy, andUnsupported critical extensionand did not find an exact prior publicmatch for this witness.
Suggested Fix Direction
I do not have a tested patch yet.
claims.
critvalues, such as non-array or non-string entries.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.