{"id":"GHSA-c32j-vqhx-rx3x","summary":"ruby-jwt: Empty-key HMAC bypass; cross-language sibling of CVE-2026-44351","details":"`JWT.decode(token, '', true, algorithm: 'HS256')` accepts an attacker-forged token.\n`OpenSSL::HMAC.digest('SHA256', '', payload)` returns a valid digest under an empty key, and no `raise\n  InvalidKeyError if key.empty?` precondition exists in the HMAC algorithm.\n\n```\nJWT.decode(token, \"\", true, algorithm: 'HS256')\n  -\u003e JWA::Hmac.verify(verification_key: \"\", ...)\n  -\u003e OpenSSL::HMAC.digest('SHA256', \"\", signing_input) == signature\n```\n\nThe same path is reached when a keyfinder block or key_finder: argument returns \"\", nil, or an\narray containing nil for an unknown key. JWT::Decode#find_key only rejects literal nil and empty\narrays, and JWT::JWA::Hmac silently coerces nil to \"\" (signing_key ||= '') before signing.\n\n```\nJWT.decode(token, nil, true, algorithms: ['HS256']) { |_h| \"\" }\n  -\u003e find_key returns \"\"               # \"\" && !Array(\"\").empty? == true\n  -\u003e JWA::Hmac.verify(verification_key: \"\", ...)\n  -\u003e verifies\n```\nCommon application patterns that produce the unsafe value: `redis.get(\"kid:#{kid}\").to_s`, ORM string columns with `default: ''`, `ENV['SECRET'] || '', Hash.new('')` lookups, [primary, fallback] where fallback may be nil. Applications passing a non-empty static key:, or whose keyfinder returns nil / raises on miss, are not affected.\n\nThe existing `enforce_hmac_key_length` option would block this but defaults to false. On OpenSSL ≥ 3.5 the empty-key HMAC.digest call no longer raises, so the OpenSSL-3.0 rescue in JWA::Hmac#sign does not fire.\n\nAffects HS256/HS384/HS512 via both JWT.decode (positional key and block keyfinder) and\n`JWT::EncodedToken#verify_signature!(key_finder:)`","aliases":["CVE-2026-45363"],"modified":"2026-07-17T21:16:40.274337639Z","published":"2026-05-18T17:24:55Z","database_specific":{"github_reviewed":true,"github_reviewed_at":"2026-05-18T17:24:55Z","nvd_published_at":null,"cwe_ids":["CWE-1391","CWE-287","CWE-326"],"severity":"HIGH"},"references":[{"type":"WEB","url":"https://github.com/jwt/ruby-jwt/security/advisories/GHSA-c32j-vqhx-rx3x"},{"type":"WEB","url":"https://github.com/jwt/ruby-jwt/issues/724"},{"type":"WEB","url":"https://github.com/jwt/ruby-jwt/commit/db560b769a07bd9724e77ff505011ac01872106f"},{"type":"PACKAGE","url":"https://github.com/jwt/ruby-jwt"},{"type":"WEB","url":"https://github.com/jwt/ruby-jwt/releases/tag/v2.10.3"},{"type":"WEB","url":"https://github.com/jwt/ruby-jwt/releases/tag/v3.2.0"},{"type":"WEB","url":"https://github.com/rubysec/ruby-advisory-db/blob/master/gems/jwt/CVE-2026-45363.yml"},{"type":"WEB","url":"https://www.cve.org/CVERecord?id=CVE-2026-45363"}],"affected":[{"package":{"name":"jwt","ecosystem":"RubyGems","purl":"pkg:gem/jwt"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"3.0.0"},{"fixed":"3.2.0"}]}],"versions":["3.0.0","3.1.0","3.1.1","3.1.2"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/05/GHSA-c32j-vqhx-rx3x/GHSA-c32j-vqhx-rx3x.json"}},{"package":{"name":"jwt","ecosystem":"RubyGems","purl":"pkg:gem/jwt"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.10.3"}]}],"versions":["0.1.1","0.1.10","0.1.11","0.1.13","0.1.2","0.1.3","0.1.4","0.1.5","0.1.6","0.1.7","0.1.8","1.0.0","1.2.0","1.2.1","1.3.0","1.4.0","1.4.1","1.5.0","1.5.1","1.5.2","1.5.4","1.5.5","1.5.6","2.0.0","2.0.0.beta1","2.1.0","2.10.0","2.10.1","2.10.2","2.2.0","2.2.0.pre.beta.0","2.2.1","2.2.2","2.2.3","2.3.0","2.4.0","2.4.0.beta1","2.4.1","2.5.0","2.6.0","2.7.0","2.7.1","2.8.0","2.8.1","2.8.2","2.9.0","2.9.1","2.9.2","2.9.3"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/05/GHSA-c32j-vqhx-rx3x/GHSA-c32j-vqhx-rx3x.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N"}]}