Email prompt injection attacks
An agent that reads email reads text a stranger wrote. Prompt injection is that text written to steer the agent: drop its instructions, leak what it can see, send mail its owner never meant to send. Here are ten ways it's done, each with examples and defenses.
- Instruction override"Ignore all previous instructions." The most direct attack, and it still works on unprotected agents.
- Role playThe attacker hands the model a new persona, one without the old rules.
- System prompt mimicryFake [SYSTEM] tokens and control directives that read as if they came from the operator.
- Delimiter abuseCode fences, XML tags and separators that make injected text look like structure.
- Data exfiltrationGetting the agent to reveal its instructions, other mail, or the credentials it can see.
- Encoding evasionBase64, hex and rot13. If the model can decode it, a payload can hide in it.
- Context manipulationFake conversation history that tells the agent it is somewhere else, talking to someone else.
- Multi-language attacksSwitching language mid-message to get past rules written for English.
- Social engineeringUrgency, authority and flattery. The tricks that work on people work on models too.
- Token smugglingZero-width characters and look-alike letters: invisible to a reader, visible to the tokenizer.
Why email is different.
In a chat, a person reads every answer. An email agent forwards, replies and updates records on its own. A successful injection can forward confidential mail to the attacker, send replies the account holder never wrote, or feed made-up data into a workflow. The agent follows instructions. It can't tell whose. No single check stops that, so defenses come in layers.
What Molted does about it.
Molted is the mailbox under your agent. It sees each message before the agent does and each reply before it goes out. It can't make a model immune to injection. It hides a flagged message's body when your agent reads the thread, and holds what the agent does next.
// get_thread: one entry of envelopedMessages
"untrustedContent": {
"bodyText": "[quarantined -- injection risk]",
"bodyHtml": null,
"sanitizationExpected": true
},
"agentGuidance": {
"injectionRisk": "high",
"contentTrust": "untrusted",
"recommendedIsolation": true,
"flags": ["injection_risk", "quarantined"]
}- Screened on arrival
- Every inbound message is checked against ten pattern families, from instruction overrides and fake system tokens to tool-call attempts and CSS-hidden text, after invisible Unicode is stripped. The subject, the text body and a text rendering of the HTML body are all covered, so an HTML-only message can't slip through. The patterns match known phrasings, mostly English. New wording can get past them.
- Quarantined by default
- When your agent reads a thread (
get_thread), a high-risk message shows as[quarantined -- injection risk], not its body. Showing the original there takes the operator scope. - Marked as untrusted
- In
get_thread, every inbound body comes back insideuntrustedContent, withagentGuidancebeside it: the risk level, whether to isolate it, andcontentTrust: "untrusted". That label doesn't depend on the patterns matching. - Replies held on a flagged thread
- If a thread had a quarantined or high-risk message in the last seven days, your agent's reply on it waits for a person, whatever the mailbox's autonomy level.
- A canary in every thread
- Reading a thread hands your agent a canary token. A reply on that thread that repeats it is blocked by default.
Keys and data.
How Molted holds your keys and your workspace, and how you take your data back out. The privacy policy has the rest.
- Keys stored as argon2 hashes
- Molted keeps a hash and a prefix of each API key, never the key itself. It can check a key, not read one back.
- One database, filtered by workspace
- Workspaces share one Postgres database, not one each. Your key resolves to your workspace, and queries are filtered by it.
- Export and delete
- Settings, Workspace data: download your workspace's data, or delete the workspace. Deletion runs after a seven-day wait, and the owner can cancel it until then.