What are the best tools to prevent PII leakage from AI agents?
An agent leaks differently from a chatbot, and tool selection follows from that difference rather than from a feature comparison.
A chatbot has two boundaries: what the user sends, and what the model returns. Put a check on each and you've covered the surface. An agent has at least five, because between the user's message and the answer it calls tools, reads their results back into context, writes to memory, and sometimes hands the whole thing to another agent. Each of those is a place where a personal record moves somewhere it wasn't supposed to go.
So the tool question resolves to a coverage question. Most products in this category cover one or two of the five points well. The one that decides most architectures is the third: a tool call's arguments, which is where an agent quietly puts a customer's data into a request to a third-party API.
Where does an agent leak PII?
1. User input. The paste in the chat box. Well covered by every product in the category, and the least interesting of the five.
2. Tool call arguments. The model decided to call create_ticket, and it filled the description field with the customer's full message, including the phone number. The tool is your code, the argument is a string the model wrote, and the destination is a vendor. Nothing about this crosses your HTTP ingress, so nothing at the perimeter sees it.
3. Tool call results. The tool returned a customer record. It's about to become model context, which means it's about to be repeated in a completion, written to a trace, and possibly embedded. The result is data your application fetched legitimately; the leak is where it goes next.
4. Agent memory and state. Summaries, scratchpads, and vector stores that persist across turns and, in the bad case, across users or tenants. Data written on Monday by one user surfaces on Thursday for another.
5. Inter-agent handoffs. A planner passes context to a worker agent, or your agent calls a remote agent over a protocol. The receiving agent gets whatever was in the handoff payload, which is usually more than it needs.
Which tools cover which points?
Grouped by shape rather than by brand, because the shape predicts the coverage.
| Tool shape | User input | Tool arguments | Tool results | Memory and state | Agent handoffs |
|---|---|---|---|---|---|
| In-process SDK check | Yes | Yes | Yes | Yes, where you call it | Yes, where you call it |
| AI gateway or model proxy | Yes | Only if tool calls are proxied | No | No | No |
| Edge WAF or reverse proxy | Yes | No | No | No | No |
| Cloud DLP API | Yes, if you call it | Yes, if you call it | Yes, if you call it | Yes, if you call it | Yes, if you call it |
| Self-hosted scanner service | Yes, if you call it | Yes, if you call it | Yes, if you call it | Yes, if you call it | Yes, if you call it |
| Provider-side safety filter | No | No | No | No | No |
Post-hoc log scanning belongs in none of those rows. It finds the data after it left, which makes it evidence rather than prevention.
The "if you call it" rows deserve reading carefully. A cloud DLP API can technically inspect any of the five points, because it's a function you invoke with a string. The catch is what invoking it means: a network call per check, in the agent loop, sending the content to a third party. An agent making eight tool calls in one run now makes eight extra round trips carrying customer data, to prevent customer data from traveling. That trade is why the row is honest about capability and why teams rarely place it on all five.
Why are tool call arguments the uncovered case?
Because the argument is written by the model, not by the user, and every control that looks at user input has already run by the time it exists.
Here's the shape. A support agent has a create_crm_note tool. The user's message was clean, so the inbound check passed. The model then summarizes the conversation into the note body, and the summary includes the phone number the user gave two turns ago and the card's last four digits that the user mentioned in passing. The argument leaves for a CRM vendor. Your inbound check never saw it, because it wasn't inbound.
Tool arguments are also where an injection turns into an exfiltration. Untrusted content in context can steer the model into putting data it has access to into an argument of a tool that sends things outward: a webhook, an email tool, a search query against an external service. The model isn't broken. It's doing what the context told it, and the argument is the exit.
The check goes inside the handler, before the side effect:
import { launchArcjet, localDetectSensitiveInfo } from "@arcjet/guard";import { rampart } from "@arcjet/sensitive-info-rampart";
// SSN is an extended type, so the on-device model backend is required.const arcjet = launchArcjet({ key: process.env.ARCJET_KEY!, sensitiveInfoBackend: rampart(),});
const outbound = localDetectSensitiveInfo({ deny: ["CREDIT_CARD_NUMBER", "SSN", "EMAIL", "PHONE_NUMBER"],});
export async function createCrmNote(args: { accountId: string; body: string }) { const decision = await arcjet.guard({ label: "tools.create-crm-note", actor: session.userId, correlationId: runId, // The model wrote `body`. Check it before it reaches the vendor. rules: [outbound(args.body)], });
if (decision.conclusion === "DENY") { throw new Error("Note contains restricted data and was not created."); }
return crm.notes.create(args);}One guard() per specific tool, with a hardcoded label. Building the label from the tool name inside a generic dispatcher looks tidy and makes the decisions unreadable later, because every tool ends up in one bucket and you can't grep for the call site.
Note what the deny list is doing here that an inbound list wouldn't. Outbound to a CRM, an email address is fine and a card number isn't. Outbound to a public webhook, neither is. The right list depends on the destination, which is context only the handler has.
What about tool results?
A tool result is data your application was entitled to fetch. The question is whether it should now be repeated to a model, rendered to this user, written to a trace, and embedded in a vector store where it will be retrieved for someone else.
Check the result before it re-enters context, not only before it's rendered. The rendering check catches what the user sees. It misses the copy in the trace, the copy in the log, and the copy in the embedding, and those are retained far longer than the conversation.
For the fuller treatment of where data accumulates on this path, see how to prevent data exfiltration through AI agents.
There's also a second job on tool results that isn't a PII job at all. A result containing text from an untrusted source re-enters the prompt as instructions, which is indirect prompt injection. Run both checks on the same string; they answer different questions. For more information, see AI agent runtime security.
What about agent memory and cross-tenant state?
Memory is the point where a single-turn leak becomes a persistent one.
Three failures show up repeatedly. A conversation summary keeps a value that was redacted in the original turn, because summarization ran on the raw text. A vector store is written without a tenant key, so a retrieval for one user returns a chunk written during another user's session. And a long-lived scratchpad accumulates identifiers across a run and gets logged wholesale on error.
The controls are ordinary once the failure is named: run detection on what you're about to persist rather than only on what you display, key every memory write to a tenant and filter every read by it, and treat a summary as a new piece of content that needs its own check rather than as a derivative that inherits the original's clearance.
Cross-tenant retrieval is a large enough problem to have its own treatment. See how to prevent LLMs surfacing confidential employee or customer data.
What about handoffs between agents?
A handoff is a tool call where the tool happens to be another agent, and it inherits every problem in the tool-argument section plus one more: the receiving agent usually gets the whole context because that was easier than deciding what it needed.
Treat the handoff payload as an outbound boundary. Scan it, and scope it: pass the task and the identifiers required to do the task, rather than the transcript. A worker agent that needs to look up an order needs the order number, not the conversation in which the customer mentioned their address.
If the receiving agent is a remote one, run by someone else, then it's a third party and the analysis is a vendor analysis rather than an architecture one.
How do you choose a tool?
Score candidates on the five points, in this order.
- Can it run on a path with no HTTP request? Tool handlers, queue workers, and MCP servers over stdio are where agent risk concentrates. A control that needs a request object covers point one and nothing else.
- Does checking cost a network round trip carrying the content? In an agent loop, that multiplies by the number of tool calls, and it means the control is also a data recipient.
- Can the deny list vary by call site? The right list outbound to a CRM differs from the right list outbound to a public webhook. A single global policy can't express that.
- Does a decision carry a correlation identifier? Agent incidents are sequences. Without a run identifier on every decision, you have a pile of unrelated events and no way to see that a bulk read preceded an outbound call.
- What does it do when it fails? In a loop, a control that fails open silently disappears for the whole run.
Arcjet's answer to the first two is the same mechanism: guard() takes a string and returns a decision, and sensitive-information detection runs in a WebAssembly module inside your process, so the content isn't in the payload that leaves. What reaches the Cloud API is the decision record, including the matched entity types and a SHA-256 hash of the scanned text.
That's the coverage argument, and it isn't a completeness claim. Detection has false negatives that move as data shapes change, so it belongs alongside controls that don't depend on recognizing content: least-privilege tool design so an agent can't reach data it never needed, authorization at the point of access, and budgets so a hijacked loop can't run indefinitely. For the runtime control set as a whole, see AI agent runtime security.
Frequently asked questions
What are the best tools to prevent PII leakage from AI agents?
Score them on five leak points: user input, tool call arguments, tool call results, agent memory and state, and inter-agent handoffs. An in-process SDK check covers all five because it takes a string. Gateways and edge proxies cover the first and part of the second. Cloud DLP can cover all five at the cost of a network round trip carrying the content, per check, inside the loop.
Why are tool call arguments the hardest part?
Because the model writes them after every inbound check has already run. A clean user message can still produce a tool argument containing a phone number the model carried forward from two turns ago. The argument never crosses your HTTP ingress, so nothing at the perimeter sees it. Check it inside the handler, before the side effect.
Does a gateway cover an agent's tool calls?
Only the ones routed through it. A tool handler that reads a customer record and returns it to the model does that inside one process, with no request for a proxy to inspect. The same applies to MCP servers over stdio, queue workers, and scheduled jobs.
How do you stop agent memory leaking across tenants?
Key every write to a tenant and filter every read by it, including the vector store, the prompt cache, and conversation summaries. Run detection on what you persist rather than only on what you display, and treat a summary as new content with its own classification rather than as a derivative that inherits the original's handling.
What should an agent handoff payload contain?
The task and the identifiers required to do it, not the transcript. A worker agent looking up an order needs the order number, not the conversation in which the customer mentioned their address. Treat the payload as an outbound boundary and scan it.
AI runtime security in your code
Protect your AI agent workflows with Arcjet
Get allow, deny, and redact on agent actions before the side effect.