Skip to content

Repository files navigation

English | 简体中文

命中即证明吸收;沉默不等于清白

DraftCanary

在保密草稿里埋入金丝雀,对大模型端点做单侧训练吸收审计

CI License: MIT Python 3.12+ code style: ruff

你把未发表的草稿喂给了云端大模型,厂商承诺「不用于训练」,却拿不出任何证据。 DraftCanary 在草稿里埋入独一无二的高熵金丝雀,并向 DeepSeek / Qwen / GLM / Kimi 周期性发起提取探测——把「我的草稿有没有被吸收进训练集」从口头承诺, 变成你自己持有、可追加、可复核的证据链。

单进程 Python CLI · 无服务、无守护进程 · 账本与报告全本地

单侧检测是科学边界:命中(hit)即证明该端点吸收过你的金丝雀; 沉默(quiet)只说明该次探测预算下未观测到提取信号,不构成「未吸收」或「安全」的证明。 DraftCanary 永远不出具清白证书——所有判定、报告与措辞都遵循这条边界。

为什么需要它

把整份草稿交给云端助手迭代,已经是科研与长文写作的常规工作流。问题是:你没有任何办法 验证「不用于训练」的承诺——条款可以承诺,但可验证的凭证在结构上就不可能由厂商单方面提供, 自我审计没有动机。现有的三个替代方案各有结构性缺陷:

方案 给你什么 结构性缺陷
厂商条款承诺 / 退出开关 法律预期 拿不出任何凭证,无法被检验
敏感稿只用本地模型 物理消除外泄路径 放弃前沿能力——艰难推理恰恰发生在云端
脱敏网关 / 占位符替换 拦截标识符与密钥 剥离的正是需要助手帮忙的推导、代码与结果
DraftCanary 金丝雀审计 可伪造检验的逐厂商信号 单侧:命中即吸收,沉默不等于清白

机制是经典的提取审计(canary / extraction probing)思路,但面向的是真实工作流: 金丝雀埋在你本来就要发出去的草稿里,探测在你本来就要用的端点上跑, 证据链由你持有——护城河在持续累积的账本数据,不在 CLI 本身。

快速开始

git clone https://github.com/SuperMarioYL/draftcanary.git
cd draftcanary
uv tool install .        # 或 pipx install . / pip install -e .

第一步:零 API key 的确定性命中演示(约 60 秒)

draftcanary demo

生成 12 枚高熵金丝雀埋入示例科研草稿 → 启动本地 mock「已污染」端点 → 自动运行 logprob 提取探测(孪生对照)→ 终端打出命中报告。全程离线。

第二步:埋入自己的草稿

draftcanary embed examples/research_draft.md   # 或你的草稿
# 产出 examples/research_draft.canary.md + ledger.jsonl

之后只把金丝雀版本(而非原始草稿)发给云端助手——这是人工步骤,工具不接管流量。

第三步(离线走查):对本地污染端点复现完整闭环

draftcanary probe --provider mock
draftcanary report

第四步:对真实端点探测(需要该厂商的 API key)

export DEEPSEEK_API_KEY=sk-…
draftcanary probe --provider deepseek --max-usd 0.5
draftcanary report --format markdown --output audit-report.md
端点 模型 API key 环境变量 获取地址
DeepSeek deepseek-flash DEEPSEEK_API_KEY https://platform.deepseek.com
Qwen(百炼兼容模式) qwen-plus DASHSCOPE_API_KEY https://bailian.console.aliyun.com
GLM(智谱) glm-5 ZHIPU_API_KEY https://open.bigmodel.cn
Kimi(月之暗面) kimi-k3 MOONSHOT_API_KEY https://platform.moonshot.cn

自定义 OpenAI 兼容端点不受限:draftcanary probe --provider deepseek --base-url https://你的网关/v1。

演示

draftcanary demo 的完整记录(真实终端录制,可由 vhs docs/demo.tape 重新渲染):

DraftCanary 演示:embed 埋入示例草稿 → mock 污染端点探测 → 命中报告

终端里的内容:12 枚金丝雀全部命中(分数 0.951 · 孪生对照 0.000 · 分离度 +0.951), 随后是 per-provider 审计时间线与单枚金丝雀的证据面板——嵌入句原文高亮、logprob 路径、与孪生对照的分离度。mock 端点模拟「草稿已被吸收进训练集」的最坏情形, 仅为机制演示,不构成对任何真实厂商的证据。

架构

单进程 Python CLI;无服务、无数据库、无守护进程,全部状态在本地 JSONL 账本:

flowchart TB
    draft["原始草稿"] -->|"draftcanary embed 埋入金丝雀"| canaried["金丝雀版本 + 账本记录"]
    canaried -->|"人工步骤:发给云端助手"| cloud["DeepSeek / Qwen / GLM / Kimi(OpenAI 兼容端点)"]
    ledger[("ledger.jsonl 追加账本")]
    canaried --> ledger
    cloud -->|"draftcanary probe 提取探测"| verdict["hit / quiet 判定"]
    verdict --> ledger
    ledger -->|"draftcanary report 渲染"| report["markdown / json / rich 审计报告"]
Loading
模块 职责
draftcanary/cli.py Typer 入口:embed / probe / report / demo
draftcanary/canary.py 高熵可读金丝雀生成 + 草稿嵌入 + 孪生派生
draftcanary/probe.py 探测核心:logprob / 续写双方法 + 孪生对照比较 + 预算墙
draftcanary/providers.py 端点注册表:四厂商 + mock(全 OpenAI 兼容)
draftcanary/ledger.py JSONL 追加账本(Canary | ProbeRecord)
draftcanary/report.py markdown / json / rich 三形态报告 + 证据高亮
draftcanary/mockserver.py 本地污染 / 静默 mock 端点(demo 与测试夹具)
experiments/ m1 本地污染实验与 m3 端点校准脚本,复用探测核心

孪生对照是判定的核心:每枚已提交金丝雀都有一枚从未提交的孪生串(同熵、同形状、 由金丝雀确定性派生),与金丝雀在同一端点、同一调度、逐字节相同的提示词下并排探测。 判定只看两者分数相对该端点噪声底的分离度——任何绝对分数都不构成结论。

方法按端点能力路由(四家真实 API 中仅两家的 chat completions 接受 logprobs 参数):

端点 方法 原因
DeepSeek / Kimi logprob 请求 schema 支持 logprobs / top_logprobs
Qwen 兼容模式 / GLM 续写 continuation schema 不含 logprobs 参数,固定回退
mock / 本地 checkpoint logprob 不受真实端点 schema 限制(demo 与实验用)

能力与边界

已实现(v0.1.0):

  • embed:12 枚(可调)80-bit 金丝雀,自然语句嵌入,原始草稿逐字保留并记录 sha256 指纹
  • probe:四厂商 + mock 适配、孪生对照、--max-usd 预算墙(全次运行共享)、方法自动路由
  • report:per-provider 命中时间线、单条证据、下次探测预算建议(markdown / json / rich)
  • demo:零 key、全离线、确定性命中
  • 账本:append-only JSONL,记录类型与计划的数据模型逐字段一致
  • 实验脚本:experiments/validate_local_poison.py(LoRA 污染开源小模型制造 ground truth, 产出「暴露次数 × 探测预算 → 检出率」曲线)、experiments/calibrate_endpoints.py (正对照 = 已知公开文本、负对照 = 全新金丝雀,校准各端点噪声底)

明确的边界(v0.1 不做,其中前两条是设计选择而非待办):

  • 永远不出具「未吸收」证明或合规证书——单侧检测是科学边界,quiet 不是清白
  • 不拦截、不代理与助手的对话——只产出金丝雀草稿文件与探测记录
  • 吸收不等于可提取:单枚草稿的金丝雀在有限预算下可能检不出来。检出率随暴露次数与 探测预算上升——这正是 m1 实验脚本要量化的曲线,请在解读 quiet 时记住这一点
  • 对 mock 端点的所有记录都是演示夹具,与任何真实厂商无关
  • 仅 DeepSeek / Qwen / GLM / Kimi 一等预设(自定义 --base-url 不受限); 仅对话补全端点;仅 CLI;单机单用户

配置

常用选项(draftcanary <命令> --help 查看全部):

选项 作用于 默认 说明
--max-usd probe 0.5 本次运行的总预算墙(USD),每轮探测前检查
--attempts probe 1 每枚金丝雀的探测轮数(每轮含孪生对照;首轮贪心,其后采样)
--noise-floor probe 0.5 hit 判定所需的最小分离度;校准值见 experiments/calibrate_endpoints.py
--provider probe 必填 deepseek / qwen / glm / kimi / mock
--base-url / --model / --api-key probe 注册表值 覆盖端点地址、模型 id、key 来源
--count embed 12 埋入的金丝雀数量
--ledger 全部 ./ledger.jsonl 账本路径
--format / --output report terminal terminal / markdown / json,后两者可写文件

预算与定价:--max-usd 按 draftcanary/providers.py 内置单价估算,默认值是构建时的 保守偏高参考值(USD / 1M tokens,预算墙宁可提前停、不可漏),需要精确对账时请按 各厂商当前价目表更新。无定价端点(mock、本地)不启用 USD 计价。

API key 通过各厂商的环境变量注入(见快速开始表格),--api-key 可临时覆盖; key 永远只存在于本机进程内,账本与报告不记录 key。

使用注意:提取探测是自动化请求——请遵守各厂商 API 条款与速率限制,预算墙与 --attempts 是控制用量的第一道闸。账本(ledger.jsonl)与金丝雀版本草稿 (*.canary.md)包含你的金丝雀 token,不要提交到公开仓库(.gitignore 已默认排除)。

路线图

  • m1 探测核心:logprob / 续写双方法 + 孪生对照 + 预算墙
  • m2 审计 CLI:embed / probe / report / demo、四厂商适配、JSONL 账本
  • m1 实验结果表:在开源 Qwen 小模型上运行 experiments/validate_local_poison.py, 提交「暴露次数 × 预算 → 检出率」的 detection-budget 曲线
  • m3 端点校准与结果表:预算封顶运行 experiments/calibrate_endpoints.py, 校准四端点噪声底与静默基线,结果表随账本周期性更新
  • PyPI 发布(release workflow 已就绪,trusted publishing 待一次性配置)
  • 更多端点一等预设(--base-url 现已可用;欢迎 issue 表态)

许可

MIT。审计工具按「现状」提供:它产出的是可复核的证据记录, 不是法律意见,也不是任何形式的合规认证。

MIT © 2026 SuperMarioYL

About

帮科研与长文作者检测保密草稿是否被国产大模型用于训练的开源审计工具

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages