A Redaction Boundary is an architectural security pattern that recursively masks sensitive data before it crosses trust boundaries—such as model inputs, audit logs, or user interfaces—to prevent unauthorized exposure. Mask sensitive values before data crosses any boundary where the raw value should not appear — the model, the display, or the audit store.
Understanding Redaction Boundaries
Every agent that calls an LLM sends a prompt. That prompt contains context pulled from logs, databases, user inputs, and tool responses. Some of that context contains information that should never leave your environment: PII, credentials, internal topology, confidential business data.
The attack surface is not only the model provider. It is your own agent, faithfully passing sensitive information into the context window because nothing in the pipeline told it not to. These leaks are often accidental — an agent retrieves a log line with an email, a bearer token appears in headers, or a tool result includes a credit-card-like value. If the harness passes that payload unchanged, the leak has already happened. The model does not need malicious intent for this to be a problem. It only needs unfiltered context.
Implementing the Redaction Boundary Pattern
A redaction boundary recursively masks sensitive values before data crosses a boundary where the raw value should not appear. It is enforced by the harness, not by hoping the agent makes good choices about what to include.
flowchart TD A[Raw user input] --> R[redact] B[Retrieved context] --> R C[Tool result] --> R R --> D[Prompt-safe payload] R --> E[Audit-safe payload] R --> F[Display-safe payload]
In LangGraph, redaction runs at more than one boundary. A redact_input node scrubs the inbound message before the model sees it, and a redact_output node scrubs the model’s answer before it leaves the graph — the agent is bracketed on both sides:
def redact_input(state: AgentState) -> dict:
last = state["messages"][-1]
if isinstance(last, HumanMessage):
return {"messages": [last.model_copy(update={"content": redact(last.content)})]}
return {}
def redact_output(state: AgentState) -> dict:
last = state["messages"][-1]
return {"messages": [last.model_copy(update={"content": redact(last.content)})]}
builder.add_edge(START, "redact_input")
builder.add_edge("redact_input", "agent")
builder.add_edge("agent", "redact_output")
builder.add_edge("redact_output", END)
Limitations and Considerations for Redaction Boundaries
Consider the following factors when you implement this pattern:
- Where redaction runs. Incoming user request, retrieved context, tool results, model output before display or event emission, and audit payloads. The original payload should not be mutated — keep raw data only inside the trusted boundary and pass redacted copies outward.
- Data Loss Prevention (DLP). You can configure DLP policies to monitor and protect interactions in real-time, blocking sensitive data from being processed or returned in AI responses.
- Reversible tokenization. Sometimes the agent needs to reason about sensitive data without seeing the real values. The boundary can replace “John Doe” with a stable placeholder such as
PERSON_1before the model ever sees it.