jwt: add opt-in strict_serialization to enforce compact form#381
Merged
Conversation
RFC 7519 requires a JWT to use the JWS/JWE Compact Serialization, but JWT.deserialize currently dispatches on the number of dots in the input and will also accept a JSON Serialization whose surrounding JSON happens to contain exactly two (JWS) or four (JWE) dot characters. A dotted value in an unprotected or per-recipient header makes that reachable, so a JSON Serialization can be parsed where only a compact token should be allowed. Add an opt-in strict_serialization constructor flag (default False, so existing behavior is unchanged). When enabled, deserialize rejects any JSON-serialized token with a clear RFC 7519 error and only accepts the compact representation, reducing the parser attack surface. Detection reuses the same json_decode check that JWS/JWE deserialize already rely on to tell the two representations apart. Add regression tests covering JWS and JWE for both the flag-on (reject JSON, accept compact) and the default flag-off (preserve current behavior) paths. Fixes #342 Signed-off-by: Arpit Jain <arpitjain099@gmail.com>
simo5
approved these changes
Jun 5, 2026
simo5
left a comment
Member
There was a problem hiding this comment.
Excellent proposal, I really like how you addressed it.
Member
|
Thank you! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #342
RFC 7519 (Section 7.1, step 8 and the definition of a JWT) requires that a JWT be represented using the JWS or JWE Compact Serialization.
JWT.deserializetoday decides what to do based onjwt.count('.'): two dots route to aJWS, four dots to aJWE. Because the JWS/JWEdeserializemethods tryjson_decodefirst, a JSON-serialized token whose surrounding JSON happens to contain exactly two (JWS) or four (JWE).characters is accepted as a JWT. That is reachable in practice: a dotted value in an unprotected header ({"kid":"a.b.c"}) or a per-recipient header lands the count on 2 or 4, so a JSON Serialization gets parsed where only a compact token should be.As the issue notes this is not a vulnerability on its own, but parsing a representation the spec disallows is extra attack surface that some callers would rather not expose.
What this changes
A new opt-in
strict_serializationkeyword argument onJWT(defaultFalse). When it isTrue,deserializerejects any JSON-serialized JWS/JWE with a clearValueErrorand only accepts the compact form. Detection reuses the samejson_decodecheck thatJWS.deserialize/JWE.deserializealready use to tell the two representations apart (a complete compact token never decodes to a JSON object).The default is
False, so existing behavior is completely unchanged. This mirrors the existing knob style on this class (expected_type, theJWT_expect_typemodule flag) and has no effect on token creation, which already only emits the compact serialization.Before / after
Tests
Added
test_jwt_strict_serialization_jwsandtest_jwt_strict_serialization_jwecovering, for both signed and encrypted tokens:deserializeand via the constructorjwt=path), compact acceptedFull suite is green (
python -m pytest jwcrypto/tests.py), flake8 clean on the changed files.I went with an opt-in flag deliberately to avoid changing the default. If you would rather have a differently named argument (for example
serialization=taking"compact"/"any"), or want this surfaced as a module-level switch instead, happy to adjust.