Module: Rubino::Util::SecretsMask
- Defined in:
- lib/rubino/util/secrets_mask.rb
Overview
Heuristic masking for credentials in tool arguments. The model often passes secrets through cleanly (env vars, config files), but a stray ‘command: “curl -H ’Authorization: Bearer sk_live_…‘”` showing up in an approval prompt — or, worse, in the persistent scrollback — is a leak waiting to happen. Mask aggressively on display; the underlying tool still receives the real value.
Constant Summary collapse
- SECRET_KEY_TOKENS =
%w[ password passwd secret token bearer api_key apikey api-key access_key accesskey access-key private_key privatekey private-key auth authorization ].freeze
- INLINE_RE =
Pattern that matches ‘key=value`, `key: value`, `key value` for the secret-named keys, inside a free-text string (shell command, URL query). The trailing value is grabbed up to whitespace or a known delimiter; quoted values are grabbed whole. `Bearer <token>` is treated as a single value so `Authorization: Bearer XYZ` masks the whole token instead of leaving XYZ exposed.
/ (?<key>password|passwd|secret|token| api[_-]?key|access[_-]?key|private[_-]?key| authorization|auth|bearer) (?<sep>\s*[:=]\s*|\s+) (?<val>"[^"]+"|'[^']+'|(?:Bearer\s+)?[^"'\s]+) /xi- MASK =
"***"
Class Method Summary collapse
-
.mask_hash(hash) ⇒ Object
Convenience for Hash arguments: returns a new Hash with sensitive values masked, leaving the original untouched (the real value still has to reach the tool).
-
.mask_inline(text) ⇒ Object
Mask inline patterns like ‘Authorization: Bearer XYZ` in any string, whether or not the caller knows the surrounding context.
-
.mask_value(value, key: nil) ⇒ Object
Mask a single value, given the key it belongs to.
-
.sensitive_key?(key) ⇒ Boolean
True if the given key looks sensitive on its own (used when the caller already has key/value pairs, e.g. a Hash of arguments).
Class Method Details
.mask_hash(hash) ⇒ Object
Convenience for Hash arguments: returns a new Hash with sensitive values masked, leaving the original untouched (the real value still has to reach the tool).
76 77 78 79 80 |
# File 'lib/rubino/util/secrets_mask.rb', line 76 def self.mask_hash(hash) return hash unless hash.is_a?(Hash) hash.each_with_object({}) { |(k, v), out| out[k] = mask_value(v, key: k) } end |
.mask_inline(text) ⇒ Object
Mask inline patterns like ‘Authorization: Bearer XYZ` in any string, whether or not the caller knows the surrounding context. Quoted values keep their quotes around the mask so the surrounding structure (`-H “Authorization: ***”`) stays balanced — otherwise the mask would eat a quote and the rest of the string would look like one long open string.
60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/rubino/util/secrets_mask.rb', line 60 def self.mask_inline(text) text.to_s.gsub(INLINE_RE) do m = Regexp.last_match val = m[:val] masked = case val[0] when '"' then %("#{MASK}") when "'" then "'#{MASK}'" else MASK end "#{m[:key]}#{m[:sep]}#{masked}" end end |
.mask_value(value, key: nil) ⇒ Object
Mask a single value, given the key it belongs to. Returns MASK if the key is sensitive; otherwise scans the value for inline secrets.
47 48 49 50 51 52 |
# File 'lib/rubino/util/secrets_mask.rb', line 47 def self.mask_value(value, key: nil) return value if value.nil? return MASK if key && sensitive_key?(key) mask_inline(value.to_s) end |
.sensitive_key?(key) ⇒ Boolean
True if the given key looks sensitive on its own (used when the caller already has key/value pairs, e.g. a Hash of arguments).
40 41 42 43 |
# File 'lib/rubino/util/secrets_mask.rb', line 40 def self.sensitive_key?(key) k = key.to_s.downcase.tr("-", "_") SECRET_KEY_TOKENS.any? { |t| k == t.tr("-", "_") || k.include?(t.tr("-", "_")) } end |