An agent's input is not a single user query. Tool results, RAG context, and MCP (Model Context Protocol) tool descriptions all join the LLM prompt on every turn, so each join point needs its own validation gate.
Why the leakage surface grows in an agent
flowchart LR
U["User input"] --> A["Agent state"]
R[("RAG index")] --> A
T["Tool result"] --> A
M["MCP server"] --> T
A --> O["Model output"]
A --> W["External action"]
Beyond the user query, an agent injects tool results and RAG context into the LLM on every turn. These results and context enter the body sent to an external LLM. Masking only the user text at the entry point leaves new paths open. Every join point creates a potential leakage or injection vector.
- Member data contained in tool results
- PII embedded in the RAG index
- Tool descriptions advertised by an MCP server
InjecAgent (Findings of ACL 2024) evaluates how instructions in external tool results can alter agent actions. Its benchmark contains 1,054 cases and evaluates 30 agent configurations, distinguishing direct harm from private-data exfiltration. Results apply to the tested models, prompts, and tools.
These results do not establish current attack success rates or rank MCP, RAG, and tool paths by risk. This article proposes separate controls for incoming external content and the permissions used to execute actions.
Tool calling
Tool calls can transmit PII outside the system. The LLM can copy PII from the user text into tool arguments. An injection payload in a tool result can also steer the LLM's next decision. Both directions need blocking.
Argument scrubber plus allowlist
Immediately before calling a tool, run the argument dict through the anonymizer once more.
def scrub_tool_args(tool_input: dict) -> dict:
return {k: anon.anonymize(v) if isinstance(v, str) else v
for k, v in tool_input.items()}The code is a conceptual example that handles only top-level string arguments. Wrap actual tool calls with checks for nested objects and lists, and verify the integration point in the SDK version in use. Register permitted arguments per tool in an allowlist. Reject free-text arguments where possible and accept a structured dict or an enum instead. Free text creates bypass paths that schema enforcement alone cannot close.
Destructive actions (sending mail, payments, deletion, external transmission, exec) belong in a separate category that forces human approval through LangGraph's interrupt(). The user must be able to inspect the action and transmitted content. Prevent argument changes after approval.
Tool results are untrusted input
Tool results can contain injection payloads when they come from an external API or a free-form database field. InjecAgent evaluates attacks through such tool results. A tool returning content does not grant action permissions to instructions inside it.
The following controls are design proposals. Neither guarantees complete prevention of injection.
- Separate roles and provenance: deliver tool results under the
toolrole and preserve their source. State that instructions in external content do not constitute user approval. - Validate before execution: check the next tool call's target, arguments, and destination against policy. Use a guardrail model's verdict as a supporting signal, and enforce allowlists and permissions in execution code.
The RAG index
RAG handles the user query and the corpus at the same time, so PII enters through several points. Member data embedded in the index, user identifiers riding along in queries, and sensitive information mixed into answers all have to be handled. So does the possibility of the index itself being poisoned.
Removing PII at indexing time
Detect PII in the corpus before indexing and mask it, replace it with a hash, or swap in an anonymized reference ID. Start with LangChain's PebbloSafeLoader(anonymize_snippets=True). For Korean-language environments, add Presidio Korean NER plus a PatternRecognizer to the same pipeline. In some scenarios it is worth keeping the original in a separate secure store and indexing only a summarized or substituted version to shrink the exposed surface.
Embeddings contain information extracted from their source text. The original Vec2Text paper (EMNLP 2023) trains inversion models against two embedding models. It evaluates short-text reconstruction and name recovery from clinical notes, without establishing the same recovery rate for every model or input length.
Based on this evidence, the proposed design includes embeddings in access control and retention policies when their source is sensitive. Self-hosting a vector database does not itself prevent inversion. Do not treat fixed quantization or noise settings as a safety threshold; evaluate retrieval quality and reconstruction risk under the relevant attack model.
Permission filters and trust scores at retrieval time
Two more steps come before search results enter the LLM context. First, filter results through role-based access control (RBAC). Documents the user cannot see must never appear in the results. Second, run sanitization once more just before context injection to catch anything masking missed.
PoisonedRAG (USENIX Security 2025) evaluates attacks that insert malicious text into a knowledge store to corrupt answers to target questions. Results depend on the retriever, generator, and attacker access. Its evaluated defenses were insufficient, so increasing document count alone is not a defense guarantee.
This article proposes the following operational controls.
- Record content provenance, authorship, changes, and access permissions.
- Compare answer evidence and check whether apparently separate documents copy the same poisoned source.
- Review or inspect crawled content before admitting it to the index.
An authorized document can still be incorrect or poisoned. Evaluate access checks and content trust separately.
MCP
Tool descriptions and schemas delivered through MCP (Model Context Protocol) are data supplied by an external server. Systems that include them in model input should review tool registration and metadata changes as well as execution results. InjecAgent and PoisonedRAG do not evaluate the safety of a particular MCP client or version in this article.
The proposed integration controls are:
- Allowlist servers and tools, and review changes to descriptions and schemas.
- Compare the call target and actual arguments with user approval or execution policy.
- Record the presented tool definitions, selected calls, and approval results without secret values.
- Limit file, network, and credential access in the tool execution environment.
A description filter does not guarantee prevention of bypasses. Test the actual permission paths across client, host, and server; success rates from different experiments do not establish product rankings.
State design
In a LangGraph deployment, the shape of the State determines the leakage surface. If a checkpoint serializes a secret such as a reversible mapping, its database stores plaintext PII paired with fake values.
The following conceptual example separates persistable state from a secret store. It does not implement a checkpointer configuration.
class PublicState(TypedDict):
safe_query: str
safe_answer: str
class SessionSecrets(TypedDict):
pii_map: dict # keep outside the persisted graph stateType and field names do not determine persistence. Manage pii_map and deanonymizer_mapping outside serialized graph state, and verify that checkpoints, logs, and error reports exclude them. Check safe_query and safe_answer for masking omissions and retention requirements before storing them.
Encrypt the checkpointer itself with PostgresSaver plus EncryptedSerializer.from_pycryptodome_aes(KMS_KEY). Set the LANGGRAPH_STRICT_MSGPACK=true environment variable to block serialization bypasses. If you use the SQLite checkpointer, never put external input in a metadata filter key.
Mapping the layered defenses
The defenses at this layer map out as follows.
| Defense | Effective against | Limitation |
|---|---|---|
| Static tool description validation | Metadata poisoning | Natural-language bypass |
| Enforced parameter visibility | Hidden parameter attacks | Degrades UX |
| Additional inspection model | Suspicious inputs and outputs | False positives, omissions, and extra calls |
| Sandboxed execution | RCE | Restricts tool capability |
| Human approval (interrupt) | Destructive actions | Loss of automation |
| Role isolation (system/user/tool) | IPI | Many documented bypasses |
| Tool allowlist plus argument schema | Arbitrary invocation | Useless against abuse of legitimate tools |
| Provenance / trust score | RAG corruption | Requires supply chain trust |
| Output validator agent | Exfiltration | Latency |
| Intent verification | Task drift | Extra call cost |
This table is a proposed mapping of controls to threats, not a validated protection rate for their combination. An allowlist cannot determine every misuse of an allowed tool, so check arguments, destinations, and permissions as well.
Measure actual bypass paths and successful authorized tasks rather than choosing a fixed number of layers. Specify whether a failed check blocks the request, sends it to manual review, or permits a retry.
Summary
At the agent layer the leakage surface splits three ways: tool calls, RAG, and MCP. Tools are handled with an argument scrubber and an allowlist, and destructive actions require human approval.
Address RAG risks by removing PII at indexing time. Apply permission filters and trust scores at retrieval time. For MCP, review changes to servers and tool definitions and check actual call permissions. Manage reversible mappings outside persisted graph state and verify their exclusion from checkpoints and logs.
The next post covers the egress gateway that external LLM traffic passes through last, including LiteLLM, Cloudflare AI Gateway, and Portkey.