keeper_secrets_manager 17.0.4 → 17.1.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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -15
  3. data/Gemfile +3 -3
  4. data/README.md +1 -1
  5. data/Rakefile +1 -1
  6. data/lib/keeper_secrets_manager/config_keys.rb +2 -2
  7. data/lib/keeper_secrets_manager/core.rb +594 -394
  8. data/lib/keeper_secrets_manager/crypto.rb +106 -113
  9. data/lib/keeper_secrets_manager/dto/payload.rb +4 -4
  10. data/lib/keeper_secrets_manager/dto.rb +50 -32
  11. data/lib/keeper_secrets_manager/errors.rb +13 -2
  12. data/lib/keeper_secrets_manager/field_types.rb +3 -3
  13. data/lib/keeper_secrets_manager/folder_manager.rb +25 -29
  14. data/lib/keeper_secrets_manager/keeper_globals.rb +9 -15
  15. data/lib/keeper_secrets_manager/notation.rb +99 -92
  16. data/lib/keeper_secrets_manager/notation_enhancements.rb +22 -24
  17. data/lib/keeper_secrets_manager/storage.rb +35 -36
  18. data/lib/keeper_secrets_manager/totp.rb +27 -27
  19. data/lib/keeper_secrets_manager/utils.rb +83 -17
  20. data/lib/keeper_secrets_manager/version.rb +2 -2
  21. data/lib/keeper_secrets_manager.rb +3 -3
  22. metadata +7 -21
  23. data/DEVELOPER_SETUP.md +0 -0
  24. data/MANUAL_TESTING_GUIDE.md +0 -332
  25. data/RUBY_SDK_COMPLETE_DOCUMENTATION.md +0 -354
  26. data/RUBY_SDK_COMPREHENSIVE_SUMMARY.md +0 -192
  27. data/examples/01_quick_start.rb +0 -45
  28. data/examples/02_authentication.rb +0 -82
  29. data/examples/03_retrieve_secrets.rb +0 -81
  30. data/examples/04_create_update_delete.rb +0 -104
  31. data/examples/05_field_types.rb +0 -135
  32. data/examples/06_files.rb +0 -137
  33. data/examples/07_folders.rb +0 -145
  34. data/examples/08_notation.rb +0 -103
  35. data/examples/09_totp.rb +0 -100
  36. data/examples/README.md +0 -89
@@ -6,18 +6,18 @@ module KeeperSecretsManager
6
6
  module Storage
7
7
  # Base storage interface
8
8
  module KeyValueStorage
9
- def get_string(key)
10
- raise NotImplementedError, "Subclass must implement get_string"
9
+ def get_string(_key)
10
+ raise NotImplementedError, 'Subclass must implement get_string'
11
11
  end
12
12
 
13
- def save_string(key, value)
14
- raise NotImplementedError, "Subclass must implement save_string"
13
+ def save_string(_key, _value)
14
+ raise NotImplementedError, 'Subclass must implement save_string'
15
15
  end
16
16
 
17
17
  def get_bytes(key)
18
18
  data = get_string(key)
19
19
  return nil unless data
20
-
20
+
21
21
  # Handle both standard and URL-safe base64
22
22
  begin
23
23
  # First try standard base64
@@ -28,7 +28,7 @@ module KeeperSecretsManager
28
28
  padding = 4 - (data.length % 4)
29
29
  padding = 0 if padding == 4
30
30
  Base64.urlsafe_decode64(data + '=' * padding)
31
- rescue => e
31
+ rescue StandardError => e
32
32
  # Last resort - try with decode64 which is more lenient
33
33
  Base64.decode64(data)
34
34
  end
@@ -39,8 +39,8 @@ module KeeperSecretsManager
39
39
  save_string(key, Base64.strict_encode64(value))
40
40
  end
41
41
 
42
- def delete(key)
43
- raise NotImplementedError, "Subclass must implement delete"
42
+ def delete(_key)
43
+ raise NotImplementedError, 'Subclass must implement delete'
44
44
  end
45
45
 
46
46
  def contains?(key)
@@ -54,7 +54,7 @@ module KeeperSecretsManager
54
54
 
55
55
  def initialize(config_data = nil)
56
56
  @data = {}
57
-
57
+
58
58
  # Initialize from JSON string, base64 string, or hash
59
59
  if config_data
60
60
  parsed = case config_data
@@ -70,7 +70,7 @@ module KeeperSecretsManager
70
70
  else
71
71
  {}
72
72
  end
73
-
73
+
74
74
  parsed.each { |k, v| @data[k.to_s] = v.to_s }
75
75
  end
76
76
  end
@@ -94,20 +94,20 @@ module KeeperSecretsManager
94
94
  def to_json(*args)
95
95
  @data.to_json(*args)
96
96
  end
97
-
97
+
98
98
  private
99
-
99
+
100
100
  def is_base64?(str)
101
101
  # Check if string is valid base64
102
102
  return false if str.nil? || str.empty?
103
-
103
+
104
104
  # Remove whitespace
105
105
  str = str.strip
106
-
106
+
107
107
  # Check if length is multiple of 4 (with padding) or can be padded to multiple of 4
108
108
  # Also check if it only contains base64 characters
109
- base64_regex = /\A[A-Za-z0-9+\/]*={0,2}\z/
110
-
109
+ base64_regex = %r{\A[A-Za-z0-9+/]*={0,2}\z}
110
+
111
111
  str.match?(base64_regex) && (str.length % 4 == 0 || str.length % 4 == 2 || str.length % 4 == 3)
112
112
  end
113
113
  end
@@ -143,11 +143,11 @@ module KeeperSecretsManager
143
143
  begin
144
144
  content = File.read(@filename)
145
145
  # Handle empty files
146
- if content.strip.empty?
147
- @data = {}
148
- else
149
- @data = JSON.parse(content)
150
- end
146
+ @data = if content.strip.empty?
147
+ {}
148
+ else
149
+ JSON.parse(content)
150
+ end
151
151
  rescue JSON::ParserError => e
152
152
  raise Error, "Failed to parse config file: #{e.message}"
153
153
  end
@@ -157,19 +157,19 @@ module KeeperSecretsManager
157
157
  def save_data
158
158
  # Ensure directory exists
159
159
  FileUtils.mkdir_p(File.dirname(@filename))
160
-
160
+
161
161
  # Write atomically to avoid corruption
162
162
  temp_file = "#{@filename}.tmp"
163
163
  File.open(temp_file, 'w') do |f|
164
164
  f.write(JSON.pretty_generate(@data))
165
165
  end
166
-
166
+
167
167
  # Move atomically
168
168
  File.rename(temp_file, @filename)
169
-
169
+
170
170
  # Set restrictive permissions (owner read/write only)
171
- File.chmod(0600, @filename)
172
- rescue => e
171
+ File.chmod(0o600, @filename)
172
+ rescue StandardError => e
173
173
  raise Error, "Failed to save config file: #{e.message}"
174
174
  end
175
175
  end
@@ -186,12 +186,12 @@ module KeeperSecretsManager
186
186
  ENV["#{@prefix}#{key.to_s.upcase}"]
187
187
  end
188
188
 
189
- def save_string(key, value)
190
- raise Error, "Environment storage is read-only"
189
+ def save_string(_key, _value)
190
+ raise Error, 'Environment storage is read-only'
191
191
  end
192
192
 
193
- def delete(key)
194
- raise Error, "Environment storage is read-only"
193
+ def delete(_key)
194
+ raise Error, 'Environment storage is read-only'
195
195
  end
196
196
  end
197
197
 
@@ -208,11 +208,9 @@ module KeeperSecretsManager
208
208
 
209
209
  def get_string(key)
210
210
  key_str = key.to_s
211
-
211
+
212
212
  # Check cache validity
213
- if @cache.key?(key_str) && !expired?(key_str)
214
- return @cache[key_str]
215
- end
213
+ return @cache[key_str] if @cache.key?(key_str) && !expired?(key_str)
216
214
 
217
215
  # Fetch from base storage
218
216
  value = @base_storage.get_string(key)
@@ -220,7 +218,7 @@ module KeeperSecretsManager
220
218
  @cache[key_str] = value
221
219
  @timestamps[key_str] = Time.now
222
220
  end
223
-
221
+
224
222
  value
225
223
  end
226
224
 
@@ -247,8 +245,9 @@ module KeeperSecretsManager
247
245
 
248
246
  def expired?(key)
249
247
  return true unless @timestamps[key]
248
+
250
249
  Time.now - @timestamps[key] > @ttl_seconds
251
250
  end
252
251
  end
253
252
  end
254
- end
253
+ end
@@ -12,7 +12,7 @@ module KeeperSecretsManager
12
12
  'SHA256' => OpenSSL::Digest::SHA256,
13
13
  'SHA512' => OpenSSL::Digest::SHA512
14
14
  }.freeze
15
-
15
+
16
16
  # Generate a TOTP code
17
17
  # @param secret [String] Base32 encoded secret
18
18
  # @param time [Time] Time to generate code for (default: current time)
@@ -23,45 +23,45 @@ module KeeperSecretsManager
23
23
  def self.generate_code(secret, time: Time.now, algorithm: 'SHA1', digits: 6, period: 30)
24
24
  # Validate inputs
25
25
  raise ArgumentError, "Invalid algorithm: #{algorithm}" unless ALGORITHMS.key?(algorithm)
26
- raise ArgumentError, "Digits must be 6 or 8" unless [6, 8].include?(digits)
27
- raise ArgumentError, "Period must be positive" unless period.positive?
28
-
26
+ raise ArgumentError, 'Digits must be 6 or 8' unless [6, 8].include?(digits)
27
+ raise ArgumentError, 'Period must be positive' unless period.positive?
28
+
29
29
  # Decode base32 secret
30
30
  key = Base32.decode(secret.upcase.tr(' ', ''))
31
-
31
+
32
32
  # Calculate time counter
33
33
  counter = (time.to_i / period).floor
34
-
34
+
35
35
  # Convert counter to 8-byte string (big-endian)
36
36
  counter_bytes = [counter].pack('Q>')
37
-
37
+
38
38
  # Generate HMAC
39
39
  digest = ALGORITHMS[algorithm].new
40
40
  hmac = OpenSSL::HMAC.digest(digest, key, counter_bytes)
41
-
41
+
42
42
  # Extract dynamic binary code
43
43
  offset = hmac[-1].ord & 0x0f
44
44
  code = (hmac[offset].ord & 0x7f) << 24 |
45
45
  (hmac[offset + 1].ord & 0xff) << 16 |
46
46
  (hmac[offset + 2].ord & 0xff) << 8 |
47
47
  (hmac[offset + 3].ord & 0xff)
48
-
48
+
49
49
  # Generate final OTP value
50
- otp = code % (10 ** digits)
51
-
50
+ otp = code % (10**digits)
51
+
52
52
  # Pad with leading zeros if necessary
53
53
  otp.to_s.rjust(digits, '0')
54
54
  end
55
-
55
+
56
56
  # Parse TOTP URL (otpauth://totp/...)
57
57
  # @param url [String] TOTP URL
58
58
  # @return [Hash] Parsed components
59
59
  def self.parse_url(url)
60
60
  uri = URI(url)
61
-
62
- raise ArgumentError, "Invalid TOTP URL scheme" unless uri.scheme == 'otpauth'
63
- raise ArgumentError, "Invalid TOTP URL type" unless uri.host == 'totp'
64
-
61
+
62
+ raise ArgumentError, 'Invalid TOTP URL scheme' unless uri.scheme == 'otpauth'
63
+ raise ArgumentError, 'Invalid TOTP URL type' unless uri.host == 'totp'
64
+
65
65
  # Extract label (issuer:account or just account)
66
66
  path = uri.path[1..-1] # Remove leading /
67
67
  if path.include?(':')
@@ -70,10 +70,10 @@ module KeeperSecretsManager
70
70
  account = path
71
71
  issuer = nil
72
72
  end
73
-
73
+
74
74
  # Parse query parameters
75
75
  params = URI.decode_www_form(uri.query || '').to_h
76
-
76
+
77
77
  {
78
78
  'account' => URI.decode_www_form_component(account || ''),
79
79
  'issuer' => issuer ? URI.decode_www_form_component(issuer) : params['issuer'],
@@ -83,7 +83,7 @@ module KeeperSecretsManager
83
83
  'period' => (params['period'] || '30').to_i
84
84
  }
85
85
  end
86
-
86
+
87
87
  # Generate TOTP URL
88
88
  # @param account [String] Account name (e.g., email)
89
89
  # @param secret [String] Base32 encoded secret
@@ -94,20 +94,20 @@ module KeeperSecretsManager
94
94
  # @return [String] TOTP URL
95
95
  def self.generate_url(account, secret, issuer: nil, algorithm: 'SHA1', digits: 6, period: 30)
96
96
  label = issuer ? "#{issuer}:#{account}" : account
97
-
97
+
98
98
  params = {
99
99
  'secret' => secret,
100
100
  'algorithm' => algorithm,
101
101
  'digits' => digits,
102
102
  'period' => period
103
103
  }
104
-
104
+
105
105
  params['issuer'] = issuer if issuer
106
-
106
+
107
107
  query = URI.encode_www_form(params)
108
108
  "otpauth://totp/#{URI.encode_www_form_component(label)}?#{query}"
109
109
  end
110
-
110
+
111
111
  # Validate a TOTP code
112
112
  # @param secret [String] Base32 encoded secret
113
113
  # @param code [String] Code to validate
@@ -122,13 +122,13 @@ module KeeperSecretsManager
122
122
  (-window..window).each do |offset|
123
123
  test_time = time + (offset * period)
124
124
  test_code = generate_code(secret, time: test_time, algorithm: algorithm, digits: digits, period: period)
125
-
125
+
126
126
  return true if test_code == code
127
127
  end
128
-
128
+
129
129
  false
130
130
  end
131
-
131
+
132
132
  # Generate a random secret suitable for TOTP
133
133
  # @param length [Integer] Number of bytes (default: 20 for 160 bits)
134
134
  # @return [String] Base32 encoded secret
@@ -137,4 +137,4 @@ module KeeperSecretsManager
137
137
  Base32.encode(random_bytes).delete('=')
138
138
  end
139
139
  end
140
- end
140
+ end
@@ -67,6 +67,75 @@ module KeeperSecretsManager
67
67
  generate_random_bytes(16)
68
68
  end
69
69
 
70
+ # Generate a cryptographically secure random password
71
+ #
72
+ # @param length [Integer] Total password length (default: 64)
73
+ # @param lowercase [Integer] Minimum number of lowercase letters (default: 0)
74
+ # @param uppercase [Integer] Minimum number of uppercase letters (default: 0)
75
+ # @param digits [Integer] Minimum number of digit characters (default: 0)
76
+ # @param special_characters [Integer] Minimum number of special characters (default: 0)
77
+ # @return [String] Generated password
78
+ # @raise [ArgumentError] If parameters are invalid or minimums exceed length
79
+ #
80
+ # @example Generate a default 64-character password
81
+ # password = KeeperSecretsManager::Utils.generate_password
82
+ # # => "Xk9$mP2...64 chars total"
83
+ #
84
+ # @example Generate a 32-character password with specific requirements
85
+ # password = KeeperSecretsManager::Utils.generate_password(
86
+ # length: 32,
87
+ # lowercase: 2,
88
+ # uppercase: 2,
89
+ # digits: 2,
90
+ # special_characters: 2
91
+ # )
92
+ # # => "aB12$...32 chars with at least 2 of each type"
93
+ #
94
+ # @example Use with record update
95
+ # record = secrets_manager.get_secrets(['RECORD_UID']).first
96
+ # record.password = KeeperSecretsManager::Utils.generate_password(length: 20)
97
+ # secrets_manager.update_secret(record)
98
+ def generate_password(length: 64, lowercase: 0, uppercase: 0, digits: 0, special_characters: 0)
99
+ # Validate inputs
100
+ raise ArgumentError, 'Length must be positive' if length <= 0
101
+ raise ArgumentError, 'Character counts must be non-negative' if [lowercase, uppercase, digits, special_characters].any?(&:negative?)
102
+
103
+ total_minimums = lowercase + uppercase + digits + special_characters
104
+ raise ArgumentError, "Sum of character minimums (#{total_minimums}) cannot exceed password length (#{length})" if total_minimums > length
105
+
106
+ # Character sets
107
+ lowercase_chars = 'abcdefghijklmnopqrstuvwxyz'
108
+ uppercase_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
109
+ digit_chars = '0123456789'
110
+ special_chars = '!@#$%^&*()_+-=[]{}|;:,.<>?'
111
+
112
+ # Build password character array
113
+ password_chars = []
114
+
115
+ # Add minimum required characters from each category
116
+ lowercase.times { password_chars << lowercase_chars[SecureRandom.random_number(lowercase_chars.length)] }
117
+ uppercase.times { password_chars << uppercase_chars[SecureRandom.random_number(uppercase_chars.length)] }
118
+ digits.times { password_chars << digit_chars[SecureRandom.random_number(digit_chars.length)] }
119
+ special_characters.times { password_chars << special_chars[SecureRandom.random_number(special_chars.length)] }
120
+
121
+ # Fill remaining length with random characters from all categories
122
+ remaining = length - total_minimums
123
+ all_chars = lowercase_chars + uppercase_chars + digit_chars + special_chars
124
+
125
+ remaining.times do
126
+ password_chars << all_chars[SecureRandom.random_number(all_chars.length)]
127
+ end
128
+
129
+ # Shuffle using Fisher-Yates algorithm with SecureRandom for cryptographic security
130
+ # This ensures minimum characters aren't clustered at the beginning
131
+ (password_chars.length - 1).downto(1) do |i|
132
+ j = SecureRandom.random_number(i + 1)
133
+ password_chars[i], password_chars[j] = password_chars[j], password_chars[i]
134
+ end
135
+
136
+ password_chars.join
137
+ end
138
+
70
139
  # Get current time in milliseconds
71
140
  def now_milliseconds
72
141
  (Time.now.to_f * 1000).to_i
@@ -75,7 +144,7 @@ module KeeperSecretsManager
75
144
  # Convert string to boolean
76
145
  def strtobool(val)
77
146
  return val if val.is_a?(TrueClass) || val.is_a?(FalseClass)
78
-
147
+
79
148
  val_str = val.to_s.downcase.strip
80
149
  case val_str
81
150
  when 'true', '1', 'yes', 'y', 'on'
@@ -94,7 +163,7 @@ module KeeperSecretsManager
94
163
 
95
164
  # Deep merge hashes
96
165
  def deep_merge(hash1, hash2)
97
- hash1.merge(hash2) do |key, old_val, new_val|
166
+ hash1.merge(hash2) do |_key, old_val, new_val|
98
167
  if old_val.is_a?(Hash) && new_val.is_a?(Hash)
99
168
  deep_merge(old_val, new_val)
100
169
  else
@@ -112,10 +181,9 @@ module KeeperSecretsManager
112
181
 
113
182
  # Convert snake_case to camelCase
114
183
  def snake_to_camel(str, capitalize_first = false)
115
- result = str.split('_').map.with_index do |word, i|
184
+ str.split('_').map.with_index do |word, i|
116
185
  i == 0 && !capitalize_first ? word : word.capitalize
117
186
  end.join
118
- result
119
187
  end
120
188
 
121
189
  # Safe integer conversion
@@ -135,10 +203,10 @@ module KeeperSecretsManager
135
203
  # Parse server URL from hostname
136
204
  def get_server_url(hostname, use_ssl = true)
137
205
  return nil if blank?(hostname)
138
-
206
+
139
207
  # Remove protocol if present
140
208
  hostname = hostname.sub(%r{^https?://}, '')
141
-
209
+
142
210
  # Build URL
143
211
  protocol = use_ssl ? 'https' : 'http'
144
212
  "#{protocol}://#{hostname}"
@@ -151,13 +219,13 @@ module KeeperSecretsManager
151
219
  parts = token_or_hostname.split(':')
152
220
  return parts[0].upcase if parts.length >= 2
153
221
  end
154
-
222
+
155
223
  # Check if hostname matches a known region
156
224
  hostname = token_or_hostname.to_s.downcase
157
225
  KeeperGlobals::KEEPER_SERVERS.each do |region, server|
158
226
  return region if hostname.include?(server)
159
227
  end
160
-
228
+
161
229
  # Default to US
162
230
  'US'
163
231
  end
@@ -165,12 +233,12 @@ module KeeperSecretsManager
165
233
  # Validate UID format
166
234
  def valid_uid?(uid)
167
235
  return false if blank?(uid)
168
-
236
+
169
237
  # UIDs are base64url encoded 16-byte values
170
238
  begin
171
239
  bytes = url_safe_str_to_bytes(uid)
172
240
  bytes.length == 16
173
- rescue
241
+ rescue StandardError
174
242
  false
175
243
  end
176
244
  end
@@ -180,17 +248,15 @@ module KeeperSecretsManager
180
248
  attempt = 0
181
249
  begin
182
250
  yield
183
- rescue => e
251
+ rescue StandardError => e
184
252
  attempt += 1
185
- if attempt >= max_attempts
186
- raise e
187
- end
188
-
189
- delay = [base_delay * (2 ** (attempt - 1)), max_delay].min
253
+ raise e if attempt >= max_attempts
254
+
255
+ delay = [base_delay * (2**(attempt - 1)), max_delay].min
190
256
  sleep(delay)
191
257
  retry
192
258
  end
193
259
  end
194
260
  end
195
261
  end
196
- end
262
+ end
@@ -1,3 +1,3 @@
1
1
  module KeeperSecretsManager
2
- VERSION = '17.0.4'.freeze
3
- end
2
+ VERSION = '17.1.0'.freeze
3
+ end
@@ -24,15 +24,15 @@ module KeeperSecretsManager
24
24
  def self.new(options = {})
25
25
  Core::SecretsManager.new(options)
26
26
  end
27
-
27
+
28
28
  # Convenience method to create from token
29
29
  def self.from_token(token, options = {})
30
30
  Core::SecretsManager.new(options.merge(token: token))
31
31
  end
32
-
32
+
33
33
  # Convenience method to create from config file
34
34
  def self.from_file(filename, options = {})
35
35
  storage = Storage::FileStorage.new(filename)
36
36
  Core::SecretsManager.new(options.merge(config: storage))
37
37
  end
38
- end
38
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: keeper_secrets_manager
3
3
  version: !ruby/object:Gem::Version
4
- version: 17.0.4
4
+ version: 17.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keeper Security
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-06-25 00:00:00.000000000 Z
11
+ date: 2025-11-06 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Ruby SDK for Keeper Secrets Manager - A zero-knowledge platform for managing
14
14
  and protecting infrastructure secrets
@@ -21,24 +21,10 @@ files:
21
21
  - ".rspec"
22
22
  - ".ruby-version"
23
23
  - CHANGELOG.md
24
- - DEVELOPER_SETUP.md
25
24
  - Gemfile
26
25
  - LICENSE
27
- - MANUAL_TESTING_GUIDE.md
28
26
  - README.md
29
- - RUBY_SDK_COMPLETE_DOCUMENTATION.md
30
- - RUBY_SDK_COMPREHENSIVE_SUMMARY.md
31
27
  - Rakefile
32
- - examples/01_quick_start.rb
33
- - examples/02_authentication.rb
34
- - examples/03_retrieve_secrets.rb
35
- - examples/04_create_update_delete.rb
36
- - examples/05_field_types.rb
37
- - examples/06_files.rb
38
- - examples/07_folders.rb
39
- - examples/08_notation.rb
40
- - examples/09_totp.rb
41
- - examples/README.md
42
28
  - lib/keeper_secrets_manager.rb
43
29
  - lib/keeper_secrets_manager/config_keys.rb
44
30
  - lib/keeper_secrets_manager/core.rb
@@ -63,7 +49,7 @@ metadata:
63
49
  homepage_uri: https://github.com/Keeper-Security/secrets-manager
64
50
  source_code_uri: https://github.com/Keeper-Security/secrets-manager
65
51
  changelog_uri: https://github.com/Keeper-Security/secrets-manager/blob/master/CHANGELOG.md
66
- post_install_message:
52
+ post_install_message:
67
53
  rdoc_options: []
68
54
  require_paths:
69
55
  - lib
@@ -71,15 +57,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
71
57
  requirements:
72
58
  - - ">="
73
59
  - !ruby/object:Gem::Version
74
- version: 2.6.0
60
+ version: 3.1.0
75
61
  required_rubygems_version: !ruby/object:Gem::Requirement
76
62
  requirements:
77
63
  - - ">="
78
64
  - !ruby/object:Gem::Version
79
65
  version: '0'
80
66
  requirements: []
81
- rubygems_version: 3.0.3.1
82
- signing_key:
67
+ rubygems_version: 3.5.22
68
+ signing_key:
83
69
  specification_version: 4
84
70
  summary: Keeper Secrets Manager SDK for Ruby
85
71
  test_files: []
data/DEVELOPER_SETUP.md DELETED
File without changes