Unknown 是错误还是结果?
当你开始将交互分类为 Human、Human + AI、Agent 或 Unknown 时,Unknown 分组往往比人们预期的更大。人们很容易把它当成一个需要隐藏的缺口,要么把 Unknown 并入 Human,要么把它当成可疑流量。两种做法都会带来问题。
Unknown 的意思是,现有 evidence 不足以支持可靠判断。来自不运行 JavaScript 的客户端的请求、对一个你没有收集任何信号的 endpoint 的调用、一个在单页后就结束的会话: 这些都可能完全合法,而且都不足以让你判断是谁或什么在执行操作。
如实报告 Unknown,才能让其他类别可信。如果你的系统从不返回 Unknown,那么每个 Human 结果都带有猜测成分,而你无法分辨哪一部分是猜测。我们关于 为什么 Unknown 在对 AI 流量分类时是有效结果的文章 更深入地说明了这一点。
本指南说明如何在实践中处理 Unknown: 明确的逐 route 回退、避免它泄漏到其他类别中的规则、如何监控它,以及如何在关键处降低它。
第 1 步: 永远不要默认归类为 Human
最常见的错误是,当分类函数没有找到任何自动化信号时就返回 Human。看起来合理,但这是错误的。没有 bot 信号,不等于有 human 信号。
// Wrong: silence becomes Human
function classifyWrong(e: Evidence) {
if (e.signatureValid) return { actor: "agent", basis: "verified" };
if (e.declaredAgent) return { actor: "agent", basis: "declared" };
return { actor: "human", basis: "inferred" };
}
// Right: Human requires positive evidence, otherwise Unknown
function classify(e: Evidence) {
if (e.signatureValid) return { actor: "agent", basis: "verified" };
if (e.declaredAgent) return { actor: "agent", basis: "declared" };
if (e.interactiveSession) return { actor: "human", basis: "inferred" };
return { actor: "unknown", basis: "unknown" };
}在所有产生结果的地方都应用同样的规则: 超时、错误、过期的缓存项和缺失的遥测都应产生 Unknown。为这些路径分别编写测试,这样行为就不会悄悄回退。下面的示例使用内置的 Node.js test runner。
import { test } from "node:test";
import assert from "node:assert/strict";
test("missing evidence is unknown, not human", () => {
const r = classify({ signatureValid: false, declaredAgent: false, interactiveSession: false });
assert.equal(r.actor, "unknown");
});第 2 步: 为每条 route 提供明确回退
Unknown 需要有定义好的行为,而正确行为取决于 route。按 route 决定,把它写下来,并保存在配置中。关于这些回退如何纳入更广泛的 policy 模型,参见 为人类和 AI agent 设计应用级 policy。
| Route 类型 | 示例 | Unknown 的合理回退 |
|---|---|---|
| 公共内容 | 营销页面、文档 | 允许,标准响应 |
| 只读数据 | 目录、搜索 API | 允许,使用标准速率限制 |
| 创建账户 | 注册 | 保留现有检查,可选人工复核队列 |
| 敏感变更 | 密码、邮箱、账单信息 | 现有重新认证,不放宽 |
| 高价值操作 | 批量导出、管理操作 | 现有强授权 |
模式很简单。对于低风险 route,Unknown 的表现就像普通流量。对于高风险 route,Unknown 应得到该 route 已有的同等保护。无论哪种情况,actor 结果都不会让一个未分类但合法的用户处境更糟。
type Fallback = "allow" | "standard_limit" | "existing_checks" | "review";
const unknownFallback: Record<string, Fallback> = {
"GET /docs/*": "allow",
"GET /api/catalog": "standard_limit",
"POST /signup": "existing_checks",
"POST /account/password": "existing_checks",
"POST /exports": "review",
};第 3 步: 将超时和错误以同样方式处理
Actor 评估位于请求路径上,因此必须有严格的时间预算。当预算被超出时,返回 Unknown,并应用该 route 的回退。绝不要因为评估太慢就阻塞请求。
async function withBudget<T>(work: Promise<T>, ms: number, onTimeout: T): Promise<T> {
let timer: NodeJS.Timeout;
const timeout = new Promise<T>((resolve) => {
timer = setTimeout(() => resolve(onTimeout), ms);
});
try {
return await Promise.race([work, timeout]);
} finally {
clearTimeout(timer!);
}
}
const result = await withBudget(evaluateActor(req), 150, { actor: "unknown", basis: "unknown" });记录结果为什么是 Unknown: 没有 evidence、超时、评估错误或过期 evidence。这些原因会告诉你要修什么。
第 4 步: 不要把 Unknown 当成可疑
Unknown 不是风险分数。许多合法客户端在某些 route 上总会是 Unknown: 命令行工具、没有签名的服务端集成、辅助功能工具、注重隐私的浏览器,以及简单的 feed 阅读器。即便是大型 AI 运营方,如今也主要通过 user agent 和公开的 IP 范围来标识其抓取器。例如,OpenAI 的爬虫文档(https://developers.openai.com/api/docs/bots) 截至 2026 年 11 月,为其每个 bot 列出了一个 user agent 字符串和一份公开的 IP 地址文件。
如果你想对可疑行为采取行动,应基于你能描述的行为,例如请求速率、登录失败模式或滥用报告,并使用它自己的 evidence。把这套逻辑与 actor class 分开,这样两者都不会互相扭曲。
第 5 步: 你如何监控 Unknown 率?
Unknown 率是被分类为 Unknown 的交互占比。按 route、按集成上下文以及按时间跟踪它。它是你拥有的最有用的健康指标之一。
type Row = { route: string; actor: string };
export function unknownRate(rows: Row[]) {
const byRoute = new Map<string, { total: number; unknown: number }>();
for (const r of rows) {
const s = byRoute.get(r.route) ?? { total: 0, unknown: 0 };
s.total += 1;
if (r.actor === "unknown") s.unknown += 1;
byRoute.set(r.route, s);
}
return [...byRoute].map(([route, s]) => ({ route, rate: s.unknown / s.total, total: s.total }));
}这些数字告诉你什么:
- 某条 route 上持续偏高的稳定比率 通常意味着那里缺少某个信号。一个从不加载浏览器集成的页面,或者一个从不看到签名请求的 API,通常会一直大多是 Unknown。
- 突然上升 往往意味着你的技术栈发生了变化,例如某个发布丢掉了一个 header、某条 CDN 规则移除了 signature headers,或者前端改动导致不再发送应用的声明。
- 突然下降 也值得检查。如果 Unknown 下降而 Human 上升,确认是新 evidence 到来,而不是回退悄悄改变了。
对剧烈变化发出告警,而不是对绝对水平发出告警。每个应用都有自己的正常状态。
第 6 步: 如何在关键处降低 Unknown?
你不需要把 Unknown 降到零。你需要在那些需要做决策的 route 上拥有足够的 evidence。按影响从高到低推进:
- 优先在高影响 route 上增加 evidence。 如果账户变更大多是 Unknown,那么 browser 集成或服务端检查最能体现价值。
- 邀请自动化客户端标识自己。 为你的 API 发布文档,说明 agent 如何使用 HTTP Message Signatures 和 IETF 的 Web Bot Auth 约定 对请求签名,并为已验证客户端提供更好的体验,例如更高限额或结构化响应。能够验证的客户端会开始这样做。
- 利用你自己的应用知识。 如果用户与内置助手交互,就把这记为声明。你的应用通常比网络知道得更多。
- 修好基础设施。 确保代理转发你依赖的 headers,并且评估有足够的时间预算完成。
如要实操了解哪些信号会把流量从 Unknown 中移出,请阅读 如何判断网站流量是否来自 AI agent。
第 7 步: 在仪表盘中如实展示 Unknown
将 Unknown 作为独立序列显示,并赋予与其他类别相同的视觉权重。不要把它隐藏到 "other" 分组中,也不要把它渲染成告警颜色。当利益相关者看到较大的 Unknown 占比时,正确反应应是 "我们需要更多 evidence 的地方在哪里",而不是 "bot 在哪里"。
第 8 步: 用结果而不是假设来复查 Unknown
每隔几周,从你最重要的 route 中抽取一部分 Unknown 交互样本,查看实际发生了什么。会话是完成了购买、创建了项目、阅读了三篇文章,还是在登录时反复失败?结果能告诉你这个分组里是什么类型的流量,而不会强行过早贴标签。
把你发现的内容分成三类实用类别:
- 合法且低风险。 Feed 阅读器、链接预览、监控工具,以及隐私设置更严格的人。保持 route 回退不变。
- 合法自动化,但本可以标识自己。 如果你提供结构化路径或签名请求,它们愿意使用的集成和 agent。发布指引,并在可行时主动联系。
- 需要自身控制的行为。 凭证填充或抓取模式,这两者都已在 OWASP Automated Threats to Web Applications 项目中归类。使用基于行为的控制来处理这些行为,这些控制应有自己的 evidence,例如登录失败率,并将其与 actor class 分开。
这次复查让你的回退建立在真实流量之上,并为下一步应在哪里增加 evidence 提供清晰清单。
常见问题
Unknown 应计入速率限制吗? 使用你的标准限制。按类别进行速率限制对已验证 agent 是可行的,它们可以获得更高限额,但 Unknown 应该获得正常限额,而不是惩罚性限额。
我们可以对 Unknown 用户进行 challenge 吗? 只有在该 route 本来就对处于那种情况的所有人进行 challenge 时才可以。仅仅因为结果是 Unknown 就加 challenge,会惩罚那些客户端发送更少信号的合法用户。
随着标准成熟,Unknown 会减少吗? 随着更多自动化客户端开始签名请求,更多 agent 流量会从 Unknown 或 declared 转为 verified。你的监控会按 route 显示这种变化。
Proof of Actor 在哪里发挥作用
Proof of Actor 会把 Unknown 作为一级结果报告出来,并给出原因,且绝不会把它转换为 Human。你的应用为每条 route 选择回退,而分析则显示覆盖终止的位置,这样你就能在关键处增加 evidence。
如需在生产应用上与我们的团队一起配置这一切,请 申请提前访问。要准确理解 evidence 能证明什么,请阅读 Detection & Limitations。
来源
- RFC 9421: HTTP Message Signatures: 自动化客户端可用于签名请求的标准
- draft-ietf-webbotauth-httpsig-protocol: IETF Web Bot Auth 草案,用于已签名的自动化流量(截至 2026 年 11 月仍在进行中)
- OpenAI: Overview of OpenAI Crawlers: OpenAI 的 bot 的 user agent 字符串和公开的 IP 地址文件
- OWASP Automated Threats to Web Applications: 凭证填充和抓取等自动化威胁的分类法
- Node.js test runner: 示例中使用的
node:test模块