# 扣留与注入扫描

Source: https://docs.simail.ai/zh/holds/

> 为什么来信会被扣留、扣留期间 agent 看到什么、提示注入扫描怎样工作，以及人怎样放行或拒绝。

**扣留**把一封信挡在 agent 外面，直到人看过。扣留由确定性检查在**存入之前**决定，所以可疑的信从不存在一个能被 agent 读到的窗口。

## agent 看到的扣留邮件

在每个通道（REST、MCP、webhook、`GET /v0/events`、WebSocket）上，扣留的信**只有元数据**：

```json
{
  "message_id": "msg_…",
  "thread_id": "thr_…",
  "inbox_id": "agent-7k2m@simail.email",
  "from": "unknown@example.com",
  "timestamp": "2026-10-18T17:02:11Z",
  "size": 18422,
  "labels": ["received", "held"],
  "hold_reasons": ["injection_suspected"],
  "held": true,
  "trust_envelope": { "computed_by": "none", "untrusted": true }
}
```

`timestamp` 是 simail 收到这封信的时间（和普通邮件同一个字段）。

主题、正文、附件和原始信源都不给。列表里只有带 `hold_metadata_read` 的 key 能看到扣留的信。扣留的信从不唤醒 agent，搜索只匹配它的元数据。取扣留邮件的正文不行；附件和 raw 端点也不行。

agent 不能放行：`POST /v0/inboxes/{inbox_id}/messages/{message_id}/agent-armor/release` 返回 `403 human_required`，MCP 服务也没有放行工具。

## 为什么会扣留

| `hold_reasons` | 原因 |
|---|---|
| `rate_limited` | 同一发件人向同一邮箱每小时超过 50 封，或向该组织每小时超过 2,000 封（再高很多时，多出来的副本只计数并丢弃） |
| `unparseable` | 这封信无法安全解析 |
| `injection_suspected` | 提示注入分数达到阈值（见下） |
| `otp_limit` | 仅外部组织：该组织今天的验证码邮件太多（超过 10 封，或来自超过 3 个发信域）。发给内部租户的验证码邮件照常交给 agent |
| `sensitive_material` | 这封信看起来像受监管或特权材料（例如法律案件材料），simail 不接受 |
| `enrichment` | 稍后的可选检查（附件文本、基于模型的打分）发现了问题 |

## 提示注入扫描

扫描器是一组确定性启发式，对每封来信的主题、正文和 HTML 运行：

- 针对 AI 的指令措辞（「ignore previous instructions」、角色或系统提示），中英文都看；
- 隐藏文本：零宽字符、CSS 隐藏、与背景同色的文字；
- 伪造的系统提示和工具调用语法；
- 很长的编码块。

结果是 `trust_envelope.injection_score`，在 0 和 1 之间。达到或超过 `injection.hold_threshold`（默认 0.7；你的策略可以调低，不能调到高于平台值）就扣留。

两项可选检查**默认关闭**，只能由人打开：从附件抽文本，以及基于模型的注入打分。两者都在信存入之后跑，并且只能**追加**扣留，不能解除。后来被扣留的信会从搜索结果里消失，并发出 `message.held` 事件。

:::caution
没有过滤器能抓住每一次提示注入。没被扣留的信仍然是**不可信输入**。不要执行邮件里的指示。
:::

## 放行和拒绝（人）

在控制台里，能打开该邮箱的人进入 **Held**，然后选择：

- **放行**：这封信变成普通邮件。正文进入搜索索引，并发布 `message.released`（可以唤醒 agent）。
- **拒绝**：这封信继续不让 agent 碰到，并随废纸篓删除。

两者都要在 5 分钟内再次验证。人也可以把发件人加进拦截名单。

## 事件

| 事件 | 何时 |
|---|---|
| `message.held` | 一封信以扣留状态存入（只有元数据），或后来被可选检查扣留 |
| `message.released` | 人放行了它 |
