Mohamed7415/fpverify
GitHub: Mohamed7415/fpverify
通过行为指纹检测 OpenAI 兼容 API 是否暗中替换了你付费的 LLM 模型,利用 LLM 无法真正随机的特性实现低成本审计。
Stars: 4 | Forks: 0
# fpverify — LLM API 的行为指纹检测
检查兼容 OpenAI 的 endpoint 实际提供的模型是否与其声明的一致。
[](LICENSE)
[](https://www.python.org/)
[](https://github.com/Mohamed7415/fpverify/actions/workflows/ci.yml)
[中文文档 →](README.zh-CN.md)
问题所在:API 分销商和中转服务可能会将你付费购买的旗舰模型悄悄替换为更便宜或量化后的模型。API 格式保持不变,响应中的 `model` 字段依然显示为旗舰模型的名称,因此在协议层面看不出任何破绽。
检测方法:LLM 无法产生真正的随机输出。当你要求一个模型“说出 1 到 100 之间的一个随机数”时,它的回答会高度集中——不同的模型会集中在不同的数值上。2026 年 7 月,我们抽样了 9 个前沿模型,每个模型各抽取 11 个全新实例;这 99 个回答中仅包含 4 个不同的值(参见[测量数据](#measurements-frontier-models-cannot-be-random))。针对几十个类似问题的回答分布会形成一个稳定的、特定于模型的签名。
fpverify 会向 endpoint 发送单 token 探针,将观察到的分布与参考指纹进行比较(Jensen-Shannon 散度),并通过序列博弈检验(e-process)做出判定。其错误率是受限的:在任何停止点,一个诚实的 endpoint 被判定为 FAIL 的概率始终 ≤ α = 0.01。

(真实运行,非预先编排:两个 endpoint 均运行在 127.0.0.1;作弊的 endpoint 声称提供 claude-sonnet-5,但实际提供的是更便宜的模型。可通过 `experiments/make_demo_gif.py` 重新生成。)
## 用法
以下命令使用 `python`;在 Windows 上请改用 `py -3.13 -X utf8`。
```
git clone https://github.com/Mohamed7415/fpverify
cd fpverify
pip install -r requirements.txt
```
### 场景 1:你只有中转服务的 API Key
启动本地 Web 控制台:
```
python -m webui.server
```
浏览器会打开 `http://127.0.0.1:8765`。填写以下三个字段:
| 字段 | 值 |
|---|---|
| Base URL | 中转地址,以 `/v1` 结尾 |
| API Key | 中转服务提供给你的密钥 |
| Model name | 点击“fetch model list”并从中转服务实际提供的列表中进行选择 |
将“library entry”保留为默认值(通过模型名称自动匹配)。点击开始。
请求计数 = 每个问题的样本数 × 库中的问题数 —— 在默认设置下通常为几十到约 300 个单 token 请求,只需花费几美分,几分钟即可完成。
判定结果:
| 判定 | 含义 |
|---|---|
| PASS | 在预算范围内没有发现模型被替换的证据 |
| FAIL | 行为与所声明模型的参考指纹存在显著偏差(误报概率 ≤ 0.01),或检测到响应级别的缓存 |
| BEST_MATCH | 声明的模型不在库中;报告其行为与库中的哪个模型相匹配 |
| UNKNOWN | 与库中的任何模型都不匹配 |
| INCONCLUSIVE | 证据不足;请增加样本量重新运行 |
在判定结果下方,控制台会显示一个自验证表格:包含所声明模型最具确定性的问题、它们的参考答案、可直接复制粘贴的提示词以及一个可下载的脚本。此验证过程不需要依赖本工具 —— 参见场景 2。
隐私说明:探针流量直接从你的机器发送到中转服务;密钥仅保留在本地进程内存中,绝不写入磁盘或上传到云端。指纹库是位于代码仓库内的公开数据;可以通过 `git pull` 进行更新。
CLI 等效命令:
```
python -m fpverify.cli library # list the fingerprint library
python -m fpverify.cli identify --base-url https://relay.example/v1 --api-key KEY --model gpt-5.6 --samples 8
```
识别能力会经历三个阶段的降级:声明的模型存在于库中时 → 执行序列检验得出判定(PASS / FAIL);不在库中时 → 报告行为最接近的匹配项(BEST_MATCH);找不到任何接近的匹配项时 → UNKNOWN。该库内置了在 2026 年 7 月测量的 9 个前沿模型(`cursor-harness` 通道,仅限同通道比较)。`api` 通道的参考指纹已开放给社区贡献 —— 注册一次只需几美分;防投毒规则详见 [`refs/README.md`](refs/README.md)。
### 场景 2:在不信任本工具的情况下验证判定结果
```
python -m fpverify.cli reproduce --claimed gpt-5.6-sol
```
导出该模型的复现包:即其参考行为最具确定性的问题,以及预期的答案(例如 GPT-5.6 sol:抛硬币 = 反面 11/11,随机颜色 = 橙色 11/11)。有以下四种运行方式:
1. 将 `cursor_prompt.md` 粘贴到 Cursor 或任何支持子智能体的 agent IDE 中;它会展开 N 个全新的子智能体(与 `cursor-harness` 参考库使用同一通道);
2. `codex_loop.sh` / `codex_loop.ps1` 会循环执行 `codex exec`,每次运行都是全新的会话;
3. `official_api.py`(仅使用标准库,零依赖)会抽样调用官方 API Key,并排打印出观察到的结果与参考结果;
4. 在官方网站上手动验证:每个问题开启一个全新的对话。
核心规则:每个样本都必须来自全新的对话或实例。在同一个对话中提问十次是无效的——因为模型会看到它之前的回答,从而刻意产生变化。
这种复现方式是双向验证的:既可以检验 FAIL 结果,也可以检验 PASS 结果。将相同的问题分别发送给官方网站(用于验证参考表)和你的中转服务(用于验证判定结果)。如果两边的结果都对得上,那么结论就不再依赖于我们了——`official_api.py --base-url` 可以指向上述任何一个 endpoint。
**如果你不信任的是我们** —— 比如你怀疑:“这个工具是收了中转商的钱,所以永远只会输出 PASS”:
- 判定结果完全是由你机器上的开源代码计算得出的,没有任何遥测数据。我们永远看不到你的 URL、密钥或结果,因此根本不存在针对每次运行进行篡改的渠道;任何作弊手段都必须存在于公开的代码中。
- 给它输入一个你明知是伪造的测试用例:使用场景 3 末尾的交叉审计自检(或者下面本地演示中的作弊 endpoint)。不匹配的运行必然判定为 FAIL。一个被篡改为永远通过的工具会立刻原形毕露;CI 流水线中也固定了相同的断言测试。
- `--report` 输出的 JSON 包含了受测 endpoint 的所有原始答案计数(`observed_counts`);结合你自己注册的参考文件,任何人都可以使用独立的代码重新计算并验证判定结果。
- 永远不存在任何付费的“认证”或供应商白名单机制。也没有任何供应商广告。
### 场景 3:你拥有官方 API Key
参考指纹将直接从官方通道进行注册采集。此过程不涉及任何共享库;这是最强大的证据模式。
```
# 1. Enroll a reference from the official API (~720 one-token requests, a few cents;
# re-enroll after model version bumps)
python -m fpverify.cli enroll \
--base-url https://api.openai.com/v1 --api-key $OFFICIAL_KEY \
--model gpt-5.6-sol --samples 20 --out ref_gpt56.json
# 2. Audit any OpenAI-compatible endpoint claiming to serve that model
python -m fpverify.cli audit \
--base-url https://some-relay.example/v1 --api-key $RELAY_KEY \
--model gpt-5.6-sol --ref ref_gpt56.json --report audit.json
```
对于明目张胆的模型替换,通常会在约 15 次查询内触发提前停止(成本约 $0.002)。
报告包含了底层论文中的聚合 JSD 值和参考区间(同源 = 0.140 / 跨部署 = 0.227 / 冒充者 = 0.463)。
包含两项自检,同时也可作为对本项目 FPR(误报率)声明的测试(仅需一个便宜的官方 API Key):
```
# Enroll model A from its official API
python -m fpverify.cli enroll --base-url https://api.deepseek.com/v1 \
--api-key $KEY --model deepseek-v4-pro --samples 20 --out ref_a.json
# Audit the SAME official endpoint against A's reference: must PASS
python -m fpverify.cli audit --base-url https://api.deepseek.com/v1 \
--api-key $KEY --model deepseek-v4-pro --ref ref_a.json
# Audit a DIFFERENT model against A's reference: must FAIL
python -m fpverify.cli audit --base-url https://api.deepseek.com/v1 \
--api-key $KEY --model deepseek-v4-flash --ref ref_a.json
```
如果一个直连的官方 endpoint 在与自身刚注册的参考指纹比对时被判定为 FAIL,请携带审计 JSON 提交 issue;这将推翻我们关于 FPR 的声明。
## 本地演示(无需任何 API Key)
本节用于验证工具本身;不涉及任何真实服务。
`sim/mock_server.py` 会在你的机器上启动假 endpoint:`--kind honest` 会从内置的模拟分布中给出答案,`--kind swap` 则用于模拟一个声称提供 claude-sonnet-5 但实际提供更便宜模型的中转服务。预期结果:前者会 PASS,后者会在约 15 次查询内 FAIL。
```
pip install httpx
python sim/mock_server.py --port 18801 --kind honest --model claude-sonnet-5 &
python sim/mock_server.py --port 18802 --kind swap --model claude-sonnet-5 &
python -m fpverify.cli enroll --base-url http://127.0.0.1:18801/v1 --api-key mock \
--model claude-sonnet-5 --out ref.json
python -m fpverify.cli audit --base-url http://127.0.0.1:18801/v1 --api-key mock \
--model claude-sonnet-5 --ref ref.json # PASS
python -m fpverify.cli audit --base-url http://127.0.0.1:18802/v1 --api-key mock \
--model claude-sonnet-5 --ref ref.json # FAIL
```
该模拟中转服务实现了九种对手策略(`--kind`):`honest / drift / quantized / swap / pin / filter_en / true_random / cache / partial_mimic`;详见 `sim/adversaries.py`。
## 测量数据:前沿模型无法做到随机
2026 年 7 月:测试了 9 个前沿模型,每个模型各抽取 11 个全新的独立实例(通过 Cursor 子智能体进行抽样,因此模型身份受到平台保证)。当被要求“说出 1 到 100 之间的一个随机数”时,99 个实例仅产生了 4 个不同的答案:73、47、37、42 —— 其中 73 占比为 65.7%。所有模型中,每个问题的中位数熵为 0.44 比特;而均匀随机分布的熵应为 6.64 比特。
如果你想亲自验证:打开一个全新的对话,让 Claude Fable 5 生成一个 1 到 100 之间的随机数。在我们的测试中,11 个全新实例中有 9 个回答了 73;而思维链变体在 11 次测试中全部回答了 73。
单一答案会发生碰撞(有五个模型的最常见回答都是 73);真正构成指纹的是这些分布的组合:
| 模型 (2026 年 7 月) | 随机数 1–100 (众数) | 颜色 | 动物 | 城市 | 抛硬币 |
|---|---|---|---|---|---|
| Claude Fable 5 | **73** (82%) | teal | otter | Kyoto | heads (100%) |
| Claude Fable 5 thinking | **73** (100%) | teal | otter | Kyoto | heads (100%) |
| Claude Sonnet 5 thinking | **37** (91%) | blue | elephant | Paris | heads (100%) |
| Claude Opus 4.8 thinking | **73** (100%) | blue | fox | Tokyo | heads (100%) |
| GPT-5.6 sol | **73** (91%) | orange | otter | Lisbon | tails (100%) |
| GPT-5.6 terra | **47** (36%) | teal | otter | Lisbon | tails (100%) |
| GLM-5.2 | **73** (91%) | teal | fox | Kyoto | heads (91%) |
| Composer 2.5 | **47** (100%) | purple | elephant | Tokyo | heads (91%) |
| Grok 4.5 | **73** (100%) | teal | otter | Lisbon | heads (45%) |

与审计相关的发现(完整分析见:[`docs/RESEARCH_NOTES.md`](docs/RESEARCH_NOTES.md) §7):
- 权重相同、推理模式不同 → 指纹相同。Fable 5 与其思维链变体对比:JSD 为 0.034,处于自噪声区间内。指纹是与权重绑定的;如果中转服务悄悄关闭了思维链模式,指纹是检测不到的,这需要依靠延迟侧信道来检测。
- 同系列的兄弟模型是可区分的。GPT-5.6 sol 与 terra:JSD 为 0.295,高于噪声区间的 p95 = 0.217(n=11,初步结果)。
- 家族聚类失效。Claude 家族内部平均距离为 0.393,而跨家族平均距离为 0.481 —— 两者处于同一量级。指纹追踪的是权重,而不是供应商。
- 语言是一个独立的维度。同一个模型在回答英文和中文探针时,其分布几乎是完全不相交的(JSD 0.605–1.0);在中文语境下,大多数模型会将出现频率最高的数字切换为 42。多语言探针能够增加区分度。
原始数据已提交至 `experiments/frontier/batch_*.json`;所有数值和图表均可通过固定随机种子重新生成:
```
python -X utf8 experiments/analyze_frontier.py
```
## 检测性能(受控模拟)
在已知真实情况的条件下,针对九种对手类型进行了有效性验证(α = 0.01,自动校准容差,预算 600 次查询;`experiments/run_evaluation.py`):
| 对手策略 | 检测率 | 得出判定的平均查询次数 | 预估成本* |
|---|---|---|---|
| 完全替换模型 | 100% | 15 | $0.002 |
| 固定答案 ("总是回答 73") | 100% | 14 | $0.002 |
| 真正随机伪装 | 100% | 14 | $0.002 |
| 过滤已知的探针话术 | 100% | 17 |0.003 |
| 部分分布模仿 | 100% | 17 | $0.003 |
| 响应缓存 | 100% | 89 | $0.014 |
| 量化降级 | 100% | 116 | $0.018 |
| 诚实 endpoint / 良性漂移 | 400 次审计中 0 次误报 | — | — |
\* 基于旗舰模型定价(输入 $2.5/百万 token,输出 $10/百万 token)。欺诈行为模仿真实分布的程度越高,所需的证据就越多;上述所有情况均在 $0.02 的预算内被捕获。
## 对抗性分析
在模拟环境中进行了四轮红/蓝军协同演化:由中转方提出一种规避策略,检测器进行反制,如此反复。经济学约束条件:中转方只有在真实流量上提供廉价模型才能获利,因此“在怀疑受到测试时提供旗舰模型”被视为零收益。完整记录见:[`docs/COEVOLUTION.md`](docs/COEVOLUTION.md)。
- 基于内容的路由(凡是类似探针的请求都转给旗舰模型):已被业务原生和功能拆分探针击败。中转方要么也将这些请求转给旗舰模型,要么提高其判定阈值,直到其廉价流量份额从 0.52 下降到 0.23。规避策略与盈利目标存在冲突。
- 基于账号的路由(对陌生账号提供旗舰模型):击败了一次性外部审计。应对措施:将探针混入真实账号的自然流量中,并随时间推移进行被动审计。
- 随机稀释(无视内容和账号,向占全部流量比例为 ε 的部分提供廉价模型):这是收敛点,也是统计学上的一个极限。任何要求 FPR ≤ α 的测试都必须保留一个宽度约为 δ 的容差区间,只要 ε 足够小,就能隐藏在其中。在本次模拟中,攻击者的安全窗口为 ε ≈ 0.20–0.28;收紧 δ 会将良性漂移的 FPR 推高至 0.092。捕获稀释率 ε 大约需要 ~1/ε² 个样本;唯一的突破口就是更大规模的注册采集和长期的样本积累。
总结:结构化的替换行为可以被低成本捕获;残余风险在于低频率的随机稀释,这本质上是“节省下的 ε 收益”与“审计方消耗的 ~1/ε² 样本成本”之间的一场博弈。而 anytime-valid(随时有效)的设计正是使得连续审计能够不断积累此类证据的关键所在。
## 工作原理
1. 探针:使用具有分类式单 token 答案的语义简单问题(例如“随机数 1–100”、“随机颜色”、抛硬币等),并采用多种表达方式和多种语言,以抵抗字符串匹配过滤。
2. 归一化:对答案进行标准化处理;将未见过的答案映射到一个 `OTHER` 桶中(采用 Good-Turing 缺失质量处理法)。
3. 比较:计算 endpoint 经验分布与参考分布在各个探针维度上的 Jensen-Shannon 散度,并进行聚合。
4. 判定:通过序列博弈 e-process 逐个查询地积累证据。具备 anytime-valid 特性:可以随时停止,对明显案例可提前停止,并将第一类错误保持在 ≤ α 的水平。
良性漂移容差 δ 是通过对每个参考模型进行 Dirichlet 后验预测模拟来自动校准的。
本项目基于《One Token Is Enough》(Bruckner,arXiv:2607.10252,2026)的研究成果,该研究在 165 个模型和 326k 次请求上确立了单 token 分布指纹。本项目在此基础上增加了序列 e-process 决策层(提前停止 + anytime-valid FPR 控制)、对抗性加固(多语言复述探针、缓存/延迟筛查)、自动校准以及上述前沿研究。
## 相关工具
| 工具 | 方法 | 判定类型 |
|---|---|---|
| [api-relay-audit](https://github.com/toby-bridges/api-relay-audit) | 安全扫描:注入、SSE 完整性、身份关键字 | 将替换视为“信号,而非确证” |
| [veridrop](https://github.com/canarybyte/veridrop) | 协议一致性 + Claude 思考特征(加密)+ 使用量字段取证 | 对 Claude 效果显著;其他方面仅限协议层面 |
| [RelayRadar (AI45Lab)](https://github.com/AI45Lab/RelayRadar) | 自适应判别提示 (AB3IT),TVD + 置换 p 值 | 固定样本假设检验 |
| [relay-radar (AetherCore)](https://github.com/AetherCore-Dev/relay-radar) | 被动风格监控 + LLMmap 探针 | 基于准确率的评分 |
| [zing](https://github.com/cenbonew/zing) | 能力/知识特征分析(上下文窗口、tokenizer、截断点) | 特征一致性检查 |
| [KBF (arXiv:2605.29524)](https://arxiv.org/abs/2605.29524) | 知识边界数字召回率 | 固定样本二项检验 |
fpverify 的不同之处在于:
1. anytime-valid 序列决策。e-process 在任何停止点都能保持 FPR ≤ α,从而实现了提前停止(对明显的替换行为约需 15 次查询)和持续低频率的被动审计——这是唯一能够在对抗账号级别的自适应路由时依然生存下来的机制(详见对抗性分析)。如果持续重复运行固定样本量的测试,其实际错误率会膨胀放大。
2. 自动校准的良性漂移容差(基于 Dirichlet 后验预测),取代了人工调整的阈值。
3. 一项始于 2026 年 7 月的前沿指纹研究,具备平台保证的模型身份、已公开原始数据、且支持一键复现。
4. 一项突破点分析,明确指出了检测失效的临界点(随机稀释率 ε ≈ 0.20–0.28;捕获 ε 需要消耗约 ~1/ε² 个样本),而不是暗示检测器是无敌的。
veridrop 的 Claude 思考特征检查属于加密级别手段,可作为互补;在审计 Claude endpoint 时,建议同时运行两者。而行为指纹是那个无需服务器端配合、适用于所有模型的底层检测手段。
## 项目结构
```
fpverify/ reusable library: probes, normalization, JSD, e-process, calibration, nearest-neighbor, library identify, reproduce packs, CLI
refs/ community reference fingerprint library (manifest + per-model distributions, contribution protocol)
webui/ local web console (stdlib server; keys never leave your machine)
sim/ red team: model distributions, adversaries, HTTP mock relay, traffic model, blue-team probes
experiments/ evaluation, frontier study, red/blue co-evolution (FPR, power, budget, distance matrices)
tests/ statistical property tests (fairness, FPR bound, power, end-to-end, co-evolution, library/identify, reproduce)
docs/ research notes (problem, threat model, method, experiments, frontier study, multimodal roadmap) + co-evolution ledger
```
## 路线图
核心决策逻辑(JSD + 序列 e-process + 校准)并不依赖于特定模态:只要嵌入图像/视频输出,将其量化到码本中,同样的机制即可适用。计划在 v2 版本中将模型替换检测扩展到图像/视频生成 API —— 并将固定随机种子的可复现性作为额外的辅助信号。设计文档见 [`docs/RESEARCH_NOTES.md`](docs/RESEARCH_NOTES.md) §8;目前尚未实现。
## 局限性
- 判定结果属于统计学证据,而非加密证明。FAIL 意味着分布与参考情况存在显著偏差;原因可能包括模型替换、量化、版本回滚或缓存。在得出结论之前,请妥善保留 JSON 报告并重新进行审计。
- 前沿指纹是在 Cursor 智能体框架内采样的(存在系统提示词,且未控制温度)。它们证明了模型行为的非随机性和可分性,但不能直接与裸 API 的数值进行比较;每个模型仅测试了 n=11 次,样本量较小,因此自噪声区间较宽。
- 权重相同情况下的模式切换(开启/关闭思维链)在指纹检测中是不可见的;要检测这种情况需要依赖延迟/长度侧信道。
- 能够在账号级别识别出审计流量的对手可以击败任何一次性的认证;对应的应对措施是进行持续的、低频率的、混合在真实流量中的审计。
- 无法防范能够完美复现目标模型完整条件分布的对手——但这样做的成本基本上等同于直接运行真实模型。
## License
MIT
标签:API验证, DLL 劫持, Python, 大语言模型, 无后门, 模型测伪, 行为指纹, 运行时操纵, 逆向工具