01 · THE CLAIMCheck what leaves, not only what comes in.
Every message, upload, API call or tool argument an agent sends can carry a key, a password or a customer's data. Telling the model "don't leak" helps, but it isn't a control. Our claim: every outgoing send gets checked before it goes. Secrets never leave. Customer data only goes to a host we know, and masked. And a secret-file read followed by a send to a new host stops.
Your agents act. We make sure they don't leak.
02 · WHAT HAPPENEDWe guarded the front door. This is the back door.
In Logs and docs are data, not orders. we said text an agent reads is not text it obeys, and we built a plan check that stops a config read followed by a send to a host off the allowlist. In An approval is for one exact change. we said a yes covers one named step. Both are about what an agent is allowed to do. Neither looks inside what it sends. If an injected instruction slips past, or an agent pastes a log line that happens to hold a key, the secret is in the payload. That is what this check reads.
03 · WHAT WE READPrompts aren't enough, and the leak looks routine.
- OWASP, LLM02:2025 Sensitive Information Disclosure. Sensitive information "includes personal identifiable information (PII), financial details, health records, confidential business data, security credentials, and legal documents." On prompts: "However, such restrictions may not always be honored and could be bypassed via prompt injection or other methods." On a fix: "Techniques like pattern matching can detect and redact confidential content before processing."
- CrowdStrike, Vanessa Villa and Dr. Jim Hoagland, "New Prompt Injection Techniques Cover Agent-Planning Attacks" (29 October 2026). In their hypothetical example, "Sending local config files, including credentials, to an outside server becomes a step that it believes the goal requires." And: "Each action looks normal by itself: Reading .env is routine debugging, and an HTTP request is routine patch-fetching."
- OpenAI, Safety in building agents. "For example, a prompt might trick a data lookup agent into sending raw customer records instead of the intended summary."
- OpenAI, Keeping your data safe when an AI agent clicks a link (28 January 2026). "Even if the model doesn't "say" anything sensitive in the chat, a forced URL load could still leak data." And on allowlists: "A link can start on a "trusted" domain and then immediately forward you somewhere else."
We cite their research and guidance only. We haven't tested any vendor's products.
04 · THE CHECKScan the payload, check the host, remember the read.
Same method as the earlier write-ups: write the rule, run it in the terminal, keep the output. This is the check, copied from leak_guard.py:
def check_send(send, allow, read_sensitive=False):
"""One outgoing send: tool call, output or upload. Returns (decision, reason, body)."""
found, host = scan(send["body"]), send["host"]
known = host in allow
if read_sensitive and not known:
return "BLOCK", "read a secret file earlier; " + host + " is new", None
if found and not known:
return "BLOCK", "found " + ", ".join(found) + "; " + host + " is new", None
secrets = [f for f in found if f in SECRETS]
if secrets:
return "BLOCK", "found " + ", ".join(secrets) + "; a secret never leaves", None
if found:
return "REDACT", "masked " + ", ".join(found) + " for " + host, redact(send["body"])
if not known:
return "ASK", host + " is a new host; a person decides", None
return "SEND", "nothing sensitive, known host", send["body"]Excerpt from our demo file. The full file is 72 lines of standard-library Python.First, check the checker. Two of the tests check a weakness on purpose: they confirm that a base64-encoded key and a key split in two get through.
$ python3 -m unittest -v test_leak_guard test_api_key_found (test_leak_guard.T.test_api_key_found) ... ok test_aws_key_found (test_leak_guard.T.test_aws_key_found) ... ok test_card_found (test_leak_guard.T.test_card_found) ... ok test_clean_known_sends (test_leak_guard.T.test_clean_known_sends) ... ok test_clean_new_host_asks (test_leak_guard.T.test_clean_new_host_asks) ... ok test_email_known_host_redacted (test_leak_guard.T.test_email_known_host_redacted) ... ok test_email_new_host_blocked (test_leak_guard.T.test_email_new_host_blocked) ... ok test_github_token_found (test_leak_guard.T.test_github_token_found) ... ok test_key_to_known_host_blocked (test_leak_guard.T.test_key_to_known_host_blocked) ... ok test_key_to_new_host_blocked (test_leak_guard.T.test_key_to_new_host_blocked) ... ok test_known_limit_base64_key_missed (test_leak_guard.T.test_known_limit_base64_key_missed) ... ok test_known_limit_split_key_missed (test_leak_guard.T.test_known_limit_split_key_missed) ... ok test_private_key_found (test_leak_guard.T.test_private_key_found) ... ok test_redact_masks_key (test_leak_guard.T.test_redact_masks_key) ... ok test_secret_read_then_known_host_ok (test_leak_guard.T.test_secret_read_then_known_host_ok) ... ok test_secret_read_then_new_host_blocks (test_leak_guard.T.test_secret_read_then_new_host_blocks) ... ok ---------------------------------------------------------------------- Ran 16 tests in 0.002s OKCopied from our run (2 Nov 2026). Text unchanged.
Then run seven made-up sends through it, including CrowdStrike's "diagnostics endpoint" pattern.
$ python3 leak_guard.py bug report, clean -> SEND nothing sensitive, known host tool output carries a key -> BLOCK found api key; a secret never leaves key to a 'diagnostics' host -> BLOCK found api key; diag-endpoint.example is new customer email in a summary -> REDACT masked email for crm.internal sent: follow up with [REDACTED email] customer email to new host -> BLOCK found email; paste.example is new clean text to new host -> ASK paste.example is a new host; a person decides read .env, then new host -> BLOCK read a secret file earlier; diag-endpoint.example is new note: FAKE keys and customers; patterns only, so encoded or split keys get through.Copied from our run (2 Nov 2026). Text unchanged.
One SEND, one REDACT, one ASK, four BLOCK. The second line matters most. The host is one we trust, but a key never leaves, so trust in the host doesn't help. The last line repeats the plan rule from our earlier write-up on logs and docs: the payload is clean, but the run read .env first, so a send to a new host is blocked.
05 · HOW THE CHECK DECIDESFirst match wins.
| IN THE SEND | RESULT |
|---|---|
| The run read a secret file, and this send goes to a new host | BLOCK |
| Anything sensitive, going to a new host | BLOCK |
| A key-shaped string, to any host | BLOCK |
| A customer field (email, card number), to a known host | REDACT |
| Clean text to a new host | ASK |
| Clean text to a known host | SEND |
06 · WHAT THIS DOES NOT PROVEHonest limits.
- Patterns only see what looks like a secret. A base64-encoded key or a key split across two sends gets through. Our own tests confirm both.
- Some secrets don't look like secrets. A password that is a plain word, a customer's name or address: the demo has no pattern for them.
- Some fakes look like secrets. Any 13 to 16 digits count as a card number. False alarms are real, and too many make people switch a check off.
- A host list is not enough on its own. As OpenAI notes, a trusted link can redirect somewhere else. The demo compares host names only.
- It's a small local demo. Fake data, 72 lines of Python. Nothing in our real agents runs it yet.
| Written rule: no secrets in files; keys only from environment variables | BUILT |
| Written agent rule: a secret or config read followed by a send off the allowlist blocks the plan | BUILT |
| Every send, post or upload waits for the owner's typed yes | BUILT |
| Leak-guard demo: SEND, REDACT, ASK or BLOCK, 16 tests OK | RAN |
| Outgoing scan wired into our real agents' tool runner | PLANNED |
| Canary values: fake secrets planted in test data to show if a path leaks | PLANNED |
| Catching encoded or split secrets | UNKNOWN |
| How many false alarms people will accept on real traffic | UNKNOWN |
"Built" here means written rules our agents work under. They are not enforced by code yet.
07 · WHAT WE'LL BUILDLeak protection (in development).
- Outgoing scan on every tool call, output and upload (this article's check).
- Known hosts only: a send to a new host is held for a person.
- Canary values in test data, so a leak path shows itself.
- Redact, then send, with one log line per send: time, agent, host (known or new), findings, decision.
- Plan check and named yes before it all (from Logs and docs are data, not orders. and An approval is for one exact change.).
08 · TAKEAWAYRead everything. Check what leaves.
A prompt that says "don't leak" is a wish. A check on every send is a control. Secrets never leave, customer data leaves masked and only to hosts you know, and a secret read followed by a send somewhere new stops. One thing we haven't solved: how many false alarms will people put up with before they turn the check off? Feel free to challenge our thinking. What checks your agent's sends today?
TRY IT YOURSELFTwo checks for this week.
- Pick one agent. List every place it can send data out: messages, uploads, API calls, links it loads.
- For each one, ask: what would stop a key from leaving today?
- OWASP: LLM02:2025 Sensitive Information Disclosure.
- Vanessa Villa and Dr. Jim Hoagland, CrowdStrike: "New Prompt Injection Techniques Cover Agent-Planning Attacks" (29 October 2026). Their example is hypothetical.
- OpenAI: Safety in building agents · Keeping your data safe when an AI agent clicks a link (28 January 2026).
- Earlier write-ups: Logs and docs are data, not orders. · An approval is for one exact change.
- The leak-guard demo is a small local teaching example: fake keys, fake customers, made-up hosts, standard-library Python. The tests and output are from our run on 2 November 2026.
