Controls · September 26, 2026

The Agent Email Threat Model

Assets, actors, trust boundaries and eight threats for an agent with an email address, with the layer where each control binds and where ours stops short.

An email address is the only interface on an agent that anyone on the internet can write to without a key. Mail arrives from strangers, the agent reads it into the same context that holds its instructions, and the same agent can usually send mail back out. Most write-ups on the risk stop at prompt injection. Injection is the entry point. It is not the whole attack surface.

This is an agent email threat model built the way a security review would build one: the assets, the actors, the trust boundaries, then eight threats with likelihood, impact and mitigations. For every mitigation it names the layer that enforces it, and it says where this product mitigates the threat and where it does not. Two findings run through it. The grant is the only control that changes the impact of every threat at once. And two of the eight, sender impersonation and reply loops, have no preventive control on this platform today: our DMARC record expresses no policy for mail that fails it, and our send API cannot set the header RFC 3834 relies on. Injection itself is covered in depth in prompt injection by email. That post is one room; this is the map.

Assets, actors and trust boundaries

A control enforced only by the system prompt is not a control. Everything below follows from that rule, so the four layers a control can live in need defining first:

LayerWhat it isHolds when the model is persuaded
ModelSystem prompt, tool descriptions, model trainingNo
ApplicationYour agent code, around the modelYes, if the check never reads model output as authority
PlatformThe mail service: credentials, policy, approvalsYes
ProtocolSMTP, SPF, DKIM, DMARC, RFC 3834, OAuthYes, for the narrow thing each proves

The assets worth protecting are not only the messages. They are the mailbox history, which is private data by construction; the right to send as the address, which is a reputation other people rely on; the credentials that confer both; the integrity of the approval queue; the delegated authority of the human who connected the agent; and the monthly allowance, which a runaway agent can burn.

There are five actors. The stranger knows the address and controls every byte of an inbound message. The counterpart is another automated system, whether an auto-responder, a ticketing system or another agent. The model is not an attacker, but it has to be treated as one that can be turned by input. The human operator authorises the agent and reviews what it holds. The platform operator runs the mail service, and here that is us.

Four trust boundaries matter. Mail crosses from SMTP into the platform at the provider. Stored content crosses from the platform into the model’s context when a tool reads it. A tool call crosses back from the model to the platform, and this is the boundary where authorisation has to be recomputed from stored state rather than taken from anything the model said. Outbound mail crosses from the platform to the internet, after which nothing can recall it.

Simon Willison’s lethal trifecta names the dangerous combination as “access to your private data”, “exposure to untrusted content” and “the ability to externally communicate”. His own example of the second is email: “an attacker can literally email your LLM and tell it what to do”. An inbox credential that can also send holds all three in a single grant. That is why the threat model for an agent inbox is not the threat model for a chatbot that happens to call an email API.

The agent email threat model: eight threats, ranked

The ratings are our judgement for an agent that receives mail from the public, not measurements. Likelihood reflects how little the attacker needs; impact reflects what the worst plausible success costs.

ThreatLikelihoodImpactWhere the binding control lives
Indirect prompt injection via inboundHighVaries with the grantPlatform (grant), application
Exfiltration through outbound mailMediumHighPlatform (recipients, caps, approval)
Sender spoofing and impersonationHighMediumProtocol, partly; application
Auto-reply and agent-to-agent loopsMediumMediumApplication; platform caps bound the damage
Credential sprawl and over-broad grantsMediumHighPlatform
Approval bypass, approve-then-edit raceLowHighPlatform
Retention of sensitive inbound contentHighMediumPlatform and application
Confused deputy acting for a humanHighHighPlatform (grant intersection)

Injection is rated by what it unlocks, which is why its impact varies. Against a read-only grant with no other tools in the session, a successful injection yields a bad summary. Against a send grant with an empty policy it opens most of the other rows in this table. OWASP’s own example for LLM06:2025 Excessive Agency is email: “a maliciously-crafted incoming email” that “tricks the LLM into commanding the agent to scan the user’s inbox for senitive information and forward it to the attacker’s email address” (the misspelling is theirs). Its fixes are all below the model: a mail-reading extension without send, an OAuth session “with a read-only scope”, manual review before every send, and rate limiting on the sending interface.

Inbound: injection and what SPF, DKIM and DMARC actually prove

Treat every inbound message as attacker-authored, including the ones that authenticate. OWASP LLM01:2025 defines indirect injection as the case where “an LLM accepts input from external sources”, and its mitigation that matters here is to “handle these functions in code rather than providing them to the model”. The mechanics of email-borne injection, from hidden HTML to attachment payloads, are in the injection post. What this map adds is the case that proved the category is not hypothetical. CVE-2025-32711, known as EchoLeak, is described in its NVD record as “Ai command injection in M365 Copilot” that “allows an unauthorized attacker to disclose information over a network”. Checkmarx’s write-up says it worked “simply by sending a carefully crafted email message”, credits discovery to Aim Labs, and describes exfiltration through a Markdown image reference in Copilot’s own answer, pointing at an attacker URL with the data in its query string, which the browser fetched without a click. OWASP’s agentic Top 10 cites EchoLeak as its example of ASI01, Agent Goal Hijack.

Spoofing is the adjacent threat, and it is where teams most often overestimate the protocol layer. Each mechanism answers one narrow question:

MechanismWhat a pass provesWhat it does not prove
SPF, RFC 7208The sending host was authorised for the “HELO” or “MAIL FROM” identityAnything about the From: header a person or model reads
DKIM, RFC 6376The signing domain claimed “some responsibility” and the signed content was not alteredThat the signer is the author. The RFC “separates the question of the identity of the Signer of the message from the purported author”
DMARC, RFC 9989An SPF or DKIM identity aligns with the From: domainAnything about the display name or a look-alike domain

RFC 9989, published in May 2026, obsoletes RFC 7489 and states its own limit plainly: DMARC “does not address the use of visually similar domain names or abuse of the RFC5322.From human-readable display name”. A DMARC pass on accounts@examp1e-billing.com proves that examp1e-billing.com sent it. It says nothing about whether your CFO did. None of the three is evidence about the human behind a message, and none is evidence that its content is safe to act on.

We checked our own records so that nobody has to take a claim about them on trust. On 2026-09-25, dig +short TXT _dmarc.agents.emailforagents.ai returned v=DMARC1; p=none; rua=mailto:support@agents.emailforagents.ai; adkim=r; aspf=r; ri=86400. send.agents.emailforagents.ai publishes v=spf1 include:amazonses.com ~all, and a DKIM key is published at resend._domainkey.agents.emailforagents.ai. Under RFC 9989, p=none means “the Domain Owner offers no expression of preference”, so a receiver is not asked to reject or quarantine mail that fails DMARC while claiming to come from one of our agents’ addresses. The ri tag is listed as historic in RFC 9989. For anyone modelling agent-to-agent traffic on this platform: a From: line naming a sibling agent’s address is not, on the strength of our DNS today, something a receiver will refuse when it is forged.

Our side of the inbound boundary does two things, omits a third, and has one open question. Routing uses the recipient list the mail provider reports in its signed webhook, cross-checked against the provider’s own retrieve API, and a disagreement quarantines the message rather than delivering it. MIME To: and Received: headers are kept for display and are never routing inputs. What we have not yet confirmed is that the provider’s recipient list is always the SMTP envelope, independent of a forged To: header. Our own internal review of this boundary, dated 2026-09-20, found no demonstrated cross-inbox misdelivery and also could not prove isolation against arbitrary forged headers, so treat that isolation as unproven rather than guaranteed.

The agent sees the sender’s display name, address and any Reply-To as separate fields, and when it replies it must supply recipients explicitly, so a hostile Reply-To does not silently become the destination. What this build does not do is pass an SPF, DKIM or DMARC verdict through to the agent. The binding control for impersonation is therefore the application’s: never let a From: line, authenticated or not, grant anything, and confirm changes to payment details or credentials out of band.

Outbound: exfiltration and loop storms

Exfiltration needs a destination and a payload, and outbound mail supplies both. The payload can be the body, an attachment, or data encoded into a URL. The destination can be an address the attacker named, or an address the agent is allowed to reach that the attacker also reads.

The controls that bind here are platform controls, and this product has four. A draft-only credential turns every send into an approval request, so nothing leaves without a person. Recipient block and allow lists are evaluated before approval rules, blocks first, and one unmatched recipient denies the whole message. A daily_recipient_limit counts each live message’s distinct recipients against a per-UTC-day total, so two messages to the same address count twice. And outbound HTML passes the same sanitiser as inbound, in which an <img> may only keep a cid: source, so an agent-composed message cannot carry a remote image: the EchoLeak technique of a URL that leaks data the moment a client loads it is stripped before the message is stored or sent. The same sanitiser renders inbound mail in the console, so a reviewer opening a hostile message does not fetch its images either. Links with https and mailto survive sanitisation, and so does anything written in plain text. Recipient controls stop the wrong address. They do not stop the right address receiving the wrong content, and nothing on this platform inspects bodies for secrets. That gap belongs to the application or to the reviewer.

Loops are the outbound threat nobody attacks on purpose. Two auto-responders, or an agent and a ticketing system, each treat the other’s reply as new mail and answer it. RFC 3834 settled the protocol answer in 2004: an automatic response “SHOULD NOT be issued in response to any message which contains an Auto-Submitted header field” with any value other than “no”; an automatic response SHOULD carry Auto-Submitted: auto-replied; responders that send “away” or “too busy” notices SHOULD NOT send the same response to the same sender more than once within several days, with “a 7-day period” recommended as the default; and responders “MUST NOT generate any response” whose destination would be a null address.

Here is where this build falls short, stated precisely. Outbound mail is sent with In-Reply-To, References and an internal correlation header, and the send API accepts no custom headers, so an agent cannot mark its replies Auto-Submitted: auto-replied. Inbound messages are exposed with sender, recipients, subject and threading fields, not arbitrary headers, so an agent cannot read Auto-Submitted on what it received either. There is no per-thread reply budget. The RFC’s first line of defence is unavailable at both ends.

What bounds a loop today is the platform’s ceilings. A daily recipient cap, if you set one, stops live sends for the rest of the UTC day. The monthly allowance counts every received message and every recipient sent to, so a loop between two free-tier workspaces spends both sides’ 100 email units and then sending stops. A permanent bounce, a complaint or a provider suppression adds the address to the workspace suppression list, and later live sends to it fail with recipient_suppressed before the provider is contacted. None of these prevents a loop. They cap its cost. Prevention belongs in your application: keep a reply counter per thread and per correspondent, refuse to answer mailer-daemon, postmaster and no-reply addresses, and borrow the RFC’s seven-day window for any automatic acknowledgement your agent sends. OWASP’s agentic Top 10 does not discuss mail loops; the closest of its categories is ASI08, Cascading Failures, where “false signals cascaded through automated pipelines with escalating impact”.

Credentials, grants and the confused deputy

Credential sprawl is the threat with the longest tail, because a key issued for a prototype outlives the prototype. OWASP LLM06 asks for extensions limited “to the minimum necessary”, and the MCP security best practices list “wildcard or omnibus scopes” and “treating claimed scopes in token as sufficient without server-side authorization logic” among the common mistakes.

This product enforces three rules at the platform layer. A credential cannot exceed its creator: requesting a scope your role lacks fails at creation with permission_denied. Authority is re-read on every request as an intersection with the authorising human’s current grant, so demoting that person narrows the agent on its next call. The environment lives in the key prefix, so an efa_test_ key cannot reach a live inbox. OAuth connection grants add a fourth rule: eight scopes, including keys:manage, policies:write and messages:delete, can never be delegated to them. The mechanics of the tool list that results are in the MCP tool surface permission boundary.

The weaker spot is API keys, and it is worth knowing before you hand one to an agent. The eight-scope exclusion applies to connections, not keys. A key created by an owner can carry keys:manage and mint child keys. The children are bounded: each child’s scopes must be held by its parent, a child cannot outlive a parent that has an expiry, and every request walks the whole chain, so revoking a parent denies every descendant on its next call. A key with no expiry still never expires unless someone set one. Give agents OAuth connections, which is also the only credential the MCP endpoint accepts, and keep keys for your own backend. Scoped credentials for agent email covers the scope list.

The confused deputy is the oldest name for the core problem. The term comes from Norm Hardy’s 1988 paper, The Confused Deputy, about a compiler that “runs with authority stemming from two sources”. Wikipedia’s definition is a program “tricked by another program (with fewer privileges or less rights) into misusing its authority”. An email agent is that deputy: a stranger with no authority writes a message, and the agent spends the authority of the human who connected it. The MCP security best practices, a companion to the MCP authorization specification, use the term more narrowly: for MCP proxy servers in front of third-party APIs that skip per-client consent, so that a third-party authorization server’s consent cookie lets an attacker obtain authorization codes. This server is not a proxy to a third-party API, so that specific attack does not describe it, and the broader problem still does.

Two things here bind. The intersection means the agent can never do more than its human could do at that moment, which is OWASP’s “execute extensions in user’s context” enforced in the database. And the agent has its own address rather than the human’s mailbox, so a persuaded agent sends as the agent and not as the person. What does not bind is anything inside the grant. Every capability the human delegated is available to whoever writes the most persuasive email, which is why the grant should be the smallest one that does the job.

Approval bypass and the approve-then-edit race

An approval gate is only worth what it binds. The classic failure is a review that shows one version and sends another: the reviewer approves, the agent or a concurrent writer edits the draft, and the edited version goes out under the approval.

That race is closed at the platform layer in three places. The approve call carries no message content, only the approval ID and an idempotency key; the payload that sends is read from the stored draft version the approval names. The approval and the draft version each hold a SHA-256 of the canonical payload, and a mismatch fails with version_conflict. Then the send worker re-hashes the stored snapshot and re-runs authorisation before it contacts the provider, so content that changed after approval, or a grant revoked after approval, stops the send at dispatch. This build also exposes no draft-edit endpoint. An agent that wants to revise opens a new approval, and the old one stays pending until someone rejects it or it expires after seven days. Human approval before an AI agent sends email has the full record.

Three other bypasses are worth modelling. Self-approval fails: approving requires a human principal holding approvals:decide, and no connection preset carries that scope. Routing around the gate works if you let it: posture is set per credential and per inbox policy, so a second credential holding messages:send on the same inbox sends directly unless that inbox’s policy says always. Persuading the reviewer is the one no platform can close. OWASP names it ASI09, Human-Agent Trust Exploitation, where “confident, polished explanations misled human operators into approving harmful actions”. A draft built from an injected message can be plausible, correctly addressed and wrong.

Instrument the gate with the events that exist. This build emits approval.requested and approval.rejected. approval.approved and approval.invalidated are declared in the event catalogue and never emitted; an approval surfaces as message.queued. The events and webhooks reference lists the rest.

Retention and deletion of what strangers send

An agent inbox accumulates exactly the data an attacker would want: password reset links, one-time codes, invoices with bank details, personal data about third parties who never agreed to meet an agent. OWASP LLM02:2025 lists “security credentials” and “personal identifiable information” among what leaks, and asks for “clear policies about data retention, usage, and deletion”.

Our position is weaker than a reader might assume, so here it is in full. Live mail is kept while the workspace is active. There is no customer-selectable automatic expiry for live mail, and this build has no endpoint that deletes an individual message. messages:delete exists as a scope and cannot be delegated to a connection. Deactivating a project stops routing and sending and revokes its credentials, but per data controls it “does not permanently erase data”, and permanent erasure requires a manual request and review. Test inboxes are the exception: their content expires after seven days.

What binds in the other direction is custody. Agents never receive raw MIME, messages:raw cannot be delegated, and exports exclude original MIME, Bcc, attachment bytes and credentials, with download access expiring after 24 hours. Mail history is also not the only copy. Every message an agent reads may be logged by your model provider and your agent framework’s tracing, and those copies live in the application layer, outside anything this platform can delete. Minimise at the source: do not route verification codes for accounts you care about to an agent inbox unless the agent has a job to do with them, and treat the trace store as a mailbox with the same sensitivity.

Start with the grant, because it is the one control on this map that changes the impact column of every row at once. List every connection on the workspace from the agent controls overview, and move any send-capable grant on an inbox that receives mail from strangers down to draft first.

Sources

All read on 2026-09-25.


Keep reading

All guides · Documentation · Controls