Class: Terret::Redactor
- Inherits:
-
Hames::Service
- Object
- Hames::Service
- Terret::Redactor
- Defined in:
- lib/terret/redactor.rb
Overview
ctx — §13's "credentials never enter the session log", in the two layers docs/exec.md §6 describes.
The FIRST is a tools/post_execute listener: the waterfall every tool result already passes through, so a tool that happened to hand back a credential is rewritten before the loop appends it — and before any other post_execute listener, or any in-memory consumer of the Result, reads it.
The SECOND is a Sessions scrubber, and it is the one that makes the promise true rather than likely: a secret can reach the log through a path that never touches a tool result at all — a user's own message, an injected steer, a plugin's durable event — and register_scrubber runs over every String of every append regardless of type. Because that runs INSIDE normalize_payload, the stored bytes and every projection derived from them agree by construction, so the log invariant needs nothing special here.
What this is not: comprehensive. Patterns are regexp sources on this row's config, so it catches shapes a deployment named and nothing else. Catching a secret by its exact bytes rather than by a named shape is ctx's job (plan §6.9, Terret::Credentials): it registers every value it resolves as its own append-boundary scrubber, running alongside this one.
Constant Summary collapse
- DEFAULT_REPLACEMENT =
"[REDACTED]"
Instance Method Summary collapse
-
#reconfigure(config) ⇒ Object
Patterns are compiled, so they are the one thing a hot swap has to re-derive; the replacement token is read per call and is already live.
-
#redact(text) ⇒ Object
The redaction itself, and the method the Sessions scrubber calls.
-
#replacement ⇒ Object
Public because a reader downstream has to recognize this mechanism's own mark: the loop refuses to replay a resumed tool call whose stored arguments carry it (Loop#redaction_token).
- #start(ctx) ⇒ Object
Instance Method Details
#reconfigure(config) ⇒ Object
Patterns are compiled, so they are the one thing a hot swap has to re-derive; the replacement token is read per call and is already live. An uncompilable pattern raises here, and the loader rolls the row back.
48 49 50 |
# File 'lib/terret/redactor.rb', line 48 def reconfigure(config) @patterns = compile(config[:patterns]) end |
#redact(text) ⇒ Object
The redaction itself, and the method the Sessions scrubber calls. A non-String is handed straight back: this is reached from the tools pipeline too, where a result's content may be anything a handler returned.
56 57 58 59 60 61 62 63 64 |
# File 'lib/terret/redactor.rb', line 56 def redact(text) return text unless text.is_a?(String) # gsub's BLOCK form, not its string form, which would read `\0`/`\1` in # a deployment's replacement token as backreferences — a token # containing `\0` would paste the matched secret back in and leave a # redactor that silently un-redacts. @patterns.reduce(text) { |acc, pattern| acc.gsub(pattern) { replacement } } end |
#replacement ⇒ Object
Public because a reader downstream has to recognize this mechanism's own mark: the loop refuses to replay a resumed tool call whose stored arguments carry it (Loop#redaction_token).
69 |
# File 'lib/terret/redactor.rb', line 69 def replacement = config[:replacement] || DEFAULT_REPLACEMENT |
#start(ctx) ⇒ Object
35 36 37 38 39 40 41 42 43 |
# File 'lib/terret/redactor.rb', line 35 def start(ctx) @patterns = compile(config[:patterns]) ctx.on("tools/post_execute") { |result, next_| next_.(redact_result(result)) } # Registered through the seam rather than as a second listener: this # runs at the append boundary itself (Sessions#register_scrubber), which # is the whole reason the backstop is trustworthy. ctx[:sessions].register_scrubber(method(:redact)) end |