在Gmail中看到**“邮件签名”(或其他客户端中的类似UI提示)提醒我们,身份和完整性在邮件工作流中很重要。但如果你通过webhooks接收入站邮件(用于QA自动化、注册验证或LLM代理),安全边界会发生变化:你的真正风险往往不是SMTP消息是否有DKIM签名,而是传递解析邮件的HTTP请求**是否真实。
本指南解释了”邮件签名”的真实含义,为什么它对webhook驱动的自动化不够用,以及如何验证webhook负载真实性,使其能够应对重试、重复和恶意输入。
如果你专门使用Mailhook,它支持接收邮件的实时webhook通知和签名负载(结构化JSON)。有关规范、最新的集成合约(确切的标头、签名构造和示例),请使用:mailhook.co/llms.txt。
“邮件签名”的真实含义
大多数收件箱UI在消息通过密码学认证检查时显示”签名”,通常是DKIM(域密钥识别邮件)。DKIM允许发送域对消息的部分内容进行签名,以便接收方能够检测传输中的篡改并将消息与域关联。
重要细节:
- DKIM帮助接收方回答”这条消息可能来自该域的授权发送者吗,它在传输中被修改了吗?”
- DKIM不能认证你的系统稍后从webhook提供商收到的HTTP请求。
即使消息完美地经过DKIM签名,恶意行为者仍可尝试:
- 用虚假的”收到新邮件”负载欺骗你的webhook端点
- 重放较旧的有效负载以重新触发OTP流程
- 如果你的验证不是基于原始主体计算的,则篡改JSON字段
因此,将”邮件签名”视为邮件本身的有用上下文,而不是webhook认证的替代品。
DKIM vs webhook签名:两个不同的信任问题
| 机制 | 认证内容 | 适用范围 | 无法保护你免受什么影响 |
|---|---|---|---|
| DKIM(显示为”邮件签名”) | 由域签名的邮件消息部分 | SMTP和邮箱接收 | 伪造的HTTP webhook调用、重放传递、你自己的解析/规范化管道 |
| Webhook签名 | 传递到你端点的HTTP请求主体 | 你的应用程序边界 | 包含恶意内容的”合法”邮件(提示注入、SSRF链接等) |
自动化中的最佳实践是同时验证两者:
- 验证webhook签名以证明请求来自你的提供商。
- 即使在验证webhook后,也要将邮件内容视为不可信输入。
有关DKIM标准背景,请参见RFC 6376。
威胁模型:你要防御什么
当你的系统通过webhook以JSON形式接收入站邮件时,假设攻击者会尝试常见的失败模式,这些模式也类似于正常的分布式系统行为:
- 欺骗:直接向你的webhook端点发送请求
- 重放:重新发送先前有效的请求以再次触发副作用
- 主体篡改:利用重新序列化JSON的框架,意外验证不同的字节表示
- 标头混淆:依赖IP允许列表或”User-Agent”字符串而不是密码学验证
- 重试放大:触发多个下游操作,因为你的处理程序不是幂等的
如果你的下游消费者是LLM代理,风险更高:伪造或重放的邮件可能成为指令注入或导致代理重新发送验证邮件、点击链接或泄露秘密。
“验证webhook负载真实性”在实践中应该意味着什么
安全的webhook验证流程通常包括以下所有内容:
1) 验证基于原始请求主体计算的签名
双方唯一能达成一致的字节序列是原始HTTP请求主体。许多webhook漏洞来自验证解析对象(或格式化的JSON字符串)而不是收到的确切字节。
实现规则:
- 在框架接收时捕获原始主体字节。
- 使用你的webhook秘密计算预期签名。
- 使用恒定时间比较来比较签名。
签名构造方式因提供商而异(HMAC、分离JWS、非对称签名)。不要猜测标头名称或规范化规则。
Mailhook为webhook通知提供签名负载,但你应该遵循mailhook.co/llms.txt中的确切合约。
2) 强制时间戳容忍度以降低重放风险
仅签名证明真实性,但不证明新鲜度。
添加时间戳要求:
- 要求提供商提供的时间戳标头或字段。
- 拒绝超过小时间窗口的请求(通常5分钟,有时根据重试和队列1到15分钟)。
- 考虑时钟偏差并设置明确的容忍度。
如果必须支持更长的延迟,请将时间戳检查与重放存储结合使用(下一节)。
3) 使用传递ID添加重放检测
即使是合法的提供商也会重试webhooks。攻击者也会重放。
因此你的处理程序应该:
- 提取稳定的事件标识符(传递ID、事件ID、消息ID或提供商特定的幂等密钥)。
- 在快速数据存储中使用TTL存储它。
- 拒绝或无操作重复。
如果你的webhook触发副作用(将OTP标记为已使用、创建帐户、批准工作流),这不是可选的。
4) 失败关闭,并将”验证”与”处理”分离
清洁的设计是:
- 验证层:签名、时间戳、重放检查
- 处理层:解析JSON、按消息/工件去重、提取OTP或链接、排队工作
如果验证失败:
- 返回4xx(通常是401/403)并且不排队。
如果处理失败:
- 只有在安全持久化事件以便重试后才返回2xx,或返回5xx以便提供商重试。有意选择。
5) 将秘密保留在日志之外并保护端点
最低限度的卫生:
- 在秘密管理器中存储webhook秘密。
- 轮换秘密并在轮换期间支持双重验证。
- 仅使用HTTPS。
- 不要在共享日志中记录原始签名、秘密或完整邮件正文。
提供商无关的验证清单(复制/粘贴用于代码审查)
在让代理或CI管道信任入站邮件事件之前,将此用作审查门:
- 原始主体被捕获并用于签名验证
- 签名验证是恒定时间的
- 时间戳是必需的,并使用定义的容忍度进行检查
- 使用稳定传递标识符的重放保护存在
- 处理程序在副作用层是幂等的
- 未验证的请求永远不会到达下游处理器
- 邮件内容被视为不可信输入(无HTML渲染、链接允许列表、最小工件提取)
参考架构:webhook优先,轮询后备
Webhook传递是接收入站邮件的最低延迟方式,但生产系统应该能够抵御偶尔的传递失败。
自动化优先收件箱系统中的常见模式是:
- 邮件到达时Webhook快速触发。
- 你的处理程序验证真实性、持久化记录并排队工作。
- 如果webhook错过或延迟,你的工作流可以轮询收件箱API作为后备。
Mailhook支持webhook通知和轮询API,这对确定性QA和代理工作流非常有用,你希望”推送优先,拉取作为备份”。

LLM代理的实用注意事项:真实性是必要的,但不充分
即使完美认证的webhooks也可以传递敌对内容,因为攻击者可以向你的一次性收件箱发送真实邮件。
为了代理安全:
- 首选最小化JSON视图(主题、发件人、接收时间和提取的工件),而不是原始HTML。
- 避免让代理”浏览”邮件中找到的任意链接。
- 为验证链接添加主机允许列表(并考虑使用严格规则在服务器端解析重定向)。
- 对代理操作设置严格预算(无重复的”重新发送代码”循环)。
这就是结构化JSON传递有帮助的地方:它使构建确定性、最小提取器变得更容易,而不是脆弱的HTML抓取。
示例伪代码(结构,非提供商特定)
目标是在不编造提供商详细信息的情况下显示控制流。
raw_body = getRawRequestBodyBytes(req)
sig = req.headers["Signature"]
timestamp = req.headers["Signature-Timestamp"]
if !sig or !timestamp:
return 401
if isTooOld(timestamp, toleranceSeconds=300):
return 401
expected = computeSignature(secret, timestamp, raw_body)
if !constantTimeEquals(sig, expected):
return 403
delivery_id = parseJson(raw_body).delivery_id
if seenRecently(delivery_id):
return 200 // 幂等接受
markSeen(delivery_id, ttl=24h)
enqueueForProcessing(raw_body)
return 200
要为Mailhook正确实现此功能(实际标头名称、签名字符串如何构造以及可用的ID),请遵循mailhook.co/llms.txt中的合约。
Mailhook的适用场景(不改变你的安全态势)
Mailhook专为邮件作为软件系统输入的工作流设计,包括LLM代理和QA自动化:
- 通过API创建一次性收件箱
- 以结构化JSON形式接收入站邮件
- 获得实时webhook通知(加上轮询作为后备)
- 使用签名webhook负载验证真实性
- 立即使用共享域名,或在需要更严格控制时引入自定义域名
如果你正在构建”邮件作为事件流”管道,关键要点是:将你的webhook视为公共API表面。在邮件层验证”邮件签名”是有帮助的,但验证webhook负载真实性是保护你自动化的关键。
有关实现详细信息、示例和规范集成合约,请从这里开始:Mailhook llms.txt。