Class: LittleGhost::Support::Redactor

Inherits:
Object
  • Object
show all
Defined in:
lib/little_ghost/support/redactor.rb

Overview

Redactor removes common credential keys, known secret values, and secret-shaped strings from nested diagnostic data. It returns a copy and leaves the caller's value unchanged.

Redaction is a defense-in-depth aid, not proof that arbitrary sensitive content is safe to export. Applications should add known secret values and keep telemetry within an appropriate trust boundary.

Constant Summary collapse

SENSITIVE_KEY =

:nodoc:

/(authorization|api[_-]?key|credential|password|secret|(?:^|[_-])token(?:$|[_-])|cookie|private[_-]?key)/i
SECRET_PATTERNS =
[
  /\bBearer\s+[A-Za-z0-9._~+\/-]+=*/i,
  /\b(?:gh[opusr]_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,})\b/,
  /\bAKIA[A-Z0-9]{16}\b/,
  /\b(?:sk|pk)-[A-Za-z0-9_-]{20,}\b/
].freeze

Instance Method Summary collapse

Constructor Details

#initialize(redactions: [], stringify_keys: false) ⇒ Redactor

Adds literal secrets to the built-in patterns. Values shorter than eight characters are ignored to avoid over-redaction.



23
24
25
26
# File 'lib/little_ghost/support/redactor.rb', line 23

def initialize(redactions: [], stringify_keys: false)
  @redactions = Array(redactions).map(&:to_s).reject { |value| value.length < 8 }.uniq.freeze
  @stringify_keys = stringify_keys
end

Instance Method Details

#call(value, key: nil) ⇒ Object

Produces a redacted copy of value.



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/little_ghost/support/redactor.rb', line 29

def call(value, key: nil)
  return "[REDACTED]" if key && sensitive_key?(key)

  case value
  when Hash
    value.to_h do |child_key, child|
      output_key = @stringify_keys ? child_key.to_s : child_key
      [output_key, call(child, key: child_key)]
    end
  when Array
    value.map { |child| call(child) }
  when String
    scrub_string(value)
  else
    value
  end
end

#scrub_string(value) ⇒ Object

Normalizes invalid UTF-8 and replaces configured and common secrets.



53
54
55
56
57
# File 'lib/little_ghost/support/redactor.rb', line 53

def scrub_string(value)
  normalized = value.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "\uFFFD")
  text = @redactions.reduce(normalized) { |current, secret| current.gsub(secret, "[REDACTED]") }
  SECRET_PATTERNS.reduce(text) { |current, pattern| current.gsub(pattern, "[REDACTED]") }
end

#sensitive_key?(key) ⇒ Boolean

Checks whether key matches a credential-like name.

Returns:

  • (Boolean)


48
49
50
# File 'lib/little_ghost/support/redactor.rb', line 48

def sensitive_key?(key)
  SENSITIVE_KEY.match?(normalize_key(key))
end