VYPR
Moderate severityNVD Advisory· Published Feb 4, 2026· Updated Feb 5, 2026

jsonwebtoken has Type Confusion that leads to potential authorization bypass

CVE-2026-25537

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.

PackageAffected versionsPatched versions
jsonwebtokencrates.io
< 10.3.010.3.0

Affected products

2

Patches

1
abbc3076742c

Fix type confusion

https://github.com/Keats/jsonwebtokenVincent ProuilletJan 27, 2026via ghsa
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

News mentions

0

No linked articles in our index yet.