Class: PromptSanitizer::Sanitizer
- Inherits:
-
Object
- Object
- PromptSanitizer::Sanitizer
- Defined in:
- lib/prompt_sanitizer/sanitizer.rb
Overview
Core sanitization class — the main public interface.
Ties together RegexEngine, SecretsEngine, optional NEREngine, SyntheticEngine, Vault, and AuditLog.
Usage (one-shot):
s = PromptSanitizer::Sanitizer.new(mode: :fast)
result = s.sanitize("Email me at [email protected]")
result.text # => "Email me at [EMAIL_1]"
result.any? # => true
Usage (multi-turn with session):
sess = s.session(session_id: "user-42")
clean = sess.anonymize(user_prompt)
raw_response = call_llm(clean)
final = sess.deanonymize(raw_response)
Instance Attribute Summary collapse
- #audit ⇒ Audit::Base? readonly
-
#mode ⇒ Symbol
readonly
Active detection mode.
Instance Method Summary collapse
- #_run(text, vault, session_id: nil) ⇒ Object private
-
#add_pattern(entity_type, pattern, confidence: 0.85) ⇒ Object
Register a custom regex pattern.
-
#initialize(mode: :fast, locale: "en", entities: nil, on_detect: :redact, audit_log: nil, ner_backend: :informers, ner_model: "distilbert") ⇒ Sanitizer
constructor
A new instance of Sanitizer.
-
#restore(text, vault:) ⇒ String
Convenience: restore vault tokens in
textback to originals. -
#sanitize(text, session_id: nil) ⇒ SanitizeResult
Sanitize
textin a single-use vault. -
#sanitize_batch(texts, session_id: nil) ⇒ Array<SanitizeResult>
Sanitize a list of texts, each with its own vault.
-
#session(session_id: nil) {|sess| ... } ⇒ Session, Object
Create a Session for multi-turn anonymize/deanonymize workflows.
Constructor Details
#initialize(mode: :fast, locale: "en", entities: nil, on_detect: :redact, audit_log: nil, ner_backend: :informers, ner_model: "distilbert") ⇒ Sanitizer
Returns a new instance of Sanitizer.
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/prompt_sanitizer/sanitizer.rb', line 30 def initialize( mode: :fast, locale: "en", entities: nil, on_detect: :redact, audit_log: nil, ner_backend: :informers, ner_model: "distilbert" ) unless Mode.valid?(mode) raise ConfigurationError, "Invalid mode: #{mode.inspect}. Use :fast, :smart, or :full" end @mode = mode @locale = locale @on_detect = on_detect.to_sym @allowed = entities ? Array(entities).map(&:to_sym).to_set : nil @regex = Engines::RegexEngine.new @secrets = Engines::SecretsEngine.new @ner = mode == :fast ? nil : Engines::NEREngine.new(backend: ner_backend, model: ner_model) @synthetic = SyntheticEngine.new(locale: locale) @audit = if audit_log && audit_log != :none audit_log elsif mode == :full Audit::MemoryAuditLog.new end end |
Instance Attribute Details
#audit ⇒ Audit::Base? (readonly)
65 66 67 |
# File 'lib/prompt_sanitizer/sanitizer.rb', line 65 def audit @audit end |
#mode ⇒ Symbol (readonly)
Returns active detection mode.
62 63 64 |
# File 'lib/prompt_sanitizer/sanitizer.rb', line 62 def mode @mode end |
Instance Method Details
#_run(text, vault, session_id: nil) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/prompt_sanitizer/sanitizer.rb', line 126 def _run(text, vault, session_id: nil) # rubocop:disable Naming/MethodParameterName return SanitizeResult.new(text: text, original: text, entities: []) if text.nil? || text.empty? # 1. Collect detections from all active layers raw = [] raw.concat(@regex.detect(text)) raw.concat(@secrets.detect(text)) raw.concat(@ner.detect(text)) if @ner # 2. Filter to allowed entity types raw = raw.select { |e| @allowed.include?(e.entity_type) } if @allowed # 3. Deduplicate overlapping spans entities = _deduplicate(raw) # 4. Handle on_detect modes if @on_detect == :block && entities.any? raise PIIDetectedError, entities end if @on_detect == :warn return SanitizeResult.new(text: text, original: text, entities: entities) end # on_detect == :redact (default) # 5. Assign replacements — reuse vault entry when same PII seen before entities.each do |entity| existing = vault.replacement_for(entity.original) if existing entity.replacement = existing else replacement = if @mode == :full @synthetic.generate(entity.entity_type, entity.original) else @synthetic.placeholder(entity.entity_type) end entity.replacement = vault.add(entity.original, replacement) end end # 6. Reconstruct text right-to-left to preserve byte offsets chars = text.dup entities.reverse_each do |entity| chars[entity.start_pos...entity.end_pos] = entity.replacement.to_s end # 7. Record audit events if @audit entities.each do |entity| method = entity.replacement.to_s.start_with?("[") ? :placeholder : :synthetic @audit.record( Audit::AuditEvent.new( timestamp: Audit.now_iso, entity_type: entity.entity_type, confidence: entity.confidence, layer: entity.layer, redaction_method: method, text_hash: Audit.hash_value(entity.original), session_id: session_id ) ) end end SanitizeResult.new(text: chars, original: text, entities: entities) end |
#add_pattern(entity_type, pattern, confidence: 0.85) ⇒ Object
Register a custom regex pattern.
109 110 111 |
# File 'lib/prompt_sanitizer/sanitizer.rb', line 109 def add_pattern(entity_type, pattern, confidence: 0.85) @regex.add_pattern(entity_type.to_sym, pattern, confidence: confidence) end |
#restore(text, vault:) ⇒ String
Convenience: restore vault tokens in text back to originals.
Useful when you hold a vault externally.
119 120 121 |
# File 'lib/prompt_sanitizer/sanitizer.rb', line 119 def restore(text, vault:) vault.restore(text) end |
#sanitize(text, session_id: nil) ⇒ SanitizeResult
Sanitize text in a single-use vault. Returns a SanitizeResult.
74 75 76 |
# File 'lib/prompt_sanitizer/sanitizer.rb', line 74 def sanitize(text, session_id: nil) _run(text, Vault.new, session_id: session_id) end |
#sanitize_batch(texts, session_id: nil) ⇒ Array<SanitizeResult>
Sanitize a list of texts, each with its own vault.
83 84 85 |
# File 'lib/prompt_sanitizer/sanitizer.rb', line 83 def sanitize_batch(texts, session_id: nil) texts.map { |t| sanitize(t, session_id: session_id) } end |
#session(session_id: nil) {|sess| ... } ⇒ Session, Object
Create a Session for multi-turn anonymize/deanonymize workflows. The session maintains a shared vault so the same PII always maps to the same token, and LLM responses can be deanonymized.
Without a block, returns a Session you manage yourself. With a block, yields the Session and clears the vault after the block.
97 98 99 100 101 102 |
# File 'lib/prompt_sanitizer/sanitizer.rb', line 97 def session(session_id: nil, &block) sess = Session.new(self, session_id: session_id) return sess unless block_given? sess.use(&block) end |