探索性案例研究 · ep1-f005

原题通过之后,
漏洞是否真的修好了?

以一个真实的凭据泄漏缺陷为例,观察不同模型驱动同一 Hermes Agent 完成修复后,补丁能否覆盖客户端复制这一额外边界。

研究问题
通过原始回归测试,是否足以说明修复满足更完整的凭据隔离要求?模型回答中识别出的风险,是否落实到了代码?
方法
9 个模型各运行两次,主实验预算 420 秒;另对 GLM-5.3 做两次 1200 秒探针。随后对归档构造函数、未修复基线和上游参考函数实施统一 HTTP 边界检查。
主要发现
解释范围
本报告比较的是指定任务、提示、Agent、工具与预算下的系统运行。单任务、小样本;不估计模型总体成功率,也不建立能力排名。

01 · 任务背景

正确行为:请求只能携带选定的凭据

给第三方供应商设置 API key 后,SDK 仍会从环境读取另一份 Bearer token,导致同一请求携带两套凭据。任务要求阻止多余凭据被发送,同时保留合法鉴权。上游采纳的方案是参考实现;允许行为等价的其他实现。

先定义行为约束,再解释实验分数。
场景应保留应排除本报告如何检查
API key 原始请求指定的 X-Api-Key环境中的 Bearer token原题回归测试 + 函数补测
Bearer 原始请求指定的 Authorization环境中的 API key函数补测;基线已通过
复制客户端后的两种请求与原始请求相同复制时重新带入的环境凭据实验后补测;非原题评分项
展开:问题时间线与上游参考答案
  1. 0genlab 发现并复现凭据跨厂商泄漏

    显式配置了第三方 API key,SDK 仍把环境里的 Anthropic Bearer 凭据附加到请求中。用本地 HTTP 服务复现并提交 issue。

    问题 #105774 ↗
  2. 修复普通请求后,发现复制客户端仍会泄漏

    kokhlo 提出修复;0genlab 补测 with_options() 复制路径,提出请求头层的 Omit 方案,作者采纳。

    复制路径证据 ↗ · 作者采纳 ↗
  3. 多人继续讨论兼容兜底与反向鉴权

    egilewski 指出 Omit 不可用时兼容分支会失去保护;0genlab 补充 HTTP 实测和修复贡献,同时指出反向凭据的复制风险。

    评审意见 ↗ · 复现与贡献 ↗
  4. 上游最终合入双向 Omit 方案

    kshitijk4poor 接续原 PR,通过 sdk.Omit() 删除未选定的鉴权头,并保持客户端复制后的保护。合并提交 036a20b3。

    最终方案 #107978 ↗
参考答案的行为约束:API key 请求只发 x-api-key;Bearer 请求只发 Authorization;客户端复制后仍成立。允许其他等价实现,不按代码相似度评分。

上游 PR 在 SDK 0.87.0 上验证;本报告的补充检查统一使用冻结镜像的 SDK 0.94.0。

02 · 研究设计

三层证据,分别回答三个问题

证据层问题判据与用途
原题验收是否完成当时可执行的验收?恢复原始 regression_test.py 后判分;只有 API key 原始请求这一项回归测试。任务文字另要求保留 OAuth,但该测试未覆盖它。
修复稳健性归档函数能否满足四项边界检查?两种鉴权 × 原始/复制请求;统一 SDK 与本地 HTTP。属于事后探索,不改写历史 score。
回答证据答复如何描述风险与验证?按统一规则阅读可见内容;“说到、计划、声称验证、实现保护”分别记录,不合成全面性分数。

实验条件与证据完整性

项目本次记录可比性与限制
任务与提示相同旧源码、TASK.md、原始测试已给根因、文件位置与 None 陷阱;评估实施修复,不是独立发现根因。未给上游补丁和复制验收。
样本与纳入规则固定两份快照,18 次主实验 + 2 次探针保留全部超时;按 task 排除 1 次 p1 冒烟测试。较早校准运行不属于本队列。
模型与渠道9 个归档模型标识,经 AIHubMix 接入模型名称不是可重建权重版本;运行跨日。上游服务变化未被完全控制。
Agent 与环境相同镜像、独立容器、2 CPU / 4 GB、固定 SDKAgent 与任务源码分别锁定。宿主判分依赖未完整冻结。
预算420 秒主实验;GLM 单独 1200 秒组;max_turns=20延时组为新运行,非续跑;不与主实验混合估计成功率。
采样与推理配置本报告尚未逐请求汇总 temperature、seed、推理预算等标为待核对;不能仅凭同一 Agent 配置断言各上游的有效参数一致。
运行安排实际时间由快照保留未建立随机执行顺序或时间分块设计;平台负载、重试等影响未独立控制。
补测与文本审阅函数级补测;单次 AI 辅助文本审阅,无独立第二审阅者,未逐项复核回答中的工具执行声明。
展开:版本、资源、网络与复现限制
复现限制:基础镜像 digest 未完整记录,宿主判分依赖未冻结。本报告记录的是现存镜像与任务结果,不能保证重新构建能得到逐字节相同镜像或相同模型输出。
展开:指标定义、判分实现与数据来源
score / exit
score=1 表示原始回归测试通过;exit=0 表示 Agent 正常结束,exit=124 表示外层超时。两者不是同一判据。
测试修改标记
score.sh 在宿主恢复原始测试再执行;test_modified_by_model 独立记录,不自动扣分。本期20次均为 false。
wall_s
从运行启动前到容器结束、日志收集及移除后计时,不含后续判分。
API 调用数
official 取 Hermes api_calls;wire 只计带有效 usage 的实录响应,未必覆盖全部失败和重试。不等于 Agent 主循环轮次。
输入口径
wire 的未缓存输入 = prompt_tokens − cached_tokens;official 使用导出 input_tokens,缓存读取另列。列表均值为两次算术平均。
补测范围
归档构造函数 + anthropic 0.94.0 + 容器内回环 HTTP 服务;容器无外网,不调用模型。补测日期:

模型任务原文 · 任务源码版本 · 镜像锁文件 · 判分脚本

03 · 结果

通过原题的补丁,仍全部漏掉复制边界

图 1 · 每次运行的原题结果与补测结果

模型按名称排列 · 点击运行编号并排核对证据
✓ 检查通过× 检查未通过— 不适用
左列是历史原题判分;右侧四列均来自实验后的函数级补测。基线和参考函数不参加模型原题运行。
实现 / 运行原题结果API key 补测Bearer 补测
原始请求复制请求原始请求复制请求
基线的 Bearer 原始请求已经通过,因此这列不能算模型带来的改善。四项测试均验证指定凭据保留与额外凭据排除;不把通过项数解释为相似度或安全得分。22 份函数 × 4 场景 = 88 次本地 HTTP 请求。

图 2 · 相同预算下的两次运行耗时

每个点是一场主实验;同一模型的两次运行上下分开。● / ○ 为通过,■ 为超时。模型按名称排列;右侧为两次原题通过数。

模型 / 逐次耗时
0150300450s
原题
虚线为 420 秒预算。超时是“未在预算内完成”,不是一次完整解题的完成时间;启动、收尾和终止轮询使记录略高于阈值。两次结果只呈现观察差异,不据此估计稳定性或画分布密度。
补充观察:GLM 延时探针(独立两次运行)

B · 单独延时探针 / glm-5.3

增加预算后,结果从未通过变为通过

附图 A · 两组独立运行

固定同一镜像和资源,把外层预算从 420 秒改为 1200 秒,重新运行两次。下图是同一时间刻度;虚线表示各组预算。

0/2 420s 主实验2/2 1200s 延时探针
04008001200s
超时不能直接解释成“模型不会修”。这四次运行提示预算会影响观察结果;延时组是新运行,不是续跑,n=2 不足以建立稳定的因果或成功率结论。glm-5.3-flash 没有对应延时探针。

04 · 机制解释与定性审阅

保护停在客户端实例上,复制后没有延续

图 3 · 从构造到复制,保护在哪一步失效?

以下以 API key 路径解释实际观察。箭头表示执行先后;“复制后”结果来自归档函数补测。
实现方式① 构造客户端② 原始请求③ 复制客户端④ 复制后的请求
未修复基线
SDK 读取环境 token带出额外凭据 ×再次构造,继续读取环境仍带出额外凭据 ×
构造后清空属性
读取环境 → 清空 auth_token当前实例不带额外凭据 ✓None 触发 SDK 再次读取环境额外凭据重新出现 ×
临时移除环境变量
移除变量 → 构造 → 恢复变量当前实例不带额外凭据 ✓从已恢复的环境读取 token额外凭据重新出现 ×
上游请求头排除
参考函数
保存显式排除头的规则构建请求时由 Omit 删除多余头 ✓复制保留 default_headers 规则仍在请求头层排除 ✓
上游对 API key 与 Bearer 两个方向分别排除另一种凭据。其他满足同样行为约束的实现也可以成立;本次未测试并发、完整 Agent 所有调用路径或其他 SDK 版本。

回答是否识别了复制风险?逐次使用同一套规则

145 条非空可见答复,包含过程说明与最终答复;另排除 21 条辅助标题生成回复。不读取 reasoning_content,不把工具参数或工具输出当作模型回答。以下是单次 AI 辅助定性审阅,尚无独立复核。

审阅规则与判定边界(版本 1.0)

所有 20 次运行使用相同审阅范围。“未见明确表述”只描述已保存的可见内容,不能推断模型没有考虑。提及 Omit 只记为排查意图,不记为完成方案。复述任务已给出的根因与 None 陷阱,不算独立发现。

展开:全部 20 次运行的统一文本审阅表
不合成“全面性”总分。风险表述、完成声明和独立结果分别记录;点击运行编号进入证据浏览器。
模型 / 运行复制边界表述Omit 表述最终交付声明独立结果:原题 / 复制
三个代表性片段:识别风险、限定范围、提及机制

05 · 讨论与局限

这次实验支持什么,还不能支持什么?

本次证据支持不能据此外推下一步所需证据
16 份原题通过实现均未通过复制补测“这些模型普遍不能修安全漏洞”多个独立任务、更多重复、预先确定的任务抽样规则
GLM r14 明确表述复制重读环境风险,但该次未完成“GLM 思考最全面”或“说到风险就代表会解决”统一审阅规则的独立复核,结合工具执行与代码落实
GLM 延时组另外两次通过原题预算增加是结果变化的唯一原因受控预算对照、执行顺序控制及更多重复
在指定 SDK 下,参考函数四项检查均通过参考方案已通过完整安全审计完整调用路径、并发、多个版本与其他运行环境检查

效度限制:任务已提供根因;模型调用跨日且版本未完全固定;单模型主实验仅两次;补测为事后探索;复制检查覆盖函数而非完整 Agent;文本审阅者知道实验结果,存在解释偏差。以上限制保留在结论附近,不藏在页尾。

06 · 开发者行动与证据

把这次发现转成验收要求

  1. 从一次请求扩展到客户端生命周期:验收同时检查原始实例和复制实例;两种鉴权均保留指定凭据、排除额外凭据。
  2. 让完成声明对应实际检查:报告列出执行过的场景,未测项明确保留,不用“已修复”替代验证范围。
  3. 下一轮先固定规则再运行:若把复制场景升级为正式评分项,应建立新任务版本、新队列与新结果,不回写本次成绩。
附录 A · 全部 20 次运行的数值明细
按模型与预算列出;调用量不用于主结论或效率排名。
模型 / 运行预算耗时scoreexitAPI 调用 / 来源测试被改
附录 B · 输入与缓存用量

B · 执行开销 / 仅 420 秒主实验

总输入里,有多少来自缓存?

附图 B · 每模型两次算术均值

把未缓存输入与缓存读取分开展示。超时运行只累计停止前的已记录用量;数据来源与覆盖范围详见口径说明,不能直接据此比较费用。

未缓存输入缓存读取
条形总长 = 未缓存输入 + 缓存读取,统一线性刻度。右侧显示合计与未缓存部分。wire 是响应 usage 实录聚合,official 是 Hermes 导出账本,都不是厂商账单。
附录 C · 其他问题与工具链记录(7 条)

补充记录

实验过程中还发现了什么?

7 条记录 · 按需展开

包括真实漏洞、早期校准问题和工具链观察;不同阶段分开标注,不把历史污染运行计入本期结果。

证据索引与复核状态

产物入口当前状态
运行快照主实验 · 延时组原始数值保留,图表由快照生成
边界补测结果与函数哈希 · 检查脚本基线、参考和20份函数;实际捕获88次请求
可见回答与审阅145条可见内容 · 规则与逐次判定与原始响应哈希及摘录核对;未独立复审
报告构建生成脚本 · 可见回答提取脚本本地可重新生成;完整环境重建仍受镜像和依赖记录限制