maquina_credentials 0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 77da8a494d686b452f4ca9745d69aaa3ce3bff688c7010ef651ed078604e5495
4
+ data.tar.gz: 88d37fea885f62326aa550411ab702856c98079170f2c62caa35aa53b426e77e
5
+ SHA512:
6
+ metadata.gz: ca1c6f06ff9cca2c67bd5438e57397cf9a80cd6ad59da3668d0e8b3d4edac1c2a13ab7af8235c64634f55b353a53d1d7b129a17bb08ace881835ad876335dd08
7
+ data.tar.gz: 5df16514d996d6fed68bb431c8c528c305eb1cedafa67dc47fb0298150b6b1e0559f2f015d065a6398345232a350cae4f41e8ab28a081dc4e41d71d49f18d6a4
data/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2026-06-24
4
+
5
+ - Initial release
6
+ - Encrypt and decrypt a `credentials.yml.enc` file with AES-256-GCM
7
+ - `mcr` command line tool with `generate`, `read`, and `write` commands
8
+ - Ruby API via `Maquina::Credentials`
@@ -0,0 +1,10 @@
1
+ # Code of Conduct
2
+
3
+ "maquina_credentials" follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.):
4
+
5
+ * Participants will be tolerant of opposing views.
6
+ * Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
7
+ * When interpreting the words and actions of others, participants should always assume good intentions.
8
+ * Behaviour which can be reasonably considered harassment will not be tolerated.
9
+
10
+ If you have any concerns about behaviour within this project, please contact us at ["mario.chavez@gmail.com"](mailto:"mario.chavez@gmail.com").
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Mario Alberto Chávez Cárdenas (https://maquina.app)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,153 @@
1
+ # Maquina Credentials
2
+
3
+ Encrypt and decrypt a single `credentials.yml.enc` file with AES-256-GCM.
4
+
5
+ `maquina_credentials` is a small, dependency-free gem for storing secrets in an
6
+ encrypted file that can safely travel with your source or container image. The
7
+ master key never lives in the file — it is supplied at runtime through the
8
+ `MAQUINA_MASTER_KEY` environment variable. Reading a missing key returns an
9
+ empty string and never raises, so it is safe to call in code paths that may run
10
+ without credentials configured.
11
+
12
+ It is inspired by Rails' encrypted credentials, distilled down to a single file,
13
+ a command line tool, and a plain Ruby API — with no dependency on Rails or
14
+ Active Support.
15
+
16
+ ## Installation
17
+
18
+ Add it to your `Gemfile`:
19
+
20
+ ```ruby
21
+ gem "maquina_credentials"
22
+ ```
23
+
24
+ Then run `bundle install`. Or install it directly:
25
+
26
+ ```sh
27
+ gem install maquina_credentials
28
+ ```
29
+
30
+ Requires Ruby >= 3.2.0.
31
+
32
+ ## Master key
33
+
34
+ Generate a master key once and store it as a secret (never commit it):
35
+
36
+ ```sh
37
+ mcr generate
38
+ ```
39
+
40
+ This prints a fresh random key built from `SecureRandom.hex(32)`. If you do not
41
+ have the executable handy, the equivalent in plain Ruby is:
42
+
43
+ ```sh
44
+ ruby -e "require 'securerandom'; puts SecureRandom.hex(32)"
45
+ ```
46
+
47
+ Provide the key to every command through `MAQUINA_MASTER_KEY`. Any 32-byte key
48
+ is used directly; keys of any other length (including the 64-character hex
49
+ string above) are run through HKDF-SHA256 to derive the encryption key.
50
+
51
+ ## Command line
52
+
53
+ The gem installs an `mcr` executable.
54
+
55
+ Read a value (dot-paths traverse nested keys):
56
+
57
+ ```sh
58
+ MAQUINA_MASTER_KEY=<key> mcr read database.password
59
+ ```
60
+
61
+ Write credentials from piped YAML:
62
+
63
+ ```sh
64
+ MAQUINA_MASTER_KEY=<key> printf "database:\n password: prod-secret\n" | mcr write
65
+ ```
66
+
67
+ Use a specific encrypted file:
68
+
69
+ ```sh
70
+ mcr --file /path/to/credentials.yml.enc read database.password
71
+ ```
72
+
73
+ Generate a new master key:
74
+
75
+ ```sh
76
+ mcr generate
77
+ ```
78
+
79
+ Show help:
80
+
81
+ ```sh
82
+ mcr help
83
+ ```
84
+
85
+ ### File selection
86
+
87
+ The encrypted file is resolved in this order:
88
+
89
+ 1. The `--file PATH` option, when given.
90
+ 2. The `MAQUINA_CREDENTIALS_FILE` environment variable.
91
+ 3. `credentials.yml.enc` in the current working directory.
92
+
93
+ ## Ruby API
94
+
95
+ The same class is available through `require "maquina_credentials"`.
96
+
97
+ ```ruby
98
+ credentials = Maquina::Credentials.new
99
+ credentials.read("anthropic_key")
100
+ credentials.read("database.password") # dot-path traversal
101
+
102
+ # Writing replaces the whole file from a full hash:
103
+ Maquina::Credentials.write({
104
+ anthropic_key: "sk-ant-...",
105
+ database: {password: "prod-secret"}
106
+ })
107
+ ```
108
+
109
+ - Missing credential paths return an empty string (`""`), never `nil`.
110
+ - All values are returned as strings.
111
+ - A missing or empty `MAQUINA_MASTER_KEY` raises
112
+ `Maquina::Credentials::MasterKeyMissing` (only when a credentials file
113
+ actually exists).
114
+ - A wrong key, tampered, truncated, or otherwise unreadable file raises
115
+ `Maquina::Credentials::DecryptionFailed`.
116
+ - An instance decrypts once and caches the result for the life of the object.
117
+
118
+ ## Security notes
119
+
120
+ - Cipher is AES-256-GCM; the wire format is strict-base64 of
121
+ `IV (12 bytes) || ciphertext || GCM auth tag (16 bytes)`.
122
+ - A fresh random IV is generated on every write.
123
+ - Writes are atomic (temp file renamed into place) and the file is set to mode
124
+ `0600`.
125
+ - YAML is parsed with `safe_load(permitted_classes: [])` on both read and write,
126
+ so a tampered file cannot instantiate arbitrary Ruby objects.
127
+
128
+ ## Container workflow
129
+
130
+ ```sh
131
+ # 1. Generate the master key once and store it as a secret.
132
+ mcr generate
133
+
134
+ # 2. Write credentials locally.
135
+ MAQUINA_MASTER_KEY=<key> printf "anthropic_key: sk-ant-...\n" | mcr write
136
+
137
+ # 3. Commit credentials.yml.enc into the image — the key never goes in.
138
+ docker build -t my-app .
139
+
140
+ # 4. Inject the key at runtime (or via orchestrator secrets).
141
+ docker run -e MAQUINA_MASTER_KEY=<key> my-app
142
+ ```
143
+
144
+ ## Development
145
+
146
+ After checking out the repo, run `bin/setup` to install dependencies. Then run
147
+ `bundle exec rake` to run the tests and Standard Ruby. Use `bin/console` for an
148
+ interactive prompt with the gem preloaded.
149
+
150
+ ## License
151
+
152
+ The gem is available as open source under the terms of the
153
+ [MIT License](https://opensource.org/licenses/MIT).
data/exe/mcr ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "maquina_credentials"
5
+ require "maquina/credentials/cli"
6
+
7
+ exit Maquina::Credentials::CLI.start(ARGV)
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+ require "securerandom"
5
+ require "yaml"
6
+
7
+ module Maquina
8
+ class Credentials
9
+ class CLI
10
+ def self.start(argv, stdout: $stdout, stderr: $stderr, stdin: $stdin)
11
+ new(argv, stdout: stdout, stderr: stderr, stdin: stdin).run
12
+ end
13
+
14
+ def initialize(argv, stdout:, stderr:, stdin:)
15
+ @argv = argv.dup
16
+ @stdout = stdout
17
+ @stderr = stderr
18
+ @stdin = stdin
19
+ @credentials_path = nil
20
+ end
21
+
22
+ def run
23
+ command = parser.parse!(@argv).first
24
+
25
+ case command
26
+ when "help", nil
27
+ @stdout.puts parser
28
+ 0
29
+ when "generate"
30
+ generate
31
+ when "read"
32
+ read(@argv[1])
33
+ when "write"
34
+ write
35
+ else
36
+ @stderr.puts parser
37
+ command ? 1 : 0
38
+ end
39
+ rescue OptionParser::ParseError => error
40
+ @stderr.puts error.message
41
+ @stderr.puts parser
42
+ 1
43
+ rescue MasterKeyMissing
44
+ @stderr.puts "MAQUINA_MASTER_KEY is required"
45
+ 1
46
+ rescue DecryptionFailed
47
+ @stderr.puts "Failed to decrypt credentials"
48
+ 1
49
+ rescue Psych::Exception
50
+ @stderr.puts "Input must be a valid YAML hash"
51
+ 1
52
+ end
53
+
54
+ private
55
+
56
+ def parser
57
+ @parser ||= OptionParser.new do |options|
58
+ options.banner = <<~TEXT.chomp
59
+ Usage:
60
+ mcr help
61
+ mcr generate
62
+ mcr read KEY [--file PATH]
63
+ mcr write [--file PATH] < credentials.yml
64
+
65
+ Commands:
66
+ help Show this help text.
67
+ generate Print a new random master key to stdout.
68
+ read Print a credential value to stdout.
69
+ write Encrypt YAML from stdin and write it to the credentials file.
70
+
71
+ File selection:
72
+ Use --file PATH to read or write a specific file.
73
+ Without --file, mcr uses credentials.yml.enc in the current directory.
74
+
75
+ Examples:
76
+ mcr generate
77
+ mcr read database.password
78
+ mcr --file /tmp/credentials.yml.enc write < credentials.yml
79
+ mcr help
80
+ TEXT
81
+
82
+ options.on("-f", "--file PATH", "Credentials file path") do |path|
83
+ @credentials_path = path
84
+ end
85
+
86
+ options.on("-h", "--help", "Print this help") do
87
+ @stdout.puts options
88
+ exit 0
89
+ end
90
+ end
91
+ end
92
+
93
+ def generate
94
+ @stdout.puts SecureRandom.hex(32)
95
+ 0
96
+ end
97
+
98
+ def read(key)
99
+ unless key
100
+ @stderr.puts "Missing KEY"
101
+ return 1
102
+ end
103
+
104
+ @stdout.puts Credentials.new(credentials_path: @credentials_path).read(key)
105
+ 0
106
+ end
107
+
108
+ def write
109
+ input = @stdin.read
110
+ hash = YAML.safe_load(input, permitted_classes: [], symbolize_names: false)
111
+
112
+ unless hash.is_a?(Hash)
113
+ @stderr.puts "Input must be a YAML hash"
114
+ return 1
115
+ end
116
+
117
+ Credentials.write(hash, credentials_path: @credentials_path)
118
+ 0
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "openssl"
5
+ require "securerandom"
6
+ require "yaml"
7
+
8
+ module Maquina
9
+ class Credentials
10
+ class MasterKeyMissing < StandardError; end
11
+ class DecryptionFailed < StandardError; end
12
+
13
+ DEFAULT_CREDENTIALS_PATH = "credentials.yml.enc"
14
+ ENV_KEY = "MAQUINA_MASTER_KEY"
15
+ FILE_ENV_KEY = "MAQUINA_CREDENTIALS_FILE"
16
+ CIPHER = "aes-256-gcm"
17
+ KEY_LENGTH = 32
18
+ IV_LENGTH = 12
19
+ AUTH_TAG_LENGTH = 16
20
+ HKDF_INFO = "maquina-credentials-v1"
21
+
22
+ def self.write(hash, credentials_path: nil)
23
+ credentials_path = resolve_credentials_path(credentials_path)
24
+ payload = YAML.dump(deep_stringify(hash))
25
+ encrypted = encrypt(payload, master_key)
26
+ tmp_path = "#{credentials_path}.tmp.#{Process.pid}"
27
+
28
+ FileUtils.mkdir_p(File.dirname(credentials_path))
29
+ File.write(tmp_path, encrypted)
30
+ File.rename(tmp_path, credentials_path)
31
+ File.chmod(0o600, credentials_path)
32
+ ensure
33
+ File.delete(tmp_path) if tmp_path && File.exist?(tmp_path)
34
+ end
35
+
36
+ def self.encrypt(payload, raw_key)
37
+ cipher = OpenSSL::Cipher.new(CIPHER)
38
+ cipher.encrypt
39
+ cipher.key = derive_key(raw_key)
40
+ iv = cipher.random_iv
41
+ cipher.iv = iv
42
+ cipher.auth_data = ""
43
+
44
+ ciphertext = cipher.update(payload) + cipher.final
45
+ strict_base64_encode(iv + ciphertext + cipher.auth_tag)
46
+ end
47
+
48
+ def self.decrypt(encrypted, raw_key)
49
+ decoded = strict_base64_decode(encrypted)
50
+ raise DecryptionFailed if decoded.bytesize < IV_LENGTH + AUTH_TAG_LENGTH
51
+
52
+ iv = decoded.byteslice(0, IV_LENGTH)
53
+ auth_tag = decoded.byteslice(-AUTH_TAG_LENGTH, AUTH_TAG_LENGTH)
54
+ ciphertext = decoded.byteslice(IV_LENGTH, decoded.bytesize - IV_LENGTH - AUTH_TAG_LENGTH)
55
+
56
+ cipher = OpenSSL::Cipher.new(CIPHER)
57
+ cipher.decrypt
58
+ cipher.key = derive_key(raw_key)
59
+ cipher.iv = iv
60
+ cipher.auth_tag = auth_tag
61
+ cipher.auth_data = ""
62
+ cipher.update(ciphertext) + cipher.final
63
+ rescue ArgumentError, OpenSSL::Cipher::CipherError
64
+ raise DecryptionFailed
65
+ end
66
+
67
+ def self.strict_base64_encode(bytes)
68
+ [bytes].pack("m0")
69
+ end
70
+
71
+ def self.strict_base64_decode(encoded)
72
+ unless encoded.match?(/\A(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?\z/)
73
+ raise ArgumentError
74
+ end
75
+
76
+ encoded.unpack1("m0")
77
+ end
78
+
79
+ def self.derive_key(raw_key)
80
+ raw_key = raw_key.b
81
+ return raw_key[0, KEY_LENGTH] if raw_key.bytesize == KEY_LENGTH
82
+
83
+ OpenSSL::KDF.hkdf(
84
+ raw_key,
85
+ salt: "",
86
+ info: HKDF_INFO,
87
+ length: KEY_LENGTH,
88
+ hash: "SHA256"
89
+ )
90
+ end
91
+
92
+ def self.master_key
93
+ key = ENV[ENV_KEY]
94
+ raise MasterKeyMissing if key.nil? || key.empty?
95
+
96
+ key
97
+ end
98
+
99
+ def self.resolve_credentials_path(credentials_path = nil)
100
+ return credentials_path unless credentials_path.nil? || credentials_path.empty?
101
+
102
+ env_path = ENV[FILE_ENV_KEY]
103
+ return env_path unless env_path.nil? || env_path.empty?
104
+
105
+ File.join(Dir.pwd, DEFAULT_CREDENTIALS_PATH)
106
+ end
107
+
108
+ def self.deep_stringify(obj)
109
+ case obj
110
+ when Hash
111
+ obj.transform_keys(&:to_s).transform_values { |value| deep_stringify(value) }
112
+ when Array
113
+ obj.map { |value| deep_stringify(value) }
114
+ else
115
+ obj
116
+ end
117
+ end
118
+
119
+ def initialize(credentials_path: nil)
120
+ @credentials_path = self.class.resolve_credentials_path(credentials_path)
121
+ @credentials = nil
122
+ @loaded = false
123
+ end
124
+
125
+ def read(path)
126
+ return "" if path.nil? || path.empty?
127
+
128
+ value = path.split(".").reduce(credentials) do |current, key|
129
+ break unless current.is_a?(Hash)
130
+
131
+ current[key]
132
+ end
133
+
134
+ value.nil? ? "" : value.to_s
135
+ end
136
+
137
+ private
138
+
139
+ attr_reader :credentials_path
140
+
141
+ def credentials
142
+ return @credentials if @loaded
143
+
144
+ @credentials = load_credentials
145
+ @loaded = true
146
+ @credentials
147
+ end
148
+
149
+ def load_credentials
150
+ return {} unless File.exist?(credentials_path)
151
+
152
+ decrypted = self.class.decrypt(File.read(credentials_path), self.class.master_key)
153
+ loaded = YAML.safe_load(decrypted, permitted_classes: [], symbolize_names: false)
154
+ raise DecryptionFailed unless loaded.is_a?(Hash)
155
+
156
+ self.class.deep_stringify(loaded)
157
+ rescue Psych::Exception
158
+ raise DecryptionFailed
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Maquina
4
+ class Credentials
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "maquina_credentials/version"
4
+ require_relative "maquina/credentials"
@@ -0,0 +1,23 @@
1
+ module Maquina
2
+ class Credentials
3
+ VERSION: String
4
+
5
+ class MasterKeyMissing < StandardError
6
+ end
7
+
8
+ class DecryptionFailed < StandardError
9
+ end
10
+
11
+ def self.write: (Hash[untyped, untyped] hash, ?credentials_path: String? credentials_path) -> void
12
+
13
+ def self.encrypt: (String payload, String raw_key) -> String
14
+
15
+ def self.decrypt: (String encrypted, String raw_key) -> String
16
+
17
+ def self.resolve_credentials_path: (?String? credentials_path) -> String
18
+
19
+ def initialize: (?credentials_path: String? credentials_path) -> void
20
+
21
+ def read: (String? path) -> String
22
+ end
23
+ end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: maquina_credentials
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mario Alberto Chávez Cárdenas
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: irb
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rake
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '13.0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '13.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: minitest
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '5.16'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '5.16'
54
+ - !ruby/object:Gem::Dependency
55
+ name: standard
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.55'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.55'
68
+ description: A small, dependency-free gem that encrypts and decrypts a single credentials.yml.enc
69
+ file with AES-256-GCM. Ships an `mcr` command line tool for reading and writing
70
+ values, plus a Ruby API. Designed for container and server workflows where the encrypted
71
+ file travels with the image and the master key is injected at runtime via MAQUINA_MASTER_KEY.
72
+ email:
73
+ - mario.chavez@gmail.com
74
+ executables:
75
+ - mcr
76
+ extensions: []
77
+ extra_rdoc_files: []
78
+ files:
79
+ - CHANGELOG.md
80
+ - CODE_OF_CONDUCT.md
81
+ - LICENSE.txt
82
+ - README.md
83
+ - exe/mcr
84
+ - lib/maquina/credentials.rb
85
+ - lib/maquina/credentials/cli.rb
86
+ - lib/maquina_credentials.rb
87
+ - lib/maquina_credentials/version.rb
88
+ - sig/maquina_credentials.rbs
89
+ homepage: https://maquina.app
90
+ licenses:
91
+ - MIT
92
+ metadata:
93
+ homepage_uri: https://maquina.app
94
+ source_code_uri: https://github.com/maquina-app/maquina_credentials
95
+ documentation_uri: https://github.com/maquina-app/maquina_credentials/blob/main/README.md
96
+ changelog_uri: https://github.com/maquina-app/maquina_credentials/blob/main/CHANGELOG.md
97
+ bug_tracker_uri: https://github.com/maquina-app/maquina_credentials/issues
98
+ rubygems_mfa_required: 'true'
99
+ rdoc_options: []
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: 3.2.0
107
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ requirements: []
113
+ rubygems_version: 4.0.14
114
+ specification_version: 4
115
+ summary: Encrypted credentials for the command line
116
+ test_files: []