jsonwebtoken has Type Confusion that leads to potential authorization bypass
Description
jsonwebtoken is a JWT lib in rust. Prior to version 10.3.0, there is a Type Confusion vulnerability in jsonwebtoken, specifically, in its claim validation logic. When a standard claim (such as nbf or exp) is provided with an incorrect JSON type (Like a String instead of a Number), the library’s internal parsing mechanism marks the claim as “FailedToParse”. Crucially, the validation logic treats this “FailedToParse” state identically to “NotPresent”. This means that if a check is enabled (like: validate_nbf = true), but the claim is not explicitly marked as required in required_spec_claims, the library will skip the validation check entirely for the malformed claim, treating it as if it were not there. This allows attackers to bypass critical time-based security restrictions (like “Not Before” checks) and commit potential authentication and authorization bypasses. This issue has been patched in version 10.3.0.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Type confusion in jsonwebtoken up to 10.2.0 lets attackers bypass time-based JWT claim checks (nbf, exp) by using unexpected JSON types, leading to authentication and authorization bypass.
Vulnerability
Overview
jsonwebtoken, a Rust JWT library before version 10.3.0, contains a type confusion vulnerability in its claim validation logic. When a standard claim such as nbf (Not Before) or exp (Expiration Time) is supplied as an incorrect JSON type (e.g., a string instead of a number), the internal parser marks the claim as FailedToParse. The validation then treats FailedToParse identically to NotPresent, meaning the claim is simply ignored if not listed in required_spec_claims [1][3].
Exploitation
An attacker can craft a JWT where a critical claim like nbf is sent as "99999999999" (a string) instead of a numeric value. Since the library expects a u64, serde deserialization fails, producing a FailedToParse state. The validation code uses matches! to check only for TryParse::Parsed, so it silently skips validation for that claim if required_spec_claims does not explicitly demand it [3]. No authentication or special network position is needed beyond the ability to present a JWT to a vulnerable service.
Impact
Attackers can bypass time-based security checks, such as the nbf (Not Before) constraint. This allows tokens that should be considered immature (i.e., not yet valid) to be accepted, potentially enabling authentication or authorization bypass. For instance, a token that should not be usable until a future time could be used immediately, or an expired token could be accepted if exp is similarly malformed [1][3].
Mitigation
The vulnerability has been patched in jsonwebtoken version 10.3.0. The fix introduces a new InvalidClaimFormat error variant and adds validation logic that rejects claims with unexpected types [4]. Users should upgrade to 10.3.0 or later; if immediate upgrade is not possible, temporarily marking the affected claims as required in required_spec_claims may reduce risk, though the patch is the definitive solution [3].
AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
jsonwebtokencrates.io | < 10.3.0 | 10.3.0 |
Affected products
2- Range: <10.3.0
- Keats/jsonwebtokenv5Range: < 10.3.0
Patches
1abbc3076742cFix type confusion
5 files changed · +71 −2
Cargo.toml+1 −1 modified@@ -1,6 +1,6 @@ [package] name = "jsonwebtoken" -version = "10.2.0" +version = "10.3.0" authors = ["Vincent Prouillet <hello@vincentprouillet.com>"] license = "MIT" readme = "README.md"
CHANGELOG.md+2 −1 modified@@ -1,8 +1,9 @@ # Changelog -## 10.3.0 (unreleased) +## 10.3.0 (2026-01-27) - Export everything needed to define your own CryptoProvider +- Fix type confusion with exp/nbf when not required ## 10.2.0 (2025-11-06)
src/errors.rs+4 −0 modified@@ -58,6 +58,8 @@ pub enum ErrorKind { // Validation errors /// When a claim required by the validation is not present MissingRequiredClaim(String), + /// When a claim has an invalid format (eg string instead of integer) + InvalidClaimFormat(String), /// When a token’s `exp` claim indicates that it has expired ExpiredSignature, /// When a token’s `iss` claim does not match the expected issuer @@ -98,6 +100,7 @@ impl StdError for Error { ErrorKind::ExpiredSignature => None, ErrorKind::MissingAlgorithm => None, ErrorKind::MissingRequiredClaim(_) => None, + ErrorKind::InvalidClaimFormat(_) => None, ErrorKind::InvalidIssuer => None, ErrorKind::InvalidAudience => None, ErrorKind::InvalidSubject => None, @@ -131,6 +134,7 @@ impl fmt::Display for Error { | ErrorKind::InvalidEddsaKey | ErrorKind::InvalidAlgorithmName => write!(f, "{:?}", self.0), ErrorKind::MissingRequiredClaim(c) => write!(f, "Missing required claim: {}", c), + ErrorKind::InvalidClaimFormat(c) => write!(f, "Invalid format for claim: {}", c), ErrorKind::InvalidRsaKey(msg) => write!(f, "RSA key invalid: {}", msg), ErrorKind::Signing(msg) => write!(f, "Signing failed: {}", msg), ErrorKind::Json(err) => write!(f, "JSON error: {}", err),
src/validation.rs+8 −0 modified@@ -274,6 +274,14 @@ pub(crate) fn validate(claims: ClaimsForValidation, options: &Validation) -> Res if options.validate_exp || options.validate_nbf { let now = get_current_timestamp(); + // Reject malformed exp/nbf claim when validation is enabled + if options.validate_exp && matches!(claims.exp, TryParse::FailedToParse) { + return Err(new_error(ErrorKind::InvalidClaimFormat("exp".to_string()))); + } + if options.validate_nbf && matches!(claims.nbf, TryParse::FailedToParse) { + return Err(new_error(ErrorKind::InvalidClaimFormat("nbf".to_string()))); + } + if matches!(claims.exp, TryParse::Parsed(exp) if exp < options.reject_tokens_expiring_in_less_than) { return Err(new_error(ErrorKind::InvalidToken));
tests/hmac.rs+56 −0 modified@@ -290,3 +290,59 @@ fn verify_hs256_rfc7517_appendix_a1() { let c = decode::<C>(token, &key, &validation).unwrap(); assert_eq!(c.claims.iss, "joe"); } + +// Regression tests for type confusion vulnerability where malformed claims +// (eg nbf or exp as strings) were silently treated as "not present" when not required +#[derive(Debug, Serialize)] +struct ClaimsWithStringNbf { + sub: String, + nbf: String, // should be a number +} + +#[derive(Debug, Serialize)] +struct ClaimsWithStringExp { + sub: String, + exp: String, // should be a number +} + +#[test] +#[wasm_bindgen_test] +fn test_string_nbf_rejected_when_validate_nbf_enabled() { + // Create token with nbf as string (malformed) + let claims = ClaimsWithStringNbf { + sub: "test".to_string(), + nbf: "99999999999".to_string(), // Far future as string + }; + let token = encode(&Header::default(), &claims, &EncodingKey::from_secret(b"secret")).unwrap(); + + let mut validation = Validation::new(Algorithm::HS256); + validation.validate_nbf = true; + validation.required_spec_claims = std::collections::HashSet::new(); + + let result = + decode::<serde_json::Value>(&token, &DecodingKey::from_secret(b"secret"), &validation); + + assert!(result.is_err()); + assert_eq!(result.unwrap_err().into_kind(), ErrorKind::InvalidClaimFormat("nbf".to_string())); +} + +#[test] +#[wasm_bindgen_test] +fn test_string_exp_rejected_when_validate_exp_enabled() { + // Create token with exp as string (malformed) + let claims = ClaimsWithStringExp { + sub: "test".to_string(), + exp: "99999999999".to_string(), // Far future as string + }; + let token = encode(&Header::default(), &claims, &EncodingKey::from_secret(b"secret")).unwrap(); + + let mut validation = Validation::new(Algorithm::HS256); + validation.validate_exp = true; + validation.required_spec_claims = std::collections::HashSet::new(); + + let result = + decode::<serde_json::Value>(&token, &DecodingKey::from_secret(b"secret"), &validation); + + assert!(result.is_err()); + assert_eq!(result.unwrap_err().into_kind(), ErrorKind::InvalidClaimFormat("exp".to_string())); +}
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/advisories/GHSA-h395-gr6q-cpjcghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-25537ghsaADVISORY
- github.com/Keats/jsonwebtoken/commit/abbc3076742c4161347bc6b8bf4aa5eb86e1dc01ghsax_refsource_MISCWEB
- github.com/Keats/jsonwebtoken/security/advisories/GHSA-h395-gr6q-cpjcghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.