rack-session 2.1.1 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 24dbcb8931b1a26d39b7165f872231e9ce7f9006e8495616416faeb4538ed8e6
4
- data.tar.gz: c26f979218fb4ac3b626d8638ee7b0e00183ac1acffdc1fb393fd85919e1cf46
3
+ metadata.gz: 82ef73dda808550f0838e5fa7d75d90a15d1b524831c58a5e082349143637357
4
+ data.tar.gz: 4214302b0ec644f8b905d575d870a7fc4de5f4c8e1450a104e7193eb117b70f2
5
5
  SHA512:
6
- metadata.gz: e345e1424c6092e771a16f15fee04c19128dacfa50b586fbd7795ce1699fe50cbf2b7028624dc945e60a055f20c99c0d88c7c1eec729b13d527f3c3bcc7d6e6e
7
- data.tar.gz: 8da88daf469c01ebeef2bb75b5ee04efc42e836807b3000b028fa2eb73f11db3585410321297e7607bd0e9413f768dab2a55e54f08e5326697e1d472f9363e6c
6
+ metadata.gz: 17b3713d1ba66fd9c40f825826f05be73d60681b51b06e8df561efa4a146c537b323ff2fc436cc8ddf75314a26f5839e89d98bcc270746ad70e18af441e1a7ae
7
+ data.tar.gz: 5a06e7eec1398684eaca507d0c69063cfcfae2bb8478255d43c49ce01d9db5bc551e1cb593bed6872718ad925ea5145ea4acb95cf6f0eae26182b7af5d37a83a
@@ -237,8 +237,10 @@ module Rack
237
237
  # Decode using legacy HMAC decoder
238
238
  session_data = @legacy_hmac_coder.decode(session_data)
239
239
 
240
- elsif !session_data && coder
241
- # Use the coder option, which has the potential to be very unsafe
240
+ elsif !session_data && encryptors.empty? && coder
241
+ # Use the coder option, which has the potential to be very unsafe.
242
+ # This path is only reached when no encryptors (secrets:) are configured;
243
+ # if encryptors are present but decryption failed, the cookie is rejected.
242
244
  session_data = coder.decode(cookie_data)
243
245
  end
244
246
  end
@@ -5,9 +5,9 @@
5
5
  # Copyright, 2022, by Philip Arndt.
6
6
 
7
7
  require 'base64'
8
+ require 'json'
8
9
  require 'openssl'
9
10
  require 'securerandom'
10
- require 'zlib'
11
11
 
12
12
  require 'rack/utils'
13
13
 
@@ -23,169 +23,392 @@ module Rack
23
23
  class InvalidMessage < Error
24
24
  end
25
25
 
26
- # The secret String must be at least 64 bytes in size. The first 32 bytes
27
- # will be used for the encryption cipher key. The remainder will be used
28
- # for an HMAC key.
29
- #
30
- # Options may include:
31
- # * :serialize_json
32
- # Use JSON for message serialization instead of Marshal. This can be
33
- # viewed as a security enhancement.
34
- # * :pad_size
35
- # Pad encrypted message data, to a multiple of this many bytes
36
- # (default: 32). This can be between 2-4096 bytes, or +nil+ to disable
37
- # padding.
38
- # * :purpose
39
- # Limit messages to a specific purpose. This can be viewed as a
40
- # security enhancement to prevent message reuse from different contexts
41
- # if keys are reused.
42
- #
43
- # Cryptography and Output Format:
44
- #
45
- # urlsafe_encode64(version + random_data + IV + encrypted data + HMAC)
46
- #
47
- # Where:
48
- # * version - 1 byte and is currently always 0x01
49
- # * random_data - 32 bytes used for generating the per-message secret
50
- # * IV - 16 bytes random initialization vector
51
- # * HMAC - 32 bytes HMAC-SHA-256 of all preceding data, plus the purpose
52
- # value
53
- def initialize(secret, opts = {})
54
- raise ArgumentError, "secret must be a String" unless String === secret
55
- raise ArgumentError, "invalid secret: #{secret.bytesize}, must be >=64" unless secret.bytesize >= 64
26
+ module Serializable
27
+ private
56
28
 
57
- case opts[:pad_size]
58
- when nil
59
- # padding is disabled
60
- when Integer
61
- raise ArgumentError, "invalid pad_size: #{opts[:pad_size]}" unless (2..4096).include? opts[:pad_size]
62
- else
63
- raise ArgumentError, "invalid pad_size: #{opts[:pad_size]}; must be Integer or nil"
29
+ # Returns a serialized payload of the message. If a :pad_size is supplied,
30
+ # the message will be padded. The first 2 bytes of the returned string will
31
+ # indicating the amount of padding.
32
+ def serialize_payload(message)
33
+ serialized_data = serializer.dump(message)
34
+
35
+ return "#{[0].pack('v')}#{serialized_data.force_encoding(Encoding::BINARY)}" if @options[:pad_size].nil?
36
+
37
+ padding_bytes = @options[:pad_size] - (2 + serialized_data.size) % @options[:pad_size]
38
+ padding_data = SecureRandom.random_bytes(padding_bytes)
39
+
40
+ "#{[padding_bytes].pack('v')}#{padding_data}#{serialized_data.force_encoding(Encoding::BINARY)}"
64
41
  end
65
42
 
66
- @options = {
67
- serialize_json: false, pad_size: 32, purpose: nil
68
- }.update(opts)
43
+ # Return the deserialized message. The first 2 bytes will be read as the
44
+ # amount of padding.
45
+ def deserialized_message(data)
46
+ # Read the first 2 bytes as the padding_bytes size
47
+ padding_bytes, = data.unpack('v')
69
48
 
70
- @hmac_secret = secret.dup.force_encoding('BINARY')
71
- @cipher_secret = @hmac_secret.slice!(0, 32)
49
+ # Slice out the serialized_data and deserialize it
50
+ serialized_data = data.slice(2 + padding_bytes, data.bytesize)
51
+ serializer.load serialized_data
52
+ end
72
53
 
73
- @hmac_secret.freeze
74
- @cipher_secret.freeze
54
+ def serializer
55
+ @serializer ||= @options[:serialize_json] ? JSON : Marshal
56
+ end
75
57
  end
76
58
 
77
- def decrypt(base64_data)
78
- data = Base64.urlsafe_decode64(base64_data)
59
+ class V1
60
+ include Serializable
61
+
62
+ # The secret String must be at least 64 bytes in size. The first 32 bytes
63
+ # will be used for the encryption cipher key. The remainder will be used
64
+ # for an HMAC key.
65
+ #
66
+ # Options may include:
67
+ # * :serialize_json
68
+ # Use JSON for message serialization instead of Marshal. This can be
69
+ # viewed as a security enhancement.
70
+ # * :pad_size
71
+ # Pad encrypted message data, to a multiple of this many bytes
72
+ # (default: 32). This can be between 2-4096 bytes, or +nil+ to disable
73
+ # padding.
74
+ # * :purpose
75
+ # Limit messages to a specific purpose. This can be viewed as a
76
+ # security enhancement to prevent message reuse from different contexts
77
+ # if keys are reused.
78
+ #
79
+ # Cryptography and Output Format:
80
+ #
81
+ # urlsafe_encode64(version + random_data + IV + encrypted data + HMAC)
82
+ #
83
+ # Where:
84
+ # * version - 1 byte with value 0x01
85
+ # * random_data - 32 bytes used for generating the per-message secret
86
+ # * IV - 16 bytes random initialization vector
87
+ # * HMAC - 32 bytes HMAC-SHA-256 of all preceding data, plus the purpose
88
+ # value
89
+ def initialize(secret, opts = {})
90
+ raise ArgumentError, 'secret must be a String' unless secret.is_a?(String)
91
+ raise ArgumentError, "invalid secret: #{secret.bytesize}, must be >=64" unless secret.bytesize >= 64
92
+
93
+ case opts[:pad_size]
94
+ when nil
95
+ # padding is disabled
96
+ when Integer
97
+ raise ArgumentError, "invalid pad_size: #{opts[:pad_size]}" unless (2..4096).include? opts[:pad_size]
98
+ else
99
+ raise ArgumentError, "invalid pad_size: #{opts[:pad_size]}; must be Integer or nil"
100
+ end
79
101
 
80
- signature = data.slice!(-32..-1)
102
+ @options = {
103
+ serialize_json: false, pad_size: 32, purpose: nil
104
+ }.update(opts)
81
105
 
82
- verify_authenticity! data, signature
106
+ @hmac_secret = secret.dup.force_encoding(Encoding::BINARY)
107
+ @cipher_secret = @hmac_secret.slice!(0, 32)
83
108
 
84
- # The version is reserved for future
85
- _version = data.slice!(0, 1)
86
- message_secret = data.slice!(0, 32)
87
- cipher_iv = data.slice!(0, 16)
109
+ @hmac_secret.freeze
110
+ @cipher_secret.freeze
111
+ end
88
112
 
89
- cipher = new_cipher
90
- cipher.decrypt
113
+ def decrypt(base64_data)
114
+ data = Base64.urlsafe_decode64(base64_data)
91
115
 
92
- set_cipher_key(cipher, cipher_secret_from_message_secret(message_secret))
116
+ signature = data.slice!(-32..-1)
117
+ verify_authenticity!(data, signature)
93
118
 
94
- cipher.iv = cipher_iv
95
- data = cipher.update(data) << cipher.final
119
+ version = data.slice!(0, 1)
120
+ raise InvalidMessage, 'wrong version' unless version == "\1"
96
121
 
97
- deserialized_message data
98
- rescue ArgumentError
99
- raise InvalidSignature, 'Message invalid'
100
- end
122
+ message_secret = data.slice!(0, 32)
123
+ cipher_iv = data.slice!(0, 16)
101
124
 
102
- def encrypt(message)
103
- version = "\1"
125
+ cipher = new_cipher
126
+ cipher.decrypt
104
127
 
105
- serialized_payload = serialize_payload(message)
106
- message_secret, cipher_secret = new_message_and_cipher_secret
128
+ set_cipher_key(cipher, cipher_secret_from_message_secret(message_secret))
107
129
 
108
- cipher = new_cipher
109
- cipher.encrypt
130
+ cipher.iv = cipher_iv
131
+ data = cipher.update(data) << cipher.final
110
132
 
111
- set_cipher_key(cipher, cipher_secret)
133
+ deserialized_message data
134
+ rescue ArgumentError
135
+ raise InvalidSignature, 'Message invalid'
136
+ end
112
137
 
113
- cipher_iv = cipher.random_iv
138
+ def encrypt(message)
139
+ version = "\1"
114
140
 
115
- encrypted_data = cipher.update(serialized_payload) << cipher.final
141
+ serialized_payload = serialize_payload(message)
142
+ message_secret, cipher_secret = new_message_and_cipher_secret
116
143
 
117
- data = String.new
118
- data << version
119
- data << message_secret
120
- data << cipher_iv
121
- data << encrypted_data
122
- data << compute_signature(data)
144
+ cipher = new_cipher
145
+ cipher.encrypt
123
146
 
124
- Base64.urlsafe_encode64(data)
125
- end
147
+ set_cipher_key(cipher, cipher_secret)
126
148
 
127
- private
149
+ cipher_iv = cipher.random_iv
128
150
 
129
- def new_cipher
130
- OpenSSL::Cipher.new('aes-256-ctr')
131
- end
151
+ encrypted_data = cipher.update(serialized_payload) << cipher.final
132
152
 
133
- def new_message_and_cipher_secret
134
- message_secret = SecureRandom.random_bytes(32)
153
+ data = String.new
154
+ data << version
155
+ data << message_secret
156
+ data << cipher_iv
157
+ data << encrypted_data
158
+ data << compute_signature(data)
135
159
 
136
- [message_secret, cipher_secret_from_message_secret(message_secret)]
137
- end
160
+ Base64.urlsafe_encode64(data)
161
+ end
138
162
 
139
- def cipher_secret_from_message_secret(message_secret)
140
- OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, @cipher_secret, message_secret)
141
- end
163
+ private
142
164
 
143
- def set_cipher_key(cipher, key)
144
- cipher.key = key
145
- end
165
+ def new_cipher
166
+ OpenSSL::Cipher.new('aes-256-ctr')
167
+ end
146
168
 
147
- def serializer
148
- @serializer ||= @options[:serialize_json] ? JSON : Marshal
149
- end
169
+ def new_message_and_cipher_secret
170
+ message_secret = SecureRandom.random_bytes(32)
171
+
172
+ [message_secret, cipher_secret_from_message_secret(message_secret)]
173
+ end
150
174
 
151
- def compute_signature(data)
152
- signing_data = data
153
- signing_data += @options[:purpose] if @options[:purpose]
175
+ def cipher_secret_from_message_secret(message_secret)
176
+ OpenSSL::HMAC.digest(OpenSSL::Digest.new('SHA256'), @cipher_secret, message_secret)
177
+ end
154
178
 
155
- OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, @hmac_secret, signing_data)
156
- end
179
+ def set_cipher_key(cipher, key)
180
+ cipher.key = key
181
+ end
157
182
 
158
- def verify_authenticity!(data, signature)
159
- raise InvalidMessage, 'Message is invalid' if data.nil? || signature.nil?
183
+ def compute_signature(data)
184
+ signing_data = data
185
+ signing_data += @options[:purpose] if @options[:purpose]
160
186
 
161
- unless Rack::Utils.secure_compare(signature, compute_signature(data))
162
- raise InvalidSignature, 'HMAC is invalid'
187
+ OpenSSL::HMAC.digest(OpenSSL::Digest.new('SHA256'), @hmac_secret, signing_data)
188
+ end
189
+
190
+ def verify_authenticity!(data, signature)
191
+ raise InvalidMessage, 'Message is invalid' if data.nil? || signature.nil?
192
+
193
+ unless Rack::Utils.secure_compare(signature, compute_signature(data))
194
+ raise InvalidSignature, 'HMAC is invalid'
195
+ end
163
196
  end
164
197
  end
165
198
 
166
- # Returns a serialized payload of the message. If a :pad_size is supplied,
167
- # the message will be padded. The first 2 bytes of the returned string will
168
- # indicating the amount of padding.
169
- def serialize_payload(message)
170
- serialized_data = serializer.dump(message)
199
+ class V2
200
+ include Serializable
201
+
202
+ # The secret String must be at least 32 bytes in size.
203
+ #
204
+ # Options may include:
205
+ # * :pad_size
206
+ # Pad encrypted message data, to a multiple of this many bytes
207
+ # (default: 32). This can be between 2-4096 bytes, or +nil+ to disable
208
+ # padding.
209
+ # * :purpose
210
+ # Limit messages to a specific purpose. This can be viewed as a
211
+ # security enhancement to prevent message reuse from different contexts
212
+ # if keys are reused.
213
+ #
214
+ # Cryptography and Output Format:
215
+ #
216
+ # strict_encode64(version + salt + IV + authentication tag + ciphertext)
217
+ #
218
+ # Where:
219
+ # * version - 1 byte with value 0x02
220
+ # * salt - 32 bytes used for generating the per-message secret
221
+ # * IV - 12 bytes random initialization vector
222
+ # * authentication tag - 16 bytes authentication tag generated by the GCM mode, covering version and salt
223
+ #
224
+ # Considerations about V2:
225
+ #
226
+ # 1) It uses non URL-safe Base64 encoding as it's faster than its
227
+ # URL-safe counterpart - as of Ruby 3.2, Base64.urlsafe_encode64 is
228
+ # roughly equivalent to
229
+ #
230
+ # Base64.strict_encode64(data).tr("-_", "+/")
231
+ #
232
+ # - and cookie values don't need to be URL-safe.
233
+ def initialize(secret, opts = {})
234
+ raise ArgumentError, 'secret must be a String' unless secret.is_a?(String)
235
+
236
+ unless secret.bytesize >= 32
237
+ raise ArgumentError, "invalid secret: it's #{secret.bytesize}-byte long, must be >=32"
238
+ end
239
+
240
+ case opts[:pad_size]
241
+ when nil
242
+ # padding is disabled
243
+ when Integer
244
+ raise ArgumentError, "invalid pad_size: #{opts[:pad_size]}" unless (2..4096).include? opts[:pad_size]
245
+ else
246
+ raise ArgumentError, "invalid pad_size: #{opts[:pad_size]}; must be Integer or nil"
247
+ end
248
+
249
+ @options = {
250
+ serialize_json: false, pad_size: 32, purpose: nil
251
+ }.update(opts)
252
+
253
+ @cipher_secret = secret.dup.force_encoding(Encoding::BINARY).slice!(0, 32)
254
+ @cipher_secret.freeze
255
+ end
256
+
257
+ def decrypt(base64_data)
258
+ data = Base64.strict_decode64(base64_data)
259
+ if data.bytesize <= 61 # version + salt + iv + auth_tag = 61 byte (and we also need some ciphertext :)
260
+ raise InvalidMessage, 'invalid message'
261
+ end
262
+
263
+ version = data[0]
264
+ raise InvalidMessage, 'invalid message' unless version == "\2"
265
+
266
+ ciphertext = data.slice!(61..-1)
267
+ auth_tag = data.slice!(45, 16)
268
+ cipher_iv = data.slice!(33, 12)
269
+
270
+ cipher = new_cipher
271
+ cipher.decrypt
272
+ salt = data.slice(1, 32)
273
+ set_cipher_key(cipher, message_secret_from_salt(salt))
274
+ cipher.iv = cipher_iv
275
+ cipher.auth_tag = auth_tag
276
+ cipher.auth_data = (purpose = @options[:purpose]) ? data + purpose : data
277
+
278
+ plaintext = cipher.update(ciphertext) << cipher.final
171
279
 
172
- return "#{[0].pack('v')}#{serialized_data}" if @options[:pad_size].nil?
280
+ deserialized_message plaintext
281
+ rescue ArgumentError, OpenSSL::Cipher::CipherError
282
+ raise InvalidSignature, 'invalid message'
283
+ end
284
+
285
+ def encrypt(message)
286
+ version = "\2"
287
+
288
+ serialized_payload = serialize_payload(message)
289
+
290
+ cipher = new_cipher
291
+ cipher.encrypt
292
+ salt, message_secret = new_salt_and_message_secret
293
+ set_cipher_key(cipher, message_secret)
294
+ cipher.iv_len = 12
295
+ cipher_iv = cipher.random_iv
296
+
297
+ data = String.new
298
+ data << version
299
+ data << salt
300
+
301
+ cipher.auth_data = (purpose = @options[:purpose]) ? data + purpose : data
302
+ encrypted_data = cipher.update(serialized_payload) << cipher.final
303
+
304
+ data << cipher_iv
305
+ data << auth_tag_from(cipher)
306
+ data << encrypted_data
307
+
308
+ Base64.strict_encode64(data)
309
+ end
310
+
311
+ private
173
312
 
174
- padding_bytes = @options[:pad_size] - (2 + serialized_data.size) % @options[:pad_size]
175
- padding_data = SecureRandom.random_bytes(padding_bytes)
313
+ def new_cipher
314
+ OpenSSL::Cipher.new('aes-256-gcm')
315
+ end
316
+
317
+ def new_salt_and_message_secret
318
+ salt = SecureRandom.random_bytes(32)
319
+
320
+ [salt, message_secret_from_salt(salt)]
321
+ end
322
+
323
+ def message_secret_from_salt(salt)
324
+ OpenSSL::HMAC.digest(OpenSSL::Digest.new('SHA256'), @cipher_secret, salt)
325
+ end
326
+
327
+ def set_cipher_key(cipher, key)
328
+ cipher.key = key
329
+ end
176
330
 
177
- "#{[padding_bytes].pack('v')}#{padding_data}#{serialized_data}"
331
+ if RUBY_ENGINE == 'jruby'
332
+ # JRuby's OpenSSL implementation doesn't currently support passing
333
+ # an argument to #auth_tag. Here we work around that.
334
+ def auth_tag_from(cipher)
335
+ tag = cipher.auth_tag
336
+ raise Error, 'the auth tag must be 16 bytes long' if tag.bytesize != 16
337
+
338
+ tag
339
+ end
340
+ else
341
+ def auth_tag_from(cipher)
342
+ cipher.auth_tag(16)
343
+ end
344
+ end
345
+ end
346
+
347
+ def initialize(secret, opts = {})
348
+ opts = opts.dup
349
+
350
+ @mode = opts.delete(:mode)&.to_sym || :guess_version
351
+ case @mode
352
+ when :v1
353
+ @v1 = V1.new(secret, opts)
354
+ when :v2
355
+ @v2 = V2.new(secret, opts)
356
+ else
357
+ @v1 = V1.new(secret, opts)
358
+ @v2 = V2.new(secret, opts)
359
+ end
178
360
  end
179
361
 
180
- # Return the deserialized message. The first 2 bytes will be read as the
181
- # amount of padding.
182
- def deserialized_message(data)
183
- # Read the first 2 bytes as the padding_bytes size
184
- padding_bytes, = data.unpack('v')
362
+ def decrypt(base64_data)
363
+ decryptor =
364
+ case @mode
365
+ when :v2
366
+ v2
367
+ when :v1
368
+ v1
369
+ else
370
+ guess_decryptor(base64_data)
371
+ end
372
+
373
+ decryptor.decrypt(base64_data)
374
+ end
185
375
 
186
- # Slice out the serialized_data and deserialize it
187
- serialized_data = data.slice(2 + padding_bytes, data.bytesize)
188
- serializer.load serialized_data
376
+ def encrypt(message)
377
+ encryptor =
378
+ case @mode
379
+ when :v1
380
+ v1
381
+ else
382
+ v2
383
+ end
384
+
385
+ encryptor.encrypt(message)
386
+ end
387
+
388
+ private
389
+
390
+ attr_reader :v1, :v2
391
+
392
+ def guess_decryptor(base64_data)
393
+ raise InvalidMessage, 'invalid message' if base64_data.nil? || base64_data.bytesize < 4
394
+
395
+ first_encoded_4_bytes = base64_data.slice(0, 4)
396
+ # Transform the 4 bytes into non-URL-safe base64-encoded data. Nothing
397
+ # happens if the data is already non-URL-safe base64.
398
+ first_encoded_4_bytes.tr!('-_', '+/')
399
+ first_decoded_3_bytes = Base64.strict_decode64(first_encoded_4_bytes)
400
+
401
+ version = first_decoded_3_bytes[0]
402
+ case version
403
+ when "\2"
404
+ v2
405
+ when "\1"
406
+ v1
407
+ else
408
+ raise InvalidMessage, 'invalid message'
409
+ end
410
+ rescue ArgumentError
411
+ raise InvalidMessage, 'invalid message'
189
412
  end
190
413
  end
191
414
  end
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Rack
7
7
  module Session
8
- VERSION = "2.1.1"
8
+ VERSION = "2.1.2"
9
9
  end
10
10
  end
data/lib/rack/session.rb CHANGED
@@ -8,6 +8,5 @@ module Rack
8
8
  module Session
9
9
  autoload :Cookie, "rack/session/cookie"
10
10
  autoload :Pool, "rack/session/pool"
11
- autoload :Memcache, "rack/session/memcache"
12
11
  end
13
12
  end
data/releases.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Releases
2
2
 
3
+ ## v2.1.2
4
+
5
+ - [CVE-2026-39324](https://github.com/advisories/GHSA-33qg-7wpp-89cq) Don't fall back to unencrypted coder if encryptors are present.
6
+
3
7
  ## v2.1.1
4
8
 
5
9
  - Prevent `Rack::Session::Pool` from recreating deleted sessions [CVE-2025-46336](https://github.com/rack/rack-session/security/advisories/GHSA-9j94-67jr-4cqj).
metadata CHANGED
@@ -1,17 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-session
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.1
4
+ version: 2.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  - Jeremy Evans
9
9
  - Jon Dufresne
10
10
  - Philip Arndt
11
- autorequire:
12
11
  bindir: bin
13
12
  cert_chain: []
14
- date: 2025-05-06 00:00:00.000000000 Z
13
+ date: 1980-01-02 00:00:00.000000000 Z
15
14
  dependencies:
16
15
  - !ruby/object:Gem::Dependency
17
16
  name: base64
@@ -111,8 +110,6 @@ dependencies:
111
110
  - - ">="
112
111
  - !ruby/object:Gem::Version
113
112
  version: '0'
114
- description:
115
- email:
116
113
  executables: []
117
114
  extensions: []
118
115
  extra_rdoc_files: []
@@ -133,7 +130,6 @@ licenses:
133
130
  - MIT
134
131
  metadata:
135
132
  rubygems_mfa_required: 'true'
136
- post_install_message:
137
133
  rdoc_options: []
138
134
  require_paths:
139
135
  - lib
@@ -148,8 +144,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
148
144
  - !ruby/object:Gem::Version
149
145
  version: '0'
150
146
  requirements: []
151
- rubygems_version: 3.5.22
152
- signing_key:
147
+ rubygems_version: 3.6.9
153
148
  specification_version: 4
154
149
  summary: A session implementation for Rack.
155
150
  test_files: []