Module: Scrubber

Defined in:
lib/scrubber_rb.rb,
lib/scrubber/match.rb,
lib/scrubber/version.rb,
lib/scrubber/instance.rb,
lib/scrubber/llm_guard.rb,
lib/scrubber/middleware.rb,
lib/scrubber/log_formatter.rb,
sig/scrubber_rb.rbs

Overview

Fast PII and secret redaction, with the scanning done in Rust.

Scrubber.scrub("contact nik@example.com")  # => "contact [EMAIL]"

The module-level methods are the convenient path and memoize one compiled engine per distinct configuration. For hot loops, build a Instance yourself with Scrubber.new and keep it around.

Defined Under Namespace

Classes: ConfigurationError, Error, Instance, InternalError, LLMGuard, LogFormatter, Match, Middleware, Native, UnknownDetectorError, UnsupportedPatternError

Constant Summary collapse

DEFAULTS =

Detector keys, sourced from the Rust registry at load time.

Returns:

  • (Array[Symbol])
Native.default_detectors.map(&:to_sym).freeze
INDIA =

Opt-in India pack, for DPDP Act workloads:

Scrubber.new(detectors: Scrubber::DEFAULTS + Scrubber::INDIA)

Returns:

  • (Array[Symbol])
Native.india_detectors.map(&:to_sym).freeze
ALL =

Every detector this build knows about.

Returns:

  • (Array[Symbol])
Native.all_detectors.map(&:to_sym).freeze
VERSION =

Returns:

  • (String)
"0.1.1"

Class Method Summary collapse

Class Method Details

.detect(text, **options) ⇒ Array[Match]

Locate matches without replacing them. Returns Match structs.

Parameters:

  • _ToStr (String)
  • (Object)

Returns:



60
61
62
# File 'lib/scrubber_rb.rb', line 60

def detect(text, **options)
  engine(**options).detect(text)
end

.engine(detectors: DEFAULTS, custom: {}, replacement: :label, hash_salt: nil) ⇒ Instance

The memoized Instance for a set of options. Exposed because asking for it explicitly beats accidentally rebuilding one per request.

Parameters:

  • (Object)

Returns:



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/scrubber_rb.rb', line 77

def engine(detectors: DEFAULTS, custom: {}, replacement: :label, hash_salt: nil)
  key = Instance.cache_key(
    detectors: detectors, custom: custom,
    replacement: replacement, hash_salt: hash_salt
  )
  # Double-checked: reads are lock-free on the happy path, and building the
  # same engine twice under a race is harmless (they are identical and
  # immutable), so the mutex only prevents wasted work.
  cache[key] || cache_mutex.synchronize do
    cache[key] ||= new(
      detectors: detectors, custom: custom,
      replacement: replacement, hash_salt: hash_salt
    )
  end
end

.match?(text, **options) ⇒ Boolean

True if anything would be redacted.

Parameters:

  • _ToStr (String)
  • (Object)

Returns:

  • (Boolean)


65
66
67
# File 'lib/scrubber_rb.rb', line 65

def match?(text, **options)
  engine(**options).match?(text)
end

.new(detectors: DEFAULTS, custom: {}, replacement: :label, hash_salt: nil) ⇒ Object

Build a reusable, frozen scrubber. See Instance.



42
43
44
45
46
47
# File 'lib/scrubber_rb.rb', line 42

def new(detectors: DEFAULTS, custom: {}, replacement: :label, hash_salt: nil)
  Instance.new(
    detectors: detectors, custom: custom,
    replacement: replacement, hash_salt: hash_salt
  )
end

.reset!nil

Drop memoized engines. Only useful in tests.

Returns:

  • (nil)


94
95
96
97
# File 'lib/scrubber_rb.rb', line 94

def reset!
  cache_mutex.synchronize { cache.clear }
  nil
end

.scrub(text, **options) ⇒ String

Redact text using a memoized engine for these options.

Parameters:

  • _ToStr (String)
  • (Object)

Returns:

  • (String)


50
51
52
# File 'lib/scrubber_rb.rb', line 50

def scrub(text, **options)
  engine(**options).scrub(text)
end

.scrub!(text, **options) ⇒ String

Redact text in place.

Parameters:

  • (String)
  • (Object)

Returns:

  • (String)


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

def scrub!(text, **options)
  engine(**options).scrub!(text)
end

.scrub_file(input_path, output_path, **options) ⇒ Integer

Stream a file through the engine. Returns bytes written.

Parameters:

  • (String, ::Pathname)
  • (String, ::Pathname)
  • (Object)

Returns:

  • (Integer)


70
71
72
73
# File 'lib/scrubber_rb.rb', line 70

def scrub_file(input_path, output_path, **options)
  chunk_size = options.delete(:chunk_size) || Instance::CHUNK_SIZE
  engine(**options).scrub_file(input_path, output_path, chunk_size: chunk_size)
end