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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1A recursive case-by-type walk handles arbitrarily nested hashes and arrays uniformly.
- 2Normalizing keys into a downcased Set makes sensitivity checks fast and case-insensitive.
- 3Partial masking preserves enough of a value to stay recognizable without exposing it fully.
Related explainers
ruby
class ToastBroadcaster include ActionView::RecordIdentifier def self.broadcast_to(user, message:, type: :notice)
How Turbo Stream toasts broadcast in Rails
turbo-streams
service-object
real-time
Intermediate
6 steps
ruby
class TemplateInterpolator PLACEHOLDER = /\{\{\s*([\w.]+)\s*\}\}/ def initialize(strict: false)
Interpolating templates with dotted keys in Ruby
regex
string-interpolation
hash-traversal
Intermediate
6 steps
java
import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map;
Building a trie for autocomplete in Java
trie
prefix-tree
recursion
Intermediate
8 steps
ruby
class KeyTransformer def self.camelize(data) new.camelize(data) end
Recursively camelizing nested Ruby data
recursion
data-transformation
pattern-matching
Intermediate
7 steps
ruby
module Paginatable extend ActiveSupport::Concern private
A reusable pagination concern in Rails
pagination
concerns
http-headers
Intermediate
8 steps
typescript
type CsvColumn<T> = { header: string; value: (row: T) => string | number | boolean | null | undefined; };
Building a type-safe CSV writer in TypeScript
generics
serialization
escaping
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/recursively-scrubbing-secrets-from-json-explained-ruby-005c/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.