Skip to content

jwt.decode() accepts unsupported critical header extensions #406

@fuyu0425

Description

@fuyu0425

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions