40 lines
1.5 KiB
Python
40 lines
1.5 KiB
Python
"""Secret redaction for tool results (Phase F).
|
|
|
|
Tool results are fed back to the LLM (in-app agent loop) or returned to an
|
|
external MCP client, so they must never leak credentials. ``read_file`` and
|
|
``read_file_raw`` already redact through the shared file service, but other
|
|
tools (``diff_backup``, ``search_*``) return content that has not been through
|
|
the redactor. This module applies :func:`backend.secret_redactor.redact` to
|
|
every string in a tool payload, recursively.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from backend.secret_redactor import redact
|
|
|
|
_MAX_DEPTH = 12
|
|
|
|
|
|
def redact_payload(payload: Any, _depth: int = 0) -> Any:
|
|
"""Return a copy of *payload* with every string secret-redacted.
|
|
|
|
Walks dicts, lists and tuples; scalars are returned unchanged. Strings are
|
|
run through :func:`backend.secret_redactor.redact`. A recursion cap avoids
|
|
pathological/cyclic structures (tool results are JSON-serialisable, so
|
|
cycles should not occur, but the guard keeps this safe).
|
|
"""
|
|
if _depth > _MAX_DEPTH:
|
|
return payload
|
|
if isinstance(payload, str):
|
|
redacted, count = redact(payload)
|
|
return redacted if count else payload
|
|
if isinstance(payload, dict):
|
|
return {key: redact_payload(value, _depth + 1) for key, value in payload.items()}
|
|
if isinstance(payload, list):
|
|
return [redact_payload(item, _depth + 1) for item in payload]
|
|
if isinstance(payload, tuple):
|
|
return tuple(redact_payload(item, _depth + 1) for item in payload)
|
|
return payload
|