Module: DataRedactor::Integrations::Claude

Defined in:
lib/data_redactor/integrations/claude.rb

Overview

Adapter for Anthropic Claude (Messages API) payloads. Scrubs PII and secrets from a request’s ‘messages` (and top-level `system:` prompt) before they leave the process, and from a response’s ‘content` blocks before they’re logged or stored.

Operates on plain Ruby Hashes/Arrays with either String or Symbol keys, so it works with the ‘anthropic` gem, a raw HTTP client, or parsed JSON —no runtime dependency on any SDK. Inputs are never mutated; a deep copy is returned.

Examples:

Scrub a request before sending

require "data_redactor/integrations/claude"

messages = [{ role: "user", content: "my email is alice@example.com" }]
safe = DataRedactor::Integrations::Claude.redact_messages(messages)
client.messages.create(model: "claude-opus-4-8", max_tokens: 1024,
                       messages: safe)

Scrub a response before logging

resp = client.messages.create(...)
logger.info DataRedactor::Integrations::Claude.redact_response(resp)

Class Method Summary collapse

Class Method Details

.redact_messages(messages, only: nil, except: nil, placeholder: DataRedactor::PLACEHOLDER_DEFAULT) ⇒ Array<Hash>, Hash

Redact a Claude ‘messages` array (and an optional top-level `system:` String) before sending the request. Returns a deep copy; the input is not mutated.

Each message’s ‘content` may be a String or an array of content blocks (`{ type: “text”, text: “…” }`); only the `text` field of `text` blocks is redacted. Non-text blocks (e.g. `image`) pass through untouched.

Examples:

Claude.redact_messages([{ role: "user", content: "ssn 123-45-6789" }])
#=> [{ role: "user", content: "ssn [REDACTED]" }]

Parameters:

  • messages (Array<Hash>, Hash)

    either a bare array of ‘{ role:, content: }` hashes, or a request Hash containing a `messages` key and an optional `system` key. Keys may be String or Symbol.

  • only (defaults to: nil)

    forwarded to DataRedactor.redact

  • except (defaults to: nil)

    forwarded to DataRedactor.redact

  • placeholder (defaults to: DataRedactor::PLACEHOLDER_DEFAULT)

    forwarded to DataRedactor.redact

Returns:

  • (Array<Hash>, Hash)

    a deep copy of the input with text leaves redacted; an Array if an Array was given, a Hash if a Hash was given.



51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/data_redactor/integrations/claude.rb', line 51

def redact_messages(messages, only: nil, except: nil, placeholder: DataRedactor::PLACEHOLDER_DEFAULT)
  redact = ->(s) { DataRedactor.redact(s, only: only, except: except, placeholder: placeholder) }

  if messages.is_a?(Hash)
    out = LLMSupport.deep_copy(messages)
    sys = LLMSupport.fetch(out, :system)
    LLMSupport.put(out, :system, redact.call(sys)) if sys.is_a?(String)
    list = LLMSupport.fetch(out, :messages)
    LLMSupport.put(out, :messages, redact_message_list(list, redact)) if list.is_a?(Array)
    out
  else
    redact_message_list(LLMSupport.deep_copy(messages), redact)
  end
end

.redact_response(response, only: nil, except: nil, placeholder: DataRedactor::PLACEHOLDER_DEFAULT) ⇒ Hash

Redact a Claude Messages API response before logging or storing it. Returns a deep copy; the input is not mutated.

Walks the response’s ‘content` array and redacts the `text` field of each `text` block, leaving the rest of the response (id, role, usage, non-text blocks) intact.

Examples:

Claude.redact_response(
  "content" => [{ "type" => "text", "text" => "card 4111111111111111" }]
)
#=> {"content"=>[{"type"=>"text", "text"=>"card [REDACTED]"}]}

Parameters:

  • response (Hash)

    a Claude response Hash with a ‘content` array of blocks. Keys may be String or Symbol.

  • only (defaults to: nil)

    forwarded to DataRedactor.redact

  • except (defaults to: nil)

    forwarded to DataRedactor.redact

  • placeholder (defaults to: DataRedactor::PLACEHOLDER_DEFAULT)

    forwarded to DataRedactor.redact

Returns:

  • (Hash)

    a deep copy of the response with text blocks redacted.



84
85
86
87
88
89
90
91
92
# File 'lib/data_redactor/integrations/claude.rb', line 84

def redact_response(response, only: nil, except: nil, placeholder: DataRedactor::PLACEHOLDER_DEFAULT)
  redact = ->(s) { DataRedactor.redact(s, only: only, except: except, placeholder: placeholder) }
  out = LLMSupport.deep_copy(response)
  return out unless out.is_a?(Hash)

  content = LLMSupport.fetch(out, :content)
  LLMSupport.put(out, :content, LLMSupport.redact_text_blocks(content, redact)) if content.is_a?(Array)
  out
end