Module: Bootprint::Sanitizer

Defined in:
lib/bootprint/sanitizer.rb

Constant Summary collapse

REDACTED =
"[REDACTED]"
JWT_PATTERN =
/\AeyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\z/
PRIVATE_KEY_PATTERN =
/-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/
URL_CREDENTIAL_PATTERN =
%r{\A([a-z][a-z0-9+.-]*://)([^/@\s]+)@}i
HIGH_ENTROPY_PATTERN =
%r{\A[A-Za-z0-9+/_=-]{32,}\z}

Class Method Summary collapse

Class Method Details

.boolean?(value) ⇒ Boolean

Returns:

  • (Boolean)


59
60
61
# File 'lib/bootprint/sanitizer.rb', line 59

def boolean?(value)
  [true, false].include?(value)
end

.high_entropy?(value) ⇒ Boolean

Returns:

  • (Boolean)


77
78
79
80
81
# File 'lib/bootprint/sanitizer.rb', line 77

def high_entropy?(value)
  return false unless HIGH_ENTROPY_PATTERN.match?(value)

  value.chars.uniq.length >= 16
end

.hostname_like?(value) ⇒ Boolean

Returns:

  • (Boolean)


83
84
85
# File 'lib/bootprint/sanitizer.rb', line 83

def hostname_like?(value)
  value.match?(/\A(?=.{1,253}\z)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}\z/i)
end

.path(value) ⇒ Object



23
24
25
# File 'lib/bootprint/sanitizer.rb', line 23

def path(value)
  text(value)
end

.path_variants(path) ⇒ Object



101
102
103
# File 'lib/bootprint/sanitizer.rb', line 101

def path_variants(path)
  [path, path.tr("\\", "/"), path.tr("/", "\\")].uniq.sort_by { |variant| -variant.length }
end

.recursive(value, patterns: Bootprint.configuration.redaction_patterns, privacy: Bootprint.configuration.privacy) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/bootprint/sanitizer.rb', line 27

def recursive(value, patterns: Bootprint.configuration.redaction_patterns, privacy: Bootprint.configuration.privacy)
  case value
  when Hash
    value.each_with_object({}) do |(key, nested), result|
      name = key.to_s
      result[name] = if secret_name?(name, patterns) && !boolean?(nested)
                       (nested)
                     elsif safe_digest_name?(name) && !nested.is_a?(Hash) && !nested.is_a?(Array)
                       nested.to_s
                     else
                       recursive(nested, patterns:, privacy:)
                     end
    end
  when Array
    value.map { |nested| recursive(nested, patterns:, privacy:) }
  when String
    sanitize_value(value, privacy:)
  else
    value
  end
end

.redaction_metadata(value) ⇒ Object



87
88
89
# File 'lib/bootprint/sanitizer.rb', line 87

def (value)
  { "present" => !value.nil?, "redacted" => true }
end

.replacementsObject



91
92
93
94
95
96
97
98
99
# File 'lib/bootprint/sanitizer.rb', line 91

def replacements
  app_root = File.expand_path(Dir.pwd)
  home = begin
    File.expand_path(Dir.home)
  rescue ArgumentError
    nil
  end
  [[app_root, "<APP_ROOT>"], [home, "<HOME>"]].reject { |path, _marker| path.nil? || path.empty? }
end

.safe_digest_name?(name) ⇒ Boolean

Returns:

  • (Boolean)


55
56
57
# File 'lib/bootprint/sanitizer.rb', line 55

def safe_digest_name?(name)
  name.match?(/(?:checksum|sha256|digest)\z/i)
end

.sanitize_value(value, privacy: :standard) ⇒ Object



69
70
71
72
73
74
75
# File 'lib/bootprint/sanitizer.rb', line 69

def sanitize_value(value, privacy: :standard)
  result = text(value)
  result = result.gsub(URL_CREDENTIAL_PATTERN, "\\1#{REDACTED}@")
  result = REDACTED if PRIVATE_KEY_PATTERN.match?(result) || JWT_PATTERN.match?(result) || high_entropy?(result)
  result = "<HOST>" if privacy.to_sym == :strict && hostname_like?(result)
  result
end

.secret_name?(name, patterns = Bootprint.configuration.redaction_patterns) ⇒ Boolean

Returns:

  • (Boolean)


49
50
51
52
53
# File 'lib/bootprint/sanitizer.rb', line 49

def secret_name?(name, patterns = Bootprint.configuration.redaction_patterns)
  return false if Bootprint.configuration.redaction_safe_list.any? { |entry| File.fnmatch?(entry, name, File::FNM_CASEFOLD) }

  patterns.any? { |pattern| name.upcase.include?(pattern.to_s.upcase) }
end

.sensitive_value?(value) ⇒ Boolean

Returns:

  • (Boolean)


63
64
65
66
67
# File 'lib/bootprint/sanitizer.rb', line 63

def sensitive_value?(value)
  string = value.to_s
  PRIVATE_KEY_PATTERN.match?(string) || JWT_PATTERN.match?(string) ||
    URL_CREDENTIAL_PATTERN.match?(string) || high_entropy?(string)
end

.text(value) ⇒ Object



13
14
15
16
17
18
19
20
21
# File 'lib/bootprint/sanitizer.rb', line 13

def text(value)
  sanitized = value.to_s.dup
  replacements.each do |path, marker|
    path_variants(path).each do |variant|
      sanitized.gsub!(variant, marker)
    end
  end
  sanitized.gsub(/[\u0000-\u0008\u000B\u000C\u000E-\u001F]/, "?")
end