Module: Lich::Common::CredentialScrub
- Defined in:
- documented/common/credential_scrub.rb
Overview
Redacts startup credentials once the login sequence no longer needs them.
Lich keeps startup credentials where a running script can read them:
@launch_data and @argv_options are instance variables on the top-level
main object, and ARGV is a global constant. All three outlive login, so
both the eaccess key and the account password stay readable for the rest of
the session. Both are live credentials: the key is not single-use and stays
valid for some period after issue, so a key read hours into a session may
still be usable.
Values are overwritten in place rather than reassigned because other objects hold references to the same Array, Hash, and String instances (the login GUI keeps the launch data array; the argv option pipeline threads one options hash through three stages). Replacing a container would leave those references pointing at unscrubbed copies.
Scope: this closes script-readable and log-readable paths. It does not guarantee the bytes are gone from process memory. Earlier copies may survive in socket write buffers, in a spawned frontend's argv, or in unreclaimed heap.
Constant Summary collapse
- REDACTED =
Replacement text for a redacted value.
'[scrubbed]'- LAUNCH_DATA_SECRET =
launch_data entry holding the eaccess key.
/\AKEY=/i- ARGV_SECRET =
argv entries of the form --flag=secret. Deliberately excludes --account=, which is an account identifier rather than a credential.
/\A(--(?:password|master-password)=)(.+)\z/i- OPTION_SECRET_KEYS =
argv_options keys holding reusable credentials.
[:password].freeze
Class Method Summary collapse
-
.redact_argv(argv) ⇒ Array<String>
Returns a copy of argv with secret values masked, without touching the input.
-
.scrub_argv!(argv) ⇒ Integer
Overwrites secret-bearing argv values in place, preserving the flag prefix so later ARGV.include? and ARGV.find checks behave unchanged.
-
.scrub_launch_data!(launch_data) ⇒ Integer
Overwrites the KEY= entry of a launch data array in place.
-
.scrub_options!(options) ⇒ Array<Symbol>
Overwrites secret values in an options hash in place.
-
.shred_file(path, attempts: 3) ⇒ Boolean
Overwrites a file's contents and removes it.
Class Method Details
.redact_argv(argv) ⇒ Array<String>
Returns a copy of argv with secret values masked, without touching the input. Use this where the real argv must still be used for its original purpose (e.g. exec) but a redacted form is needed for something incidental to that purpose, such as a log line.
Unlike scrub_argv!, this never mutates: reconnect logs its exec
argv while also passing the same, unredacted array to exec, so an
in-place scrub here would strip the real password before the process
replacement that needs it.
102 103 104 105 106 107 108 109 110 111 |
# File 'documented/common/credential_scrub.rb', line 102 def redact_argv(argv) return [] unless argv.is_a?(Array) argv.map do |arg| next arg unless arg.is_a?(String) match = ARGV_SECRET.match(arg) match.nil? ? arg : "#{match[1]}#{REDACTED}" end end |
.scrub_argv!(argv) ⇒ Integer
Overwrites secret-bearing argv values in place, preserving the flag prefix so later ARGV.include? and ARGV.find checks behave unchanged.
73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'documented/common/credential_scrub.rb', line 73 def scrub_argv!(argv) return 0 unless argv.is_a?(Array) scrubbed = 0 argv.each_with_index do |arg, index| next unless arg.is_a?(String) match = ARGV_SECRET.match(arg) next if match.nil? scrubbed += 1 if overwrite(argv, index, "#{match[1]}#{REDACTED}") end scrubbed end |
.scrub_launch_data!(launch_data) ⇒ Integer
Overwrites the KEY= entry of a launch data array in place.
56 57 58 59 60 61 62 63 64 65 66 |
# File 'documented/common/credential_scrub.rb', line 56 def scrub_launch_data!(launch_data) return 0 unless launch_data.is_a?(Array) scrubbed = 0 launch_data.each_with_index do |line, index| next unless line.is_a?(String) && line.match?(LAUNCH_DATA_SECRET) scrubbed += 1 if overwrite(launch_data, index, "KEY=#{REDACTED}") end scrubbed end |
.scrub_options!(options) ⇒ Array<Symbol>
Overwrites secret values in an options hash in place.
117 118 119 120 121 122 123 124 125 126 |
# File 'documented/common/credential_scrub.rb', line 117 def () return [] unless .is_a?(Hash) OPTION_SECRET_KEYS.select do |key| value = [key] next false unless value.is_a?(String) overwrite(, key, REDACTED) end end |
.shred_file(path, attempts: 3) ⇒ Boolean
Overwrites a file's contents and removes it. Never raises.
Overwrite-then-delete is best effort: copy-on-write, journaling, and flash translation layers may retain the original blocks. The retry loop exists because Windows refuses deletion while a spawned frontend still holds the file open.
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'documented/common/credential_scrub.rb', line 138 def shred_file(path, attempts: 3) path = path.to_s return false if path.empty? overwrite_contents(path) attempts.times do |attempt| begin File.delete(path) if File.exist?(path) return true unless File.exist?(path) rescue StandardError nil end sleep 0.05 unless attempt == attempts - 1 end !File.exist?(path) rescue StandardError false end |