Module: Pgbus::MCP::Redactor
- Defined in:
- lib/pgbus/mcp/redactor.rb
Overview
Strips message bodies / headers / job arguments from tool responses before they leave the process. Job payloads can carry PII or secrets, so the diagnostic tools return metadata (ids, counts, ages, read_ct, vt, queue names, job_class) by default and only include the raw payload when an explicit, separately-gated flag is set.
deep_redact is applied centrally at the response boundary (BaseTool.json_response), so redaction is fail-safe: a tool author cannot leak a payload by forgetting to redact — they have to opt in.
See issue #180: “No sensitive payload leakage — diagnostic tools should return metadata … and redact or omit message payloads unless an explicit, separately-gated flag is set.”
Constant Summary collapse
- PAYLOAD_KEYS =
Keys whose values are message bodies / headers and must never be returned unless payloads are explicitly allowed.
%i[message headers payload arguments].freeze
- REDACTED =
Replacement marker so the agent can see a field exists but was hidden, rather than the field silently vanishing.
"[redacted]"
Class Method Summary collapse
-
.deep_redact(value, include_payloads: false) ⇒ Object
Recursively redact payload-bearing keys anywhere in a nested structure of hashes and arrays.
-
.leaf?(value) ⇒ Boolean
A leaf is anything that isn’t a structure we recurse into — i.e.
-
.payload_key?(key) ⇒ Boolean
True if
keynames a payload-bearing field.
Class Method Details
.deep_redact(value, include_payloads: false) ⇒ Object
Recursively redact payload-bearing keys anywhere in a nested structure of hashes and arrays. This is the fail-safe applied at the response boundary (BaseTool.json_response): a tool that returns a payload key —at any depth, even one its author forgot about — has it stripped unless payloads are explicitly allowed for the call.
A payload key is redacted only when its value is a leaf (the serialized message body / headers string). When the value is a Hash or Array the key is an envelope (e.g. a {…} detail wrapper, not a raw body), so we descend into it instead of blanking the whole subtree.
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/pgbus/mcp/redactor.rb', line 45 def deep_redact(value, include_payloads: false) return value if include_payloads case value when Hash value.each_with_object({}) do |(key, nested), result| result[key] = if payload_key?(key) && leaf?(nested) && !nested.nil? REDACTED else deep_redact(nested, include_payloads: include_payloads) end end when Array value.map { |element| deep_redact(element, include_payloads: include_payloads) } else value end end |
.leaf?(value) ⇒ Boolean
A leaf is anything that isn’t a structure we recurse into — i.e. the actual serialized payload value rather than an envelope hash/array.
67 68 69 |
# File 'lib/pgbus/mcp/redactor.rb', line 67 def leaf?(value) !value.is_a?(Hash) && !value.is_a?(Array) end |
.payload_key?(key) ⇒ Boolean
True if key names a payload-bearing field. Tolerates string or symbol keys and never raises on a non-symbolizable key.
31 32 33 |
# File 'lib/pgbus/mcp/redactor.rb', line 31 def payload_key?(key) PAYLOAD_KEYS.include?(key.respond_to?(:to_sym) ? key.to_sym : key) end |