Version in base suite: 2.7.1-1 Base version: ruby-jwt_2.7.1-1 Target version: ruby-jwt_2.7.1-1+deb13u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/r/ruby-jwt/ruby-jwt_2.7.1-1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/r/ruby-jwt/ruby-jwt_2.7.1-1+deb13u1.dsc changelog | 8 + patches/CVE-2026-45363.patch | 236 +++++++++++++++++++++++++++++++++++++++++++ patches/series | 1 3 files changed, 245 insertions(+) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpf5kjcnlv/ruby-jwt_2.7.1-1.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpf5kjcnlv/ruby-jwt_2.7.1-1+deb13u1.dsc: no acceptable signature found diff -Nru ruby-jwt-2.7.1/debian/changelog ruby-jwt-2.7.1/debian/changelog --- ruby-jwt-2.7.1/debian/changelog 2023-09-06 21:09:00.000000000 +0000 +++ ruby-jwt-2.7.1/debian/changelog 2026-09-28 07:16:44.000000000 +0000 @@ -1,3 +1,11 @@ +ruby-jwt (2.7.1-1+deb13u1) trixie-security; urgency=high + + * Team upload. + * Fix CVE-2026-45363: accepts an attacker-forged token when HMAC + verification key was empty, nil. + + -- Abhijith PA Mon, 28 Sep 2026 12:46:44 +0530 + ruby-jwt (2.7.1-1) unstable; urgency=medium * Team upload diff -Nru ruby-jwt-2.7.1/debian/patches/CVE-2026-45363.patch ruby-jwt-2.7.1/debian/patches/CVE-2026-45363.patch --- ruby-jwt-2.7.1/debian/patches/CVE-2026-45363.patch 1970-01-01 00:00:00.000000000 +0000 +++ ruby-jwt-2.7.1/debian/patches/CVE-2026-45363.patch 2026-09-28 06:48:00.000000000 +0000 @@ -0,0 +1,236 @@ +Description: CVE-2026-45363 + +Author: Abhijith PA +Origin: https://github.com/jwt/ruby-jwt/commit/9820020869ad147b941e49d96ab8beba35532964 +Forwarded: not-needed +Last-Update: 2026-09-23 + +--- a/lib/jwt/algos/hmac.rb ++++ b/lib/jwt/algos/hmac.rb +@@ -14,23 +14,20 @@ module JWT + SUPPORTED = MAPPING.keys + + def sign(algorithm, msg, key) +- key ||= '' +- +- raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String) +- ++ ensure_valid_key!(key) + OpenSSL::HMAC.digest(MAPPING[algorithm].new, key, msg) +- rescue OpenSSL::HMACError => e +- if key == '' && e.message == 'EVP_PKEY_new_mac_key: malloc failure' +- raise JWT::DecodeError, 'OpenSSL 3.0 does not support nil or empty hmac_secret' +- end +- +- raise e + end + + def verify(algorithm, key, signing_input, signature) ++ ensure_valid_key!(key) + SecurityUtils.secure_compare(signature, sign(algorithm, signing_input, key)) + end + ++ def ensure_valid_key!(key) ++ raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String) ++ raise JWT::DecodeError, 'HMAC key cannot be empty' if key.empty? ++ end ++ + # Copy of https://github.com/rails/rails/blob/v7.0.3.1/activesupport/lib/active_support/security_utils.rb + # rubocop:disable Naming/MethodParameterName, Style/StringLiterals, Style/NumericPredicate + module SecurityUtils +--- a/spec/integration/readme_examples_spec.rb ++++ b/spec/integration/readme_examples_spec.rb +@@ -28,17 +28,6 @@ RSpec.describe 'README.md code test' do + ] + end + +- it 'decodes with HMAC algorithm without secret key' do +- #pending 'Different behaviour on OpenSSL 3.0 (https://github.com/openssl/openssl/issues/13089)' if ::JWT.openssl_3_hmac_empty_key_regression? +- token = JWT.encode payload, nil, 'HS256' +- decoded_token = JWT.decode token, nil, false +- +- expect(token).to eq 'eyJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.pVzcY2dX8JNM3LzIYeP2B1e1Wcpt1K3TWVvIYSF4x-o' +- expect(decoded_token).to eq [ +- { 'data' => 'test' }, +- { 'alg' => 'HS256' } +- ] +- end + + it 'RSA' do + rsa_private = OpenSSL::PKey::RSA.generate 2048 +--- a/spec/jwt/algos/hmac_spec.rb ++++ b/spec/jwt/algos/hmac_spec.rb +@@ -4,68 +4,39 @@ RSpec.describe ::JWT::Algos::Hmac do + describe '.sign' do + subject { described_class.sign('HS256', 'test', hmac_secret) } + +- # Address OpenSSL 3.0 errors with empty hmac_secret - https://github.com/jwt/ruby-jwt/issues/526 ++ # GHSA-c32j-vqhx-rx3x: empty/nil keys must be rejected before reaching OpenSSL, ++ # so a forged token signed with "" cannot verify. + context 'when nil hmac_secret is passed' do + let(:hmac_secret) { nil } +- context 'when OpenSSL 3.0 raises a malloc failure' do +- before do +- allow(OpenSSL::HMAC).to receive(:digest).and_raise(OpenSSL::HMACError.new('EVP_PKEY_new_mac_key: malloc failure')) +- end + + it 'raises JWT::DecodeError' do +- expect { subject }.to raise_error(JWT::DecodeError, 'OpenSSL 3.0 does not support nil or empty hmac_secret') ++ expect { subject }.to raise_error(JWT::DecodeError, 'HMAC key expected to be a String') + end +- end + +- context 'when OpenSSL raises any other error' do +- before do +- allow(OpenSSL::HMAC).to receive(:digest).and_raise(OpenSSL::HMACError.new('Another Random Error')) +- end +- +- it 'raises the original error' do +- expect { subject }.to raise_error(OpenSSL::HMACError, 'Another Random Error') +- end +- end +- +- context 'when other versions of openssl do not raise an exception' do +- let(:response) { Base64.decode64("Q7DO+ZJl+eNMEOqdNQGSbSezn1fG1nRWHYuiNueoGfs=\n") } +- before do +- allow(OpenSSL::HMAC).to receive(:digest).and_return(response) +- end +- +- it { is_expected.to eql(response) } ++ it 'does not call OpenSSL::HMAC.digest' do ++ expect(OpenSSL::HMAC).not_to receive(:digest) ++ expect { subject }.to raise_error(JWT::DecodeError) + end + end + + context 'when blank hmac_secret is passed' do + let(:hmac_secret) { '' } +- context 'when OpenSSL 3.0 raises a malloc failure' do +- before do +- allow(OpenSSL::HMAC).to receive(:digest).and_raise(OpenSSL::HMACError.new('EVP_PKEY_new_mac_key: malloc failure')) +- end + +- it 'raises JWT::DecodeError' do +- expect { subject }.to raise_error(JWT::DecodeError, 'OpenSSL 3.0 does not support nil or empty hmac_secret') +- end ++ it 'raises JWT::DecodeError' do ++ expect { subject }.to raise_error(JWT::DecodeError, 'HMAC key cannot be empty') + end + +- context 'when OpenSSL raises any other error' do +- before do +- allow(OpenSSL::HMAC).to receive(:digest).and_raise(OpenSSL::HMACError.new('Another Random Error')) +- end +- +- it 'raises the original error' do +- expect { subject }.to raise_error(OpenSSL::HMACError, 'Another Random Error') +- end ++ it 'does not call OpenSSL::HMAC.digest' do ++ expect(OpenSSL::HMAC).not_to receive(:digest) ++ expect { subject }.to raise_error(JWT::DecodeError) + end ++ end + +- context 'when other versions of openssl do not raise an exception' do +- let(:response) { Base64.decode64("Q7DO+ZJl+eNMEOqdNQGSbSezn1fG1nRWHYuiNueoGfs=\n") } +- before do +- allow(OpenSSL::HMAC).to receive(:digest).and_return(response) +- end ++ context 'when non-String hmac_secret is passed' do ++ let(:hmac_secret) { 123 } + +- it { is_expected.to eql(response) } ++ it 'raises JWT::DecodeError' do ++ expect { subject }.to raise_error(JWT::DecodeError, 'HMAC key expected to be a String') + end + end + +@@ -101,4 +72,51 @@ RSpec.describe ::JWT::Algos::Hmac do + end + end + end ++ ++ describe '.verify' do ++ subject { described_class.verify('HS256', hmac_secret, 'test', signature) } ++ # GHSA-c32j-vqhx-rx3x: empty/nil keys must be rejected before reaching OpenSSL, ++ # so a forged token signed with "" cannot verify. ++ ++ context 'when verification_key is nil' do ++ let(:signature) { '' } ++ let(:hmac_secret) { nil } ++ ++ it 'raises JWT::DecodeError' do ++ expect { subject }.to raise_error(JWT::DecodeError, 'HMAC key expected to be a String') ++ end ++ ++ it 'does not call OpenSSL::HMAC.digest' do ++ expect(OpenSSL::HMAC).not_to receive(:digest) ++ expect { subject }.to raise_error(JWT::DecodeError) ++ end ++ end ++ ++ ++ context 'when verification_key is an empty string' do ++ let(:signature) { '' } ++ let(:hmac_secret) { '' } ++ ++ ++ it 'raises JWT::DecodeError' do ++ expect { subject }.to raise_error(JWT::DecodeError, 'HMAC key cannot be empty') ++ end ++ ++ ++ it 'does not call OpenSSL::HMAC.digest' do ++ expect(OpenSSL::HMAC).not_to receive(:digest) ++ expect { subject }.to raise_error(JWT::DecodeError) ++ end ++ end ++ ++ ++ context 'when verification_key is not a String' do ++ let(:signature) { '' } ++ let(:hmac_secret) { 123 } ++ ++ it 'raises JWT::DecodeError' do ++ expect { subject }.to raise_error(JWT::DecodeError, 'HMAC key expected to be a String') ++ end ++ end ++ end + end +--- a/spec/jwt_spec.rb ++++ b/spec/jwt_spec.rb +@@ -629,20 +629,6 @@ RSpec.describe JWT do + end + end + +- context 'when hmac algorithm is used without secret key' do +- it 'encodes payload' do +- #pending 'Different behaviour on OpenSSL 3.0 (https://github.com/openssl/openssl/issues/13089)' if ::JWT.openssl_3_hmac_empty_key_regression? +- payload = { a: 1, b: 'b' } +- +- token = JWT.encode(payload, '', 'HS256') +- +- expect do +- token_without_secret = JWT.encode(payload, nil, 'HS256') +- expect(token).to eq(token_without_secret) +- end.not_to raise_error +- end +- end +- + context 'algorithm case insensitivity' do + let(:payload) { { 'a' => 1, 'b' => 'b' } } + +@@ -759,13 +745,6 @@ RSpec.describe JWT do + end + end + +- describe 'when token signed with nil and decoded with nil' do +- let(:no_key_token) { ::JWT.encode(payload, nil, 'HS512') } +- it 'raises JWT::DecodeError' do +- #pending 'Different behaviour on OpenSSL 3.0 (https://github.com/openssl/openssl/issues/13089)' if ::JWT.openssl_3_hmac_empty_key_regression? +- expect { ::JWT.decode(no_key_token, nil, true, algorithms: 'HS512') }.to raise_error(JWT::DecodeError, 'No verification key available') +- end +- end + + context 'when token ends with a newline char' do + let(:token) { "#{JWT.encode(payload, 'secret', 'HS256')}\n" } diff -Nru ruby-jwt-2.7.1/debian/patches/series ruby-jwt-2.7.1/debian/patches/series --- ruby-jwt-2.7.1/debian/patches/series 2023-09-06 20:50:00.000000000 +0000 +++ ruby-jwt-2.7.1/debian/patches/series 2026-09-28 07:16:44.000000000 +0000 @@ -1 +1,2 @@ no-pending-tests.patch +CVE-2026-45363.patch