Your AI agent just summarized a customer's support ticket, drafted a reply, and queued it for review. Efficient. Impressive. Also: it just sent that customer's home address, credit card last-four, and medical complaint to an external LLM API you don't control.
Nobody got hacked. Nobody wrote malicious code. The agent did exactly what you told it to. And that's the terrifying part.
The Problem Isn't Hackers — It's Architecture
Most developers worry about prompt injection (and they should — I wrote about that too). But data leakage doesn't need an attacker. It happens every time your agent sends user data to a tool, an API, or a model endpoint without checking what's inside.
Think of it like a new intern who photocopies every document — including the ones marked CONFIDENTIAL — and mails them to an external consultant "for context." Technically helpful. Legally catastrophic.
Here's the uncomfortable math: if your agent reads a database row, passes it to an LLM for summarization, and the LLM provider logs inputs for training, you just shared PII with a third party. No breach required. No malicious actor involved. Just an agent doing its job too well.
Three Ways Agents Leak Data (Without Trying)
1. Tool call arguments. Your agent calls send_email(to="support@vendor.com", body="Customer Jane Doe (SSN: 123-45-6789) reports..."). The tool faithfully sends it. The agent didn't know it shouldn't include the SSN — you never told it not to.
2. Context window stuffing. You retrieve 50 customer records for "find the overdue ones." All 50 — including names, emails, phone numbers, and balances — land in the prompt sent to your model provider. You needed five records. You sent fifty.
3. Logging and tracing. Your observability pipeline captures every tool call with full arguments. Now PII lives in your Datadog dashboard, your S3 bucket, and your Slack alerts channel. Your security team doesn't even know to look there.
The Fix: A Three-Layer Sandwich
The pattern that actually works isn't one guardrail — it's three layers that assume each other will fail. Like cheese, bread, and more bread. Nobody trusts a single slice to hold a sandwich together.
Layer 1: Scrub Before the Agent Sees It
Strip or mask sensitive fields before they enter the agent's context:
import re
PII_PATTERNS = {
"ssn": r"\b\d{3}-\d{2}-\d{4}\b",
"email": r"\b[\w.-]+@[\w.-]+\.\w+\b",
"credit_card": r"\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b",
}
def scrub_pii(text: str) -> str:
for field, pattern in PII_PATTERNS.items():
text = re.sub(pattern, f"[REDACTED_{field.upper()}]", text)
return text
# Before passing to agent:
clean_ticket = scrub_pii(raw_ticket_text)
Is regex perfect? Absolutely not. But it catches the obvious stuff before it ever reaches the model, and obvious stuff is what gets you fined.
Layer 2: Sandbox Tool Outputs
Don't let tools return unbounded data. Wrap every data-fetching tool so it returns only the fields the agent actually needs:
def safe_customer_lookup(customer_id: str) -> dict:
full_record = db.get_customer(customer_id)
# Only expose what the agent needs — nothing more
return {
"name": full_record["first_name"],
"status": full_record["account_status"],
"balance_overdue": full_record["balance"] < 0,
# NOT: ssn, address, credit_card, medical_notes
}
This is the biggest win per line of code. Your agent can't leak what it never received. It's like childproofing a kitchen — you don't teach the toddler which outlets are dangerous, you put covers on all of them and hide the knife drawer.
Layer 3: Audit the Outbound
Before any tool call that sends data out (email, API, webhook), run a final check on the payload:
BLOCKED_PATTERNS = [
r"\b\d{3}-\d{2}-\d{4}\b", # SSN
r"\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b", # CC
]
def audit_outbound(tool_name: str, args: dict) -> bool:
payload = str(args)
for pattern in BLOCKED_PATTERNS:
if re.search(pattern, payload):
raise DataLeakBlocked(
f"Tool '{tool_name}' blocked: outbound payload "
f"contains sensitive pattern"
)
return True
# In your agent's tool executor:
audit_outbound("send_email", email_args)
send_email(**email_args)
If Layer 1 missed it and Layer 2 didn't filter it, Layer 3 catches it at the door. Three independent layers, one principle: never trust a single gate with your users' data.
Why Most Teams Skip This (And Regret It Later)
Because it works fine in dev. Your test database has john@example.com and 123 Main Street and a SSN that's all zeros. Everything looks clean. The agent behaves perfectly.
Then production hits, and suddenly the agent is processing real support tickets with real Social Security numbers, real medical details, and real lawyers who notice when those details show up in a third-party's training logs.
The three-layer sandwich takes an afternoon to implement. The GDPR fine takes a year to fight. The class-action takes longer.
The One-Line Checklist
Before you ship any agent that touches user data, ask one question:
"If I printed every single tool call argument this agent makes, would I be comfortable posting it on a billboard outside my office?"
If the answer is "probably not," you need the sandwich.
What's the worst accidental data leak you've caught in an AI pipeline? Drop it in the comments — bonus points if you caught it before it hit production. 🛡️
Top comments (0)