ruby 47 lines · 7 steps

Recursively scrubbing secrets from JSON

A scrubber walks any nested payload, redacting sensitive keys and partially masking emails before serializing.

Explained by highlit
1require 'json'
2require 'set'
3 
4class SensitiveScrubber
5 DEFAULT_KEYS = %w[
6 password password_confirmation token access_token refresh_token
7 secret api_key ssn credit_card cvv authorization
8 ].freeze
9 
10 EMAIL_PATTERN = /\A[^@\s]+@[^@\s]+\z/
11 
12 def initialize(keys: DEFAULT_KEYS, mask: '[FILTERED]')
13 @keys = Set.new(keys.map(&:to_s).map(&:downcase))
14 @mask = mask
15 end
16 
17 def to_json(payload, **opts)
18 JSON.generate(scrub(payload), **opts)
19 end
20 
21 private
22 
23 def scrub(value)
24 case value
25 when Hash
26 value.each_with_object({}) do |(key, val), acc|
27 acc[key] = sensitive?(key) ? @mask : scrub(val)
28 end
29 when Array
30 value.map { |item| scrub(item) }
31 when String
32 value.match?(EMAIL_PATTERN) ? mask_email(value) : value
33 else
34 value
35 end
36 end
37 
38 def sensitive?(key)
39 @keys.include?(key.to_s.downcase)
40 end
41 
42 def mask_email(email)
43 local, domain = email.split('@', 2)
44 visible = local[0, 2]
45 "#{visible}#{'*' * [local.length - 2, 1].max}@#{domain}"
46 end
47end
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A recursive case-by-type walk handles arbitrarily nested hashes and arrays uniformly.
  2. 2Normalizing keys into a downcased Set makes sensitivity checks fast and case-insensitive.
  3. 3Partial masking preserves enough of a value to stay recognizable without exposing it fully.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Recursively scrubbing secrets from JSON — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code