
对某个 OpenAI 兼容对话端点背后的服务技术栈进行指纹识别。
它记录 tokenizer 统计、隐藏模板偏移、验证文本、JSON/SSE 形态、角色接受情况、命名的限制以及网关响应头。它不加载权重、不存储 API 密钥,也不会直接询问模型“你是谁”。
| 导入 | lm_fingerprint |
| CLI | lm-fingerprint |
| 描述 | 针对 /chat/completions 的版本化基础设施探测。离线合成库。本地 Hugging Face tokenizer 验证(不加载权重)。 |
| 不是什么 | 不是梯度攻击。不是越狱。不是编码映射身份识别(tokprint)。不是“这个端口上运行的是哪个二进制”(Julius)。 |
仅限授权使用:你所拥有的端点、你控制的实验室、范围内的漏洞奖励计划、书面渗透测试。参见 SECURITY.md。
--api-key-env指定一个环境变量名。API 密钥永远不会被记录。
OpenAI 兼容的对话接口是一种常见的传输协议。vLLM、SGLang、实验室网关和转售商都能支持 /chat/completions。model 字段只是一个标签。主机可以更换后端、包装 tokenizer,或在前面放置一个路由器。当系统提示词改变时,人格探测也会跟着改变。
真正不变的是底层管道:服务器如何统计 token、它额外添加了多少隐藏模板 token、其验证器编写的文本、SSE 方言、接受哪些角色、当 max_tokens 设置得离谱时它给出的数字,以及 Server / 提供商的响应头。
这个工具要回答的问题正是:处理这条对话路径的是什么技术栈? 而不是哪个检查点、哪个进程绑定在端口上,也不是谁生成了某次补全。
ModelPrint 曾用九个探测在浏览器中提出过同样的问题。本包则是它的 CLI 和库形式:版本化探测、可登记库、成对证据、漂移,以及一条从不调用托管推理 API 的本地 tokenizer 路径。
指纹是探测 value 的带版本 JSON 记录。每个探测在相同技术栈被访问两次时必须返回相同的值。时间戳、请求 ID、延迟和密钥都会被去除。
Tokenizer 计数复用了 ModelPrint 1.0.0 的文本(MIT,unclecode/modelprint),字节完全一致,但减去了单个字符 "a" 的基线值,从而抵消隐藏模板的影响。这四个数字是编码映射 (T) 的一个投影。计数相等并不意味着 (T_1 \equiv T_2)。GPT-2 和 OPT 在这几个计数上会碰撞;而 rust 流水线哈希则不会。
hf_identity(仅限本地 HF 路径)是该流水线承诺加上聊天模板哈希的 SHA-256。相同的 (T) 和相同的模板会匹配(tiny-gpt2 / gpt2)。这是“家族 + 模板”,而不是检查点。
目录结构:
src/lm_fingerprint/
cli.py argparse. --api-key-env only. No --api-key.
client.py HttpTransport, InProcessTransport, redact_secrets
probes.py 23 ProbeSpec rows (tokenizer / errors / shape / roles / limits / proxy)
engine.py run suite → Fingerprint
schema.py schema_version=1, probe_suite_version=lm-fingerprint-probes-v1
similarity.py weighted exact-match, coverage, confidence, library rank, drift
library.py enroll / match. Packaged rows are synthetic fixtures
synthetic.py in-process stacks for tests and the shipped library
hf.py local AutoTokenizer + named validation profile. No weights
localmap.py encode-pipeline-v2 commitment (rust tokenizer.json)
proof.py measured ModelPrint 9-probe identity
report.py text reports
data/library.json
两条路径进入同一探测套件:
live endpoint local Hub tokenizer (optional [hf])
| |
HttpTransport.chat HfChatTransport
POST /chat/completions encode / chat_template for counts
| synthetic profile for errors/shape
+------------------+----------------------+
|
engine.run_probes
|
Fingerprint (JSON, keyless)
|
compare | match | drift | enroll
探测编写指南:docs/PROBES.md。模块地图:docs/ARCHITECTURE.md。
匹配结果表示共享基础设施。同一个实验室可以在一个技术栈上服务两个检查点。路由器可以用自己的错误包装器来应答。计数一致并不是 (T) 的同一性。
Python 3.10+。核心运行时基于标准库。
git clone https://gitlab.com/WattoCyber/lm-fingerprint.git
cd lm-fingerprint
pip install -e ".[dev]"
PYTHONPATH=src python3 scripts/repro.py
预期看到 REPRO_OK。该闸门完全离线:无需 GPU、无需生产 API。如果 tokenizer 未缓存,本地 HF 测试会跳过。
pip install -e ".[hf]" # transformers, for fingerprint-hf / verify-hf
lm-fingerprint list-probes
lm-fingerprint list
list 会打印随附的技术栈:openai-chat-strict、glm-compat、router-openai、permissive-compat。这些是测试夹具,而不是抓取到的提供商。无需网络即可比较其中两个:
from lm_fingerprint.client import InProcessTransport
from lm_fingerprint.engine import fingerprint_endpoint
from lm_fingerprint.similarity import compare_fingerprints
from lm_fingerprint.synthetic import SYNTHETIC_STACKS, handler_for
def fp(stack_id):
t = InProcessTransport(handler_for(stack_id), headers=SYNTHETIC_STACKS[stack_id].headers)
return fingerprint_endpoint(t, model=f"synthetic/{stack_id}", base_url=f"inprocess://{stack_id}")
print(compare_fingerprints(fp("openai-chat-strict"), fp("router-openai"))["note"])
词 tokenizer 相同,但错误和响应头不同。得分约为 0.58。ModelPrint 的四个计数一致;本工具不会将这种情况称为共享技术栈。
lm-fingerprint fingerprint \
--base-url https://your-lab.example/v1 \
--model the-label \
--api-key-env OPENAI_API_KEY \
--out lab.json
lm-fingerprint match --fingerprint lab.json
lm-fingerprint enroll --fingerprint lab.json --stack-id my-lab --library my-lib.json
不要通过命令行传递密钥。也不要抓取生产 API 来填充公共库。
lm-fingerprint fingerprint-hf --model sshleifer/tiny-gpt2 --local-files-only
lm-fingerprint verify-hf --local-files-only
当模板存在时,计数来自 apply_chat_template;否则来自 encode(..., add_special_tokens=False)。错误分类来自一个命名的合成配置文件(默认 openai-chat-strict),以保证运行保持离线。远程句柄(https://、api://、……)会被拒绝。
lm-fingerprint list-probes
lm-fingerprint list [--library PATH]
lm-fingerprint fingerprint --base-url URL --model MODEL --api-key-env VAR [--out PATH]
lm-fingerprint fingerprint-hf --model HANDLE [--profile NAME] [--local-files-only]
lm-fingerprint verify-hf [--models a,b,c] [--local-files-only]
lm-fingerprint prove [--local-files-only]
lm-fingerprint compare --a a.json --b b.json
lm-fingerprint match --fingerprint a.json [--library PATH]
lm-fingerprint drift --baseline old.json --current new.json
lm-fingerprint enroll --fingerprint a.json --stack-id ID --library PATH
lm-fingerprint export --fingerprint a.json
没有 --api-key 标志。较旧的 wireprint-fingerprint / stackprint-fingerprint JSON 仍然可以加载。
from lm_fingerprint import (
StackLibrary,
compare_fingerprints,
fingerprint_endpoint,
fingerprint_hf,
match_library,
)
from lm_fingerprint.client import HttpTransport
fp = fingerprint_endpoint(HttpTransport(url, api_key=key), model="x", base_url=url)
hit = match_library(fp, StackLibrary.load())
local = fingerprint_hf("sshleifer/tiny-gpt2", local_files_only=True)
schema_version = 1,probe_suite_version = lm-fingerprint-probes-v1。
比较方式:对两侧均为 ok 且 stable 的探测进行加权精确匹配。置信度 = score × coverage。库匹配会报告 certainty(exact / strong / possible / unknown)以及与第二近邻的差距。
重新运行:
lm-fingerprint prove --local-files-only
与 ModelPrint 相同的探测残留(probes/index.js 中的九个探测)。他们的 README 就是评分规则:匹配的指纹证明共享基础设施,而非同一性。
详细说明:docs/PROOF.md。
fingerprint-hf 从不加载权重,也从不调用托管的推理服务。--api-key-env。产物不得包含 sk- 或 Authorization。scripts/repro.py 是产品闸门。除非你刚刚运行过它,否则不要宣称绿灯。MIT。参见 LICENSE。ModelPrint 的 tokenizer 文本仍为 MIT(unclecode/modelprint),且字节完全一致。
| 你掌握 | 你能观察到 | 这个工具做什么 |
|---|
| 一个经授权的 OpenAI 兼容 base URL + 密钥 | 对话 HTTP:用量、错误、响应头、SSE | fingerprint / compare / match / drift |
| 本地 tokenizer 文件 | encode 和 chat_template | fingerprint-hf / verify-hf。权重保留在磁盘上 |
| 只有一个 host:port | Banner / /health / /api/tags | 超出范围。请使用 Julius |
| 字段 | 含义 |
|---|
kind | lm-fingerprint |
target.model / target.base_url_host | 指向的目标。仅主机;不含密钥 |
features.tokenizer_norm | 四个 ModelPrint 类计数(英语、中文、代码、表情符号) |
features.template_offset | 隐藏的服务模板大小 |
probes.<id>.value | 可比较的单元格。必须稳定 |
probes.<id>.weight | 对相似度的贡献 |
keys_stored | 始终为 false |
weights_loaded | 始终为 false |
| 测试 | ModelPrint | 本工具 |
|---|
相同 tokenizer、不同技术栈(openai-chat-strict vs router-openai) | tokenizer 4/4 + 模板匹配 | 得分 0.58,标记为共享统计 |
gpt2 vs facebook/opt-125m | 四个计数和模板都碰撞 | encode_commitment 区分 |
| Qwen2 / 2.5 / 3 | tokenizer 4/4 碰撞;模板区分 | hf_identity 区分 |
tiny-gpt2 vs gpt2 | 匹配(相同 (T)) | 匹配(必需) |
| 探测集 | 9 | 23 + HF 编码/模板承诺 |
logprobs、流形状、系统角色 | 作为想法列出 | 已随附 |
| 工具 | 用途 |
|---|
| ModelPrint | 相同的探测残留,浏览器,九个探测 |
| tokprint | 本地编码映射家族 ID。位于 gradient-untangler |
| gradient-untangler | 通过本地权重计算梯度 |
| Julius | 端口上运行的是哪个服务器软件 |
| TokenPrint / LLMmap / UTF | 生成文本、谱系或植入的 token |