falsepass

falsepass#

检查强化学习(RL)环境的判分器(verifier)有没有两类错误:假通过(明知是错的回答拿到了奖励)和假失败(明知是对的回答没拿到满分)。

举个真实的例子:在一个公开的环境里,把题目原样抄回去,每道题都能拿满分。原因是每道题都列出了全部候选答案,而判分器只检查正确答案有没有出现在回复里的某个位置。所以只要把所有答案都写上,就能拿满分。falsepass 能在训练之前发现这类漏洞。

一个 RL 环境由任务数据集和判分器组成,判分器把模型的输出换算成奖励。falsepass 把判分器本身当作被测对象:用环境自己的奖励代码,去给一批事先已知对错的回答打分,然后报告每一处判分和事实不符的地方。不需要模型,不需要真的跑训练,也不需要 GPU。

支持三个框架,已发布的或还在本地文件夹里的都可以:

框架 falsepass 实际运行的是 falsepass init …
verifiers(Prime Intellect 的 Environments Hub),三代接口都支持 环境自己的 rubric 或 taskset owner/environment,或 ./文件夹
Inspect AI 任务自己的 solver 链(用按脚本回答的替身代替模型),再用它自己的 scorer 打分 path/to/task.py、./项目,或 inspect_evals/gsm8k
HUD 任务模板自己的判分代码,调用方式和 HUD 服务器一样 path/to/环境文件夹(加 --answers refs.json 可跑全部测试)

安装#

需要先装好 uv(每次审计都在一个用完即弃的虚拟环境里运行),以及 Python 3.11 或更新版本。

uvx falsepass demo              # 直接试用,不用安装
uv tool install falsepass       # 或者装成常驻命令 `falsepass`
pip install falsepass           # 或者装进现有的 Python 环境
uvx --from git+https://github.com/Super-Eric/falsepass falsepass --version   # 还没发布的 main 分支

快速上手#

falsepass demo                            # 审计一个自带的小环境,里面故意埋了一个漏洞(约一分钟)
falsepass init owner/environment          # Hub 上的环境:包名、版本和 verifiers 版本自动从 Hub 读取
falsepass init ./my-environment           # 或者你自己的环境,指向它的文件夹(含 pyproject.toml);每次运行都用最新代码
falsepass audit my-environment            # 开始检查;结果在 audits/my-environment/summary.md
falsepass results my-environment --json   # 核心指标和没通过的测试,给脚本和 AI 代理读

demo 会把小环境写到 ./falsepass-demo 再审计。这个环境的判分只看"标准答案有没有出现在回答里的某个位置",结果摘要会从几个方向指出这一行代码:静态扫描直接定位到它;"一次列出多个答案"(P5)、"给出答案后又收回"(P9)、"在答案框里否定答案"(P15)这几类回答都拿到了奖励。下面是真实输出的精简节选(完整文件:examples/toy-env/summary.md):

- False-pass ceiling: 1 of 1 (highest reward given to a known-bad completion; via P5 shotgun/all_in_one_slot)
- Lazy-policy reward: 0 (best average reward of one fixed strategy that never uses the reference answer; via P3 empty/empty)
- False-fail rate: 0.108 (93 known-good completions)

| Probe              | Variant                | Expect | Runs | Failed | Score min / mean / max |
| P5 shotgun         | all_in_one_slot        | zero   | 10   | 10     | 1 / 1 / 1              |
| P9 oversized       | correct_then_retracted | zero   | 10   | 10     | 1 / 1 / 1              |
| P15 hedged_in_slot | denied                 | zero   | 10   | 10     | 1 / 1 / 1              |

Static hint: falsepass_toy_env.py:26 reference_substring_match
    return 1.0 if answer in reply else 0.0
    confirmed: P9 failed 10/20, P5 failed 10/30

意思是:错误回答拿到的最高分是满分(1 分里拿了 1 分);"一次列出所有答案"、"给出答案又收回"、"在答案框里否定答案"三类回答,10 次全部拿了满分;静态扫描指出的第 26 行,被这些测试结果证实了。

Inspect AI 和 HUD#

falsepass init inspect_evals/gsm8k                     # inspect_evals 里的任务(版本从 PyPI 固定)
falsepass init my_eval.py --task hard_split            # 任务文件;有多个任务时用 --task 选一个
falsepass init ./my-hud-env                            # HUD 环境文件夹(env.py + tasks.py)
falsepass init ./my-hud-env --answers refs.json        # 提供标准答案:跑全部测试

Inspect:替身只代替调用 generate() 的那一步;提示模板、系统消息、答案解析(multiple_choice())都是任务自己的代码。如果 solver 或 scorer 自己去调用模型(自我批评、model_graded_qa),每一行都会被标记出来,而不是给出一个不可信的分数。需要沙箱的任务暂不支持。

HUD:任务模板把标准答案藏在判分代码里,所以不加 --answers 时只跑不需要标准答案的测试:空回答、原样抄回题目,以及一次列出 0 到 1000 所有整数、所有字母、yes/no 之类的词或题目里所有词的回答(A1;提供答案时它也会和其他测试一起跑)。如果这些回答能拿分,判分实际上就是在做子串匹配。答案文件是 JSON:要么是"任务 slug → 答案"的对象({"add-2-3": "5"}),要么是按任务顺序排列的列表(["5", "12"])。要给已经建好的审计补上答案,带 --force 重新运行 init。examples/inspect-toy/ 和 examples/hud-toy/ 各埋了一个漏洞,可以拿来试。

给环境作者:发布前先检查#

在本地运行 falsepass init ./your-env && falsepass audit your-env,或者在环境的仓库里加上 GitHub Action,每次提交都自动检查一遍:

# .github/workflows/falsepass.yml
on: [push, pull_request]
jobs:
  falsepass:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: Super-Eric/falsepass@main
        with:
          path: .                         # 环境的 pyproject.toml 所在的文件夹
          fail-on: "ceiling>0.1 errors>0" # 错误回答得分超过 0.1,或判分代码报错时,标记为失败

检查结果的表格会显示在这次运行的摘要页里。可用的判定条件有:ceiling(错误回答拿到的最高分)、lazy(一个从不看题、每次都给同样回答的策略平均能拿多少分)、false_fail(正确回答被拒的比例)、errors(判分代码报错的次数)。如果加载数据集需要登录或 API 密钥,在 CI 里必须配置相应的密钥才能审计。

给 AI 编程代理#

  • MCP 服务:falsepass mcp 通过 stdio 把各个命令提供为工具(falsepass_scan、_init、_audit、_audit_status、_results、_completion、_repro、_discover)。审计在后台运行,客户端单次调用的超时不会把它打断;出错时以工具错误返回,并写明原因。任何 MCP 客户端都可以用这条命令启动它:uvx --from "falsepass[mcp]" falsepass mcp。以 Claude Code 为例:claude mcp add falsepass -- uvx --from "falsepass[mcp]" falsepass mcp。
  • Skill:skills/falsepass/SKILL.md 是一份写好的审计流程(Agent Skills 格式),内容包括怎么运行审计、怎么区分漏洞和有意的设计、怎么写给维护者。把这个文件夹复制到代理的 skills 目录即可(Claude Code 是 ~/.claude/skills/)。
  • 普通命令行:所有命令都不需要交互,结果写成文件;init --json、results --json 和 scan --json 输出 JSON;results --completion ID 输出某个回答的全文;audit --fail-on ... 在条件满足时以退出码 1 结束。输出格式见 docs/OUTPUT.md(英文)。

不管由谁来运行,输出都只是一份线索清单。代理应当先复现线索、读过奖励代码,再判断是不是漏洞;不能只凭一张测试表格就说某个环境有问题。

全部命令#

falsepass init owner/environment          # 为 Hub 上的环境建好 audits/<name>/,也可以传本地文件夹
falsepass audit environment               # 问答类任务不需要写适配器:答案格式会自动推断
falsepass audit environment --search 120  # 加上变异搜索(每个方向、每道题调用判分器的次数)
falsepass audit environment --fail-on 'ceiling>0.1' --fail-on 'errors>0'   # CI 门槛,满足时退出码为 1
falsepass results environment [--json]    # 上一次的结果:summary.md,或精简的 JSON
falsepass results environment --completion ID   # 某个回答的全文(ID 在 JSON 摘要里)
falsepass scan path/to/source [--json]    # 只做静态扫描,不安装也不运行任何东西
falsepass discover --days 90 --limit 160  # 最近有人维护的 Hub 环境,以及每个需要什么条件
falsepass repro environment               # 生成独立脚本,给那些没通过的回答重新打分
falsepass rollout environment -n 20       # 看本地模型(Ollama)自己答题实际拿多少分
falsepass propose environment -n 5        # 让本地模型写错误回答,并根据得分反复改写
falsepass draft environment               # REPORT.draft.md:待人工审核的候选问题
falsepass overview -o RESULTS.md          # 把磁盘上所有审计结果汇成一张表
falsepass container environment           # 容器类任务:把标准答案及其变体写成输出文件(需要 Docker)
falsepass upstream environment            # 在 GitHub 上找到环境的源码仓库,和审计的版本逐个文件对比
falsepass verify-fix environment --source ../patched/pkg   # 用打过补丁的代码重跑复现脚本,看哪几行结果变了
falsepass demo                            # 上面那个小环境
falsepass mcp                             # MCP 服务,走 stdio(需要 `falsepass[mcp]`)

每次审计都在一个由 audits/<env>/env.toml 固定版本的临时虚拟环境里运行,因为 Hub 上的环境依赖的 verifiers 版本互不兼容。输出文件是 audits/<env>/probe-results.json 和 summary.md。如果是在本仓库的克隆里使用,命令前要加 uv run;uv run pytest 运行工具自身的测试。

测试项目#

编号 测试内容 正确的判分器应该…
P1 标准答案,按要求的格式 给满分
P2 同一个答案,加上无害的变化 给满分
P3 空回答 / 只有空白 不给分
P4 把题目原样抄回去 不给分
P5 一次列出所有候选答案 不给分
P6 错误答案,格式完全正确 不给分
P7 语气很肯定的错误答案 不给分
P8 错误答案,加一句写给评委的话(用大模型当评委的环境) 不给分
P9 正确答案埋在一堆废话里,随后又收回 不给分
P10 格式损坏的输出 不给分,也不报错
P11 同一个回答打分十次 每次分数相同
P12 数据集:空题目、重复题目、相互矛盾的标签 一个都没有
P13 NaN、Infinity、整数答案加 0.9(JSON 答案:小数改成 NaN,整数加 0.9 或改成 Infinity) 不给分,也不报错
P14 正确答案,但在正文里答案前后又提到了答案标签或 \boxed{} 给满分
P15 在答案框里否定答案,或把答案和错误值并列(\boxed{R \text{ is wrong}}、\boxed{W = R}) 不给分
A1 不需要标准答案:一次列出 0–1000 所有整数、所有字母、yes/no 之类的词,或题目里所有的词 不给分
T7 先调用一次无实际作用的工具,再给出正确答案 给满分(如果环境本身不提供工具,这一项需要人来判断)
C0–C2 容器类任务:把标准答案直接写成输出文件(C0),数字改成 NaN / Infinity / 小数计数(C1),清空、截断或换成输入文件(C2) C0 给满分,所有变体都不给满分
T4 多轮:先跑一个无关的对话,再重放同一个对话 奖励和对话记录都相同
T5 多轮:一个永远不作答的策略 被中途截断,不给分
T8 多轮:先答错,再把环境泄露出来的内容(如"The word was …")原样答回去 不给分;如果环境宣布结束后回合还在继续,也会注明
T6 工具类:只根据工具定义构造的 6 种策略(不调用工具、每个工具空参数调用一次、直接调用结束工具……) 不给分,也不报错
X*、T1–T3 在适配器里定义的环境专用测试 —
S1 搜索:对已知错误的回答做变异(不加入答案),往高分方向找 找不到高于 0 的
S2 搜索:对正确回答做变异(不改答案),往低分方向找 找不到低于满分的
S3 搜索:提到答案但不明确认定它的回答 找不到高于 0 的

搜索(--search N)是带固定随机种子的束搜索,在一串串命名好的小变异上进行。命中的结果会化简成最短的组合报告出来,例如 nothing + pad_think。S2 的命中只说明判分可能过于死板,需要人来看:比如带引号的 "B" 算不算 B,属于判断问题。

静态扫描在每次审计时对 audits/<env>/src 运行,也可以用 falsepass scan 单独运行。规则都是范围很窄的 AST 规则,每一条都来自一次真实审计中发现的问题;每条提示旁边都会标出相应测试的结论(confirmed: P6 failed 25/25、not confirmed、not checked)。

自动推断适配器:没有 adapter.py 时,工具会把标准答案写成各种候选格式(从解析器和题目文字里找),用环境自己的奖励代码打分,取得分最高的那种格式。错误答案从标签集合里取,或者把数字偏移一下,或者用另一道题的答案。这适用于有标准答案的环境,单轮多轮都行;工具类、沙箱类环境,以及数据里没有答案的游戏,需要写策略:falsepass init --policies 会生成一个骨架(问答类用 --manual)。题目中用来包住输入内容的标签(如 <conversation>…</conversation>)不会被当成答案格式;如果所有格式得分相同,就用不加格式的答案。作为对照,自动模式会比较标准答案和空回答的得分;如果奖励区分不出两者,整个结果标记为无法判断(inconclusive)。

多轮:多轮环境的打分方式是运行它自己的对话循环,用一个按脚本回答的替身代替模型(V0RolloutScorer),所以追问、停止条件和每轮的状态都会被真实执行。搜索会对脚本的每一轮都做变异。脚本里的一条也可以包含工具调用({"tool_calls": [{"name": …, "arguments": {…}}]}),由环境用真实的工具执行。自动模式也支持工具在本进程内运行的工具类环境:这类环境没有答案格式可以推断,所以会运行 6 种 T6 策略和 T5,并在摘要里说明无法做对照。需要读懂题目才能找到漏洞的策略,仍然要手写(falsepass.model.Policy:一个根据目前的对话给出下一句回复的函数)。

真实模型对照:falsepass rollout <env> 把一部分题目发给一个 OpenAI 兼容接口背后的模型(默认是本地的 Ollama,不花钱),再用测试所用的同一个打分器给模型自己的回答打分。它会标出值得看的行(回答里没有标准答案却拿了分;有标准答案却没拿满分;得分比标准答案还高;回答被长度上限截断),但从不把这些标记称为问题。工具类环境通过 verifiers 自己的客户端驱动模型,所以模型能收到工具定义,它的工具调用也会被执行。用 --endpoint、--model 和 --tag 可以再加一个模型对比。

模型写的错误答案:falsepass propose <env> 让模型写一个切题但错误的回答,打分后把分数反馈给模型,让它改写几次。对于有固定答案的题目,回答里不能出现标准答案(代码里会检查);对于开放式题目,"错误"只是模型自己的判断,所以每一行都只是值得读的材料,不是结论。

发现环境:falsepass discover 列出 Hub 上最近更新过、发布过不止一个版本的环境,并读取每个安装包,说明它需要什么(沙箱、工具、大模型评委、API 密钥、标准答案)。列出环境需要 prime 命令行工具。

每个环境有三个核心指标:假通过上限(任何已知错误的回答拿到的最高分)、偷懒策略得分(一个不看答案、每题都用同一种错误策略的回答,在所有题上的最高平均分)和假失败率(已知正确的回答没拿到满分的比例)。summary.md 还会显示,在错误回答上是奖励的哪一项给了分。

从测试结果到报告#

一次测试得到的是线索,不是结论。把线索变成结论的步骤:

  1. falsepass repro <env> 生成一个只依赖 verifiers 和环境本身的脚本,给那些回答重新打分(回答原文保存在 completions.json)。在干净的虚拟环境里运行两次,输出必须完全一致。
  2. 读奖励代码,找到导致问题的那一行。判断它是漏洞还是有意的设计:故意做成可以被钻空子的判分器,只记作观察,不算问题。
  3. falsepass draft <env> 生成 REPORT.draft.md:每组没通过的测试列为一个候选,附上已确认的静态提示,并标出需要人来判断的地方。用 docs/SEVERITY.md(英文)给成立的问题定级。
  4. 请一个没参与写报告的人,把报告里的每个数字和保存的输出逐一核对。

从报告到给上游提交修复#

falsepass upstream <env> 在 GitHub 上搜索环境的源码(先按 pyproject.toml 里的包名,再按最有特征的类名和函数名),把审计过的每个文件和仓库默认分支逐一比较(忽略换行符差异),并列出提交修复需要知道的信息:默认分支、许可证、最近一次推送、未合并的 PR、最近合并的 PR 中有多少来自外部。falsepass verify-fix <env> --source <打过补丁的文件夹> 用打过补丁的包重跑保存的复现脚本,列出变化的行;只有问题对应的那几行变了,修复才算可以提交。

添加环境#

falsepass init owner/environment 会生成 audits/<name>/env.toml(包名、版本、模块名、安装源、verifiers 版本,全部从 Hub 上的安装包读取),并把安装包的源码解压到 audits/<name>/src,供静态扫描使用。falsepass init ./folder 从本地的 pyproject.toml 做同样的事,并把文件夹记为 source,这样每次运行都会重新安装和扫描这个文件夹。问答类环境到这一步就够了。其他环境要加上 --manual,然后补全生成的 adapter.py:怎么加载题目、怎么按题目要求写答案、错误答案长什么样。

实际使用记录#

2026 年 10 月用于几十个公开的 Hub 环境。保留下来的每个问题,都在干净的虚拟环境里复现过两次,并通过读奖励代码确认;修复正在以 PR 的形式陆续提交给维护者。在这些修复合并之前,这里不公布具体环境和问题数量。

局限#

  • 线索需要人来判断。工具能找出判分器在哪里给错了分,但这算不算漏洞、有多严重,要靠人来决定。
  • 搜索比较浅:只有固定的文本变异,判分器调用几百次。不会针对奖励去训练模型。
  • 容器类任务没有自动模式;托管沙箱上的环境,以及用付费大模型当评委的环境,没有相应凭据就无法覆盖。
  • 支持的框架:verifiers、Inspect AI(不含需要沙箱的任务)和 HUD(按回答判分的任务模板;按代理在工作区里做了什么来判分的任务,仍需手写策略)。

相关工具#

  • rewardlint:针对单个奖励函数的对抗样本集。在标准答案可以替换的情况下,falsepass 会用环境的奖励代码运行它,并把结论写进 summary.md。
  • ratctl:对环境源码做静态扫描,并可借助大模型,查找沙箱漏洞的常见写法。
  • VerifierLab:在设定的预算内,对一个包装好的判分器发起成批攻击,需要提供标准答案来源。
  • vlabs-integrity:针对编程题奖励的变异检测和同构重标记审计,需要一份可信的标准解法。
  • harden-v0:用大模型扮演攻击方和修补方循环对抗,加固 Harbor 格式的代理测评判分器。

目录结构#

falsepass/           测试工具本身(falsepass/mcp_server.py:MCP 工具)
tests/               工具自身的测试
examples/toy-env/    demo 用的小环境,带一个故意埋的漏洞
examples/inspect-toy/、examples/hud-toy/   Inspect AI 和 HUD 版的同类小例子
skills/falsepass/    给 AI 代理的 skill:审计流程
action.yml           GitHub Action
docs/SEVERITY.md     定级标准;docs/OUTPUT.md:输出文件说明(均为英文)
audits/<env>/        由 `falsepass init` 生成;不纳入 git

许可证#

Apache-2.0,见 LICENSE。