http_signature 1.3.1 → 1.4.0

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: bcc1de3b03efae4926bf2dcbd2b8b6ee628691b6aa1044fbefe59f76dadb3264
4
- data.tar.gz: f86411fa7e90f74067e74e38dbdbd75ab377a47a9a9cbef3b87289298bff8baf
3
+ metadata.gz: 6e13e2dd019cbe8bc7359f1b49cfcd16bd3bb0e11818c7a4709a5892e7a9b362
4
+ data.tar.gz: 5879cc1525680c68286539b73d7091cdac20218727128af6e42cf2cff36e4c77
5
5
  SHA512:
6
- metadata.gz: 69ce187ea87533cde9ad1978217f9852eeb12159a78dd37369ccdff76f92cfd2815f99953d623456f98fde87f2013749c7ddbe38ab35e40a5b74a9cf53abd320
7
- data.tar.gz: 6f67d20c2ee63cfc2a5f97014d738ec0dcd39533bf7f84d405823dfdf3464edc42ec986c66fc6de92d0f293171882690d55b3fa19bb799c139e641ea13918ac2
6
+ metadata.gz: 69726bec8a75e3214a9d0c5b3f94f8d65e76c4c64746cc7ade2b11012f8d299d69ac33960f28feb486572702df2460f593cbcc04213f10bf42bd0706404763b8
7
+ data.tar.gz: e0c993df95150e6698faa87327a806262d7fd7f2141e70cae917dfa89f40ba018acbe721a4b7744b8a5af4ae3e3063fbe4427afbb35a562972de396a05d30acd
@@ -0,0 +1,9 @@
1
+ # Please see the documentation for all configuration options:
2
+ # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
3
+
4
+ version: 2
5
+ updates:
6
+ - package-ecosystem: "bundler"
7
+ directory: "/"
8
+ schedule:
9
+ interval: "weekly"
data/AGENTS.md CHANGED
@@ -18,4 +18,4 @@ Always run linters before commit
18
18
  - Update version number in lib/http_signature/version.rb
19
19
  - Run `bundle install`
20
20
  - Commit with message "Version x.x.x" and push
21
- - Create release on github with correct version tag
21
+ - Create release on github with title "Version x.x.x", correct tag as "vx.x.x", e.g., v1.0.0, and auto generated notes
data/CLAUDE.md ADDED
@@ -0,0 +1 @@
1
+ AGENTS.md
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- http_signature (1.3.1)
4
+ http_signature (1.4.0)
5
5
  base64
6
6
  starry (~> 0.2)
7
7
 
@@ -44,15 +44,26 @@ GEM
44
44
  concurrent-ruby (1.3.6)
45
45
  connection_pool (3.0.2)
46
46
  crass (1.0.6)
47
+ csv (3.3.5)
47
48
  docile (1.4.1)
48
49
  drb (2.2.3)
49
50
  erubi (1.13.1)
51
+ ethon (0.15.0)
52
+ ffi (>= 1.15.0)
53
+ excon (1.4.0)
54
+ logger
50
55
  faraday (2.14.1)
51
56
  faraday-net_http (>= 2.0, < 3.5)
52
57
  json
53
58
  logger
54
59
  faraday-net_http (3.4.2)
55
60
  net-http (~> 0.5)
61
+ ffi (1.17.3-arm64-darwin)
62
+ ffi (1.17.3-x86_64-linux-gnu)
63
+ httparty (0.24.2)
64
+ csv
65
+ mini_mime (>= 1.0.0)
66
+ multi_xml (>= 0.5.2)
56
67
  i18n (1.14.8)
57
68
  concurrent-ruby (~> 1.0)
58
69
  json (2.18.1)
@@ -62,8 +73,11 @@ GEM
62
73
  loofah (2.25.0)
63
74
  crass (~> 1.0.2)
64
75
  nokogiri (>= 1.12.0)
76
+ mini_mime (1.1.5)
65
77
  minitest (6.0.0)
66
78
  prism (~> 1.5)
79
+ multi_xml (0.8.1)
80
+ bigdecimal (>= 3.1, < 5)
67
81
  net-http (0.9.1)
68
82
  uri (>= 0.11.1)
69
83
  nokogiri (1.19.0-arm64-darwin)
@@ -71,10 +85,10 @@ GEM
71
85
  nokogiri (1.19.0-x86_64-linux-gnu)
72
86
  racc (~> 1.4)
73
87
  parallel (1.27.0)
74
- parser (3.3.10.0)
88
+ parser (3.3.10.2)
75
89
  ast (~> 2.4.1)
76
90
  racc
77
- prism (1.7.0)
91
+ prism (1.9.0)
78
92
  racc (1.8.1)
79
93
  rack (3.2.5)
80
94
  rack-session (2.1.1)
@@ -92,7 +106,7 @@ GEM
92
106
  rainbow (3.1.1)
93
107
  rake (13.3.1)
94
108
  regexp_parser (2.11.3)
95
- rubocop (1.81.7)
109
+ rubocop (1.84.2)
96
110
  json (~> 2.3)
97
111
  language_server-protocol (~> 3.17.0.2)
98
112
  lint_roller (~> 1.1.0)
@@ -100,7 +114,7 @@ GEM
100
114
  parser (>= 3.3.0.2)
101
115
  rainbow (>= 2.2.2, < 4.0)
102
116
  regexp_parser (>= 2.9.3, < 3.0)
103
- rubocop-ast (>= 1.47.1, < 2.0)
117
+ rubocop-ast (>= 1.49.0, < 2.0)
104
118
  ruby-progressbar (~> 1.7)
105
119
  unicode-display_width (>= 2.4.0, < 4.0)
106
120
  rubocop-ast (1.49.0)
@@ -118,10 +132,10 @@ GEM
118
132
  simplecov_json_formatter (~> 0.1)
119
133
  simplecov-html (0.13.2)
120
134
  simplecov_json_formatter (0.1.4)
121
- standard (1.52.0)
135
+ standard (1.54.0)
122
136
  language_server-protocol (~> 3.17.0.2)
123
137
  lint_roller (~> 1.0)
124
- rubocop (~> 1.81.7)
138
+ rubocop (~> 1.84.0)
125
139
  standard-custom (~> 1.0.0)
126
140
  standard-performance (~> 1.8)
127
141
  standard-custom (1.0.2)
@@ -132,6 +146,8 @@ GEM
132
146
  rubocop-performance (~> 1.26.0)
133
147
  starry (0.2.0)
134
148
  base64
149
+ typhoeus (1.5.0)
150
+ ethon (>= 0.9.0, < 0.16.0)
135
151
  tzinfo (2.0.6)
136
152
  concurrent-ruby (~> 1.0)
137
153
  unicode-display_width (3.2.0)
@@ -147,13 +163,16 @@ PLATFORMS
147
163
  DEPENDENCIES
148
164
  actionpack (>= 6.1)
149
165
  bundler
166
+ excon
150
167
  faraday (>= 2.7)
151
168
  http_signature!
169
+ httparty
152
170
  minitest (>= 5.24)
153
171
  rack
154
172
  rake
155
173
  simplecov
156
174
  standard
175
+ typhoeus
157
176
 
158
177
  BUNDLED WITH
159
178
  4.0.2
data/README.md CHANGED
@@ -1,15 +1,12 @@
1
1
  # HTTP Signature
2
2
 
3
- Create and validate HTTP Message Signatures per [RFC 9421](https://www.rfc-editor.org/rfc/rfc9421) using the `Signature-Input` and `Signature` headers.
3
+ A Ruby gem for signing and verifying HTTP requests. You pick which parts of the request to sign (method, URL, headers, body), and the receiver can verify nothing was tampered with — and that it came from someone who holds the key.
4
4
 
5
- TL;DR: You specify what should be signed in `Signature-Input` with [components](https://www.rfc-editor.org/rfc/rfc9421#name-derived-components) and lowercase headers. And then the signature is in the `Signature` header
5
+ Built on [RFC 9421](https://www.rfc-editor.org/rfc/rfc9421) (HTTP Message Signatures). Works with HMAC, RSA, ECDSA, and Ed25519.
6
6
 
7
- Example:
7
+ When using a standard to sign your HTTP requests you don't need to write a custom implementation every time. There's [loads of libraries](https://httpsig.org/) across languages that's implementing it!
8
8
 
9
- ```
10
- Signature-Input: sig1=("@method" "@target-uri" "date");created=1767816111;keyid="Test";alg="hmac-sha256"
11
- Signature: sig1=:7a1ajkE2rOu+gnW3WLZ4ZEcgCm3TfExmypM/giIgdM0=:
12
- ```
9
+ Use your favorite http clients to sign requests and your favorite frameworks to verify them, see [Outgoing request examples](#outgoing-request-examples) and [Incoming request examples](#incoming-request-examples)
13
10
 
14
11
  ## Installation
15
12
 
@@ -21,22 +18,29 @@ bundle add http_signature
21
18
 
22
19
  ### Create signature
23
20
 
24
- `HTTPSignature.create` returns both `Signature-Input` and `Signature` headers that you can include in your request.
21
+ `HTTPSignature.create` returns `Signature-Input`, `Signature` and `Content-Digest` headers that you can include in your request.
25
22
 
26
- ```ruby
27
- headers = { "date" => "Tue, 20 Apr 2021 02:07:55 GMT" }
23
+ This example will sign:
24
+ - The whole `url` string: `https://example.com/foo?pet=dog`
25
+ - The HTTP method: `POST`
26
+ - The headers `Content-Type` and `Content-Digest`
27
+ - The body, which is in the `Content-Digest` header and is automatically generated when a body is provided
28
28
 
29
- sig_headers = HTTPSignature.create(
29
+ ```ruby
30
+ HTTPSignature.create(
30
31
  url: "https://example.com/foo?pet=dog",
31
- method: :get,
32
- key_id: "Test",
32
+ method: :post,
33
+ key_id: "key-1",
33
34
  key: "secret",
34
- headers: headers,
35
- components: %w[@method @target-uri date]
35
+ headers: {"Content-Type" => "application/json"},
36
+ body: {payload: {foo: "bar"}}.to_json,
37
+ components: %w[@method @target-uri content-type content-digest]
36
38
  )
37
-
38
- request["Signature-Input"] = sig_headers["Signature-Input"]
39
- request["Signature"] = sig_headers["Signature"]
39
+ # =>
40
+ # {"Signature-Input" =>
41
+ # "sig1=(\"@method\" \"@target-uri\" \"content-type\" \"content-digest\");created=1772541832;keyid=\"key-1\";alg=\"hmac-sha256\"",
42
+ # "Signature" => "sig1=:5ij6rnnwS9oOtu78zU4yBFy9uL3ItXM7ug368cJZuTU=:",
43
+ # "Content-Digest" => "sha-256=:zWToMIpmVcAx10/ZGOrMzi7HQyUBat/TskigQnncEQ8=:"}
40
44
  ```
41
45
  #### All options
42
46
 
@@ -254,37 +258,35 @@ end
254
258
 
255
259
  ## Outgoing request examples
256
260
 
257
- ### NET::HTTP
261
+ ### Net::HTTP
258
262
 
259
263
  ```ruby
260
264
  require "net/http"
261
265
  require "http_signature"
262
266
 
263
267
  uri = URI("http://example.com/hello")
268
+ body = {name: "World"}.to_json
269
+ headers = {"Content-Type" => "application/json"}
264
270
 
265
- Net::HTTP.start(uri.host, uri.port) do |http|
266
- request = Net::HTTP::Get.new(uri)
267
-
268
- sig_headers = HTTPSignature.create(
269
- url: request.uri,
270
- method: request.method,
271
- headers: request.each_header.map { |k, v| [k, v] }.to_h,
272
- key: "MYSECRETKEY",
273
- key_id: "KEY_1",
274
- algorithm: "hmac-sha256",
275
- body: request.body || ""
276
- )
271
+ sig_headers = HTTPSignature.create(
272
+ url: uri.to_s,
273
+ method: :post,
274
+ headers:,
275
+ key: "MYSECRETKEY",
276
+ key_id: "KEY_1",
277
+ body:
278
+ )
277
279
 
278
- request["Signature-Input"] = sig_headers["Signature-Input"]
279
- request["Signature"] = sig_headers["Signature"]
280
+ req = Net::HTTP::Post.new(uri)
281
+ headers.merge(sig_headers).each { |k, v| req[k] = v }
282
+ req.body = body
280
283
 
281
- response = http.request(request) # Net::HTTPResponse
282
- end
284
+ Net::HTTP.start(uri.host, uri.port) { |http| http.request(req) }
283
285
  ```
284
286
 
285
287
  ### Faraday
286
288
 
287
- As a faraday middleware
289
+ As a Faraday middleware
288
290
 
289
291
  ```ruby
290
292
  require "http_signature/faraday"
@@ -292,17 +294,96 @@ require "http_signature/faraday"
292
294
  HTTPSignature::Faraday.key = "secret"
293
295
  HTTPSignature::Faraday.key_id = "key-1"
294
296
 
295
- Faraday.new("http://example.com") do |faraday|
296
- faraday.use(HTTPSignature::Faraday)
297
- faraday.adapter(Faraday.default_adapter)
297
+ conn = Faraday.new("http://example.com") do |f|
298
+ f.use(HTTPSignature::Faraday)
299
+ end
300
+
301
+ # Requests will automatically include Signature-Input, Signature, and Content-Digest headers
302
+ conn.post("/hello") do |req|
303
+ req.headers["Content-Type"] = "application/json"
304
+ req.body = {name: "World"}.to_json
305
+ end
306
+ ```
307
+
308
+ Example with setting params per request:
309
+
310
+ ```ruby
311
+ conn = Faraday.new("http://example.com") do |f|
312
+ f.use(
313
+ HTTPSignature::Faraday,
314
+ key: "secret",
315
+ key_id: "key-1",
316
+ components: ["@method", "@target-uri", "content-type", "content-digest"]
317
+ )
298
318
  end
319
+ ```
320
+
321
+ The middleware also accepts the params: `created:`, `expires:`, `nonce:`, `label:`, `algorithm:`, and `include_alg:`.
299
322
 
300
- # Now this request will contain the `Signature-Input` and `Signature` headers
301
- response = conn.get("/")
323
+ ### HTTParty
324
+
325
+ ```ruby
326
+ require "httparty"
327
+ require "http_signature"
328
+
329
+ url = "http://example.com/hello"
330
+ body = {name: "World"}.to_json
331
+ headers = {"Content-Type" => "application/json"}
332
+
333
+ sig_headers = HTTPSignature.create(
334
+ url:,
335
+ method: "POST",
336
+ headers:,
337
+ key: "MYSECRETKEY",
338
+ key_id: "KEY_1",
339
+ body:
340
+ )
341
+
342
+ HTTParty.post(url, body:, headers: headers.merge(sig_headers))
343
+ ```
344
+
345
+ ### Excon
346
+
347
+ ```ruby
348
+ require "excon"
349
+ require "http_signature"
350
+
351
+ url = "http://example.com/hello"
352
+ body = {name: "World"}.to_json
353
+ headers = {"Content-Type" => "application/json"}
354
+
355
+ sig_headers = HTTPSignature.create(
356
+ url:,
357
+ method: "POST",
358
+ headers:,
359
+ key: "MYSECRETKEY",
360
+ key_id: "KEY_1",
361
+ body:
362
+ )
363
+
364
+ Excon.post(url, headers: headers.merge(sig_headers), body:)
365
+ ```
366
+
367
+ ### Typhoeus
368
+
369
+ ```ruby
370
+ require "typhoeus"
371
+ require "http_signature"
372
+
373
+ url = "http://example.com/hello"
374
+ body = {name: "World"}.to_json
375
+ headers = {"Content-Type" => "application/json"}
376
+
377
+ sig_headers = HTTPSignature.create(
378
+ url:,
379
+ method: "POST",
380
+ headers:,
381
+ key: "MYSECRETKEY",
382
+ key_id: "KEY_1",
383
+ body:
384
+ )
302
385
 
303
- # Request looking like:
304
- # Signature-Input: sig1=("@method" "@authority" "@target-uri" "date");created=...
305
- # Signature: sig1=:BASE64_SIGNATURE:
386
+ Typhoeus.post(url, body:, headers: headers.merge(sig_headers))
306
387
  ```
307
388
 
308
389
  ## Incoming request examples
@@ -28,6 +28,9 @@ Gem::Specification.new do |spec|
28
28
  spec.add_development_dependency "standard"
29
29
  spec.add_development_dependency "simplecov"
30
30
  spec.add_development_dependency "actionpack", ">= 6.1"
31
+ spec.add_development_dependency "httparty"
32
+ spec.add_development_dependency "excon"
33
+ spec.add_development_dependency "typhoeus"
31
34
 
32
35
  spec.add_dependency "base64"
33
36
  spec.add_dependency "starry", "~> 0.2"
@@ -8,8 +8,16 @@ class HTTPSignature::Faraday < Faraday::Middleware
8
8
  attr_accessor :key, :key_id
9
9
  end
10
10
 
11
+ def initialize(app, options = nil)
12
+ super(app)
13
+ @options = options || {}
14
+ end
15
+
11
16
  def call(env)
12
- raise "key and key_id needs to be set" if self.class.key.nil? || self.class.key_id.nil?
17
+ options = merged_options(env)
18
+ key = options.fetch(:key, self.class.key)
19
+ key_id = options.fetch(:key_id, self.class.key_id)
20
+ raise "key and key_id needs to be set" if key.nil? || key_id.nil?
13
21
 
14
22
  body =
15
23
  if env[:body]&.respond_to?(:read)
@@ -20,19 +28,7 @@ class HTTPSignature::Faraday < Faraday::Middleware
20
28
  env[:body].to_s
21
29
  end
22
30
 
23
- # Choose which headers to sign
24
- filtered_headers = %w[Host Date Content-Digest]
25
- headers_to_sign = env[:request_headers].select { |k, _v| filtered_headers.include?(k.to_s) }
26
-
27
- signature_headers = HTTPSignature.create(
28
- url: env[:url],
29
- method: env[:method],
30
- headers: headers_to_sign,
31
- key: self.class.key,
32
- key_id: self.class.key_id,
33
- algorithm: "hmac-sha256",
34
- body: body
35
- )
31
+ signature_headers = HTTPSignature.create(**signature_options(env:, key:, key_id:, body:, options:))
36
32
 
37
33
  signature_headers.each do |header, value|
38
34
  env[:request_headers][header] = value
@@ -40,4 +36,32 @@ class HTTPSignature::Faraday < Faraday::Middleware
40
36
 
41
37
  @app.call(env)
42
38
  end
39
+
40
+ private
41
+
42
+ def merged_options(env)
43
+ request_options = env.request.context&.dig(:http_signature) || {}
44
+ @options.merge(request_options)
45
+ end
46
+
47
+ def signature_options(env:, key:, key_id:, body:, options:)
48
+ {
49
+ url: env[:url],
50
+ method: env[:method],
51
+ headers: env[:request_headers],
52
+ key:,
53
+ key_id:,
54
+ body:,
55
+ **create_options(options)
56
+ }.tap do |signature_options|
57
+ if options.key?(:components) && !options[:components].nil?
58
+ signature_options[:components] = options[:components]
59
+ end
60
+ end
61
+ end
62
+
63
+ def create_options(options)
64
+ options.slice(:created, :expires, :nonce, :label, :include_alg, :algorithm)
65
+ .reject { |_key, value| value.nil? }
66
+ end
43
67
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPSignature
4
- VERSION = "1.3.1"
4
+ VERSION = "1.4.0"
5
5
  end
@@ -108,6 +108,8 @@ module HTTPSignature
108
108
 
109
109
  validate_components!(components)
110
110
 
111
+ had_content_digest = normalized_headers.key?("content-digest")
112
+
111
113
  normalized_headers =
112
114
  if components.include?("content-digest")
113
115
  ensure_content_digest(normalized_headers, body)
@@ -140,10 +142,16 @@ module HTTPSignature
140
142
  signature_bytes = sign(base_string, key: key, algorithm: algorithm_entry)
141
143
  signature_header = build_signature_header(label, signature_bytes)
142
144
 
143
- {
145
+ result = {
144
146
  "Signature-Input" => signature_input_header,
145
147
  "Signature" => signature_header
146
148
  }
149
+
150
+ if !had_content_digest && normalized_headers["content-digest"]
151
+ result["Content-Digest"] = normalized_headers["content-digest"]
152
+ end
153
+
154
+ result
147
155
  end
148
156
 
149
157
  # Verify RFC 9421 Signature headers
@@ -154,7 +162,8 @@ module HTTPSignature
154
162
  # @param body [String] Request body (default: "")
155
163
  # @param key [String, OpenSSL::PKey::PKey, nil] Verification key. If nil, uses key_resolver or configured keys
156
164
  # @param key_resolver [Proc, nil] Callable that receives key_id and returns the key (default: nil)
157
- # @param label [String] Signature label to verify (default: "sig1")
165
+ # @param label [String, nil] Signature label to verify. If nil, auto-detects
166
+ # the first label from the Signature-Input header (default: nil)
158
167
  # @param query_string_params [Hash] Additional query params to merge into URL (default: {})
159
168
  # @param max_age [Integer, nil] Maximum signature age in seconds. Takes precedence over
160
169
  # the expires timestamp in the signature (default: nil)
@@ -174,7 +183,7 @@ module HTTPSignature
174
183
  body: "",
175
184
  key: nil,
176
185
  key_resolver: nil,
177
- label: DEFAULT_LABEL,
186
+ label: nil,
178
187
  query_string_params: {},
179
188
  max_age: nil,
180
189
  algorithm: nil,
@@ -191,6 +200,8 @@ module HTTPSignature
191
200
  signature_header = normalized_headers["signature"]
192
201
  raise SignatureError, "Signature headers are required for verification" unless signature_input_header && signature_header
193
202
 
203
+ label ||= detect_label(signature_input_header)
204
+
194
205
  parsed_input = parse_signature_input(signature_input_header, label)
195
206
  validate_components!(parsed_input[:components])
196
207
  parsed_signature = parse_signature(signature_header, label)
@@ -624,6 +635,16 @@ module HTTPSignature
624
635
  header.to_s.split(/,(?=[^,]+=)/).map(&:strip)
625
636
  end
626
637
 
638
+ def self.detect_label(signature_input_header)
639
+ entry = split_header(signature_input_header).first
640
+ raise SignatureError, "Signature-Input missing" unless entry
641
+
642
+ label = entry.split("=", 2).first
643
+ raise SignatureError, "Signature-Input missing" if label.nil? || label.empty?
644
+
645
+ label
646
+ end
647
+
627
648
  def self.key_from_store(key_id)
628
649
  return unless keys && key_id
629
650
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: http_signature
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Larsson
@@ -121,6 +121,48 @@ dependencies:
121
121
  - - ">="
122
122
  - !ruby/object:Gem::Version
123
123
  version: '6.1'
124
+ - !ruby/object:Gem::Dependency
125
+ name: httparty
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ type: :development
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ - !ruby/object:Gem::Dependency
139
+ name: excon
140
+ requirement: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ type: :development
146
+ prerelease: false
147
+ version_requirements: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ - !ruby/object:Gem::Dependency
153
+ name: typhoeus
154
+ requirement: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ type: :development
160
+ prerelease: false
161
+ version_requirements: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
124
166
  - !ruby/object:Gem::Dependency
125
167
  name: base64
126
168
  requirement: !ruby/object:Gem::Requirement
@@ -156,12 +198,14 @@ executables: []
156
198
  extensions: []
157
199
  extra_rdoc_files: []
158
200
  files:
201
+ - ".github/dependabot.yml"
159
202
  - ".github/workflows/ci.yml"
160
203
  - ".github/workflows/push_gem.yml"
161
204
  - ".github/workflows/standardrb.yml"
162
205
  - ".gitignore"
163
206
  - ".ruby-version"
164
207
  - AGENTS.md
208
+ - CLAUDE.md
165
209
  - Gemfile
166
210
  - Gemfile.lock
167
211
  - README.md