fix: 2.0.17 — 安全审计一波过 (5 HIGH + 1 MED v2.0.16 回归 + LOW 收尾)
codex 全项目审计 + 我审 + codex 互审三轮收敛后的产出. 5 个 HIGH 都
是公网部署真线上风险, 1 个 MED 是 v2.0.16 我自己 ship 的回归.
HIGH 1 (auth default-allow): validateApiKey() / checkAuth() 当 API_KEY
未配置时不再 return true. 改为只在 localhost bind (127.0.0.1, ::1,
::ffff:127.0.0.1 mapped) 才放行, 0.0.0.0/::/公网 IP 一律 fail closed.
crypto.timingSafeEqual 取代 ===. 新增 HOST/BIND_HOST env.
HIGH 2 (apiKey leak): /auth/accounts 和 /dashboard/api/accounts 不再
返回完整 apiKey. getAccountList() 改返 apiKey_masked. 新增
getAccountInternal() 仅供 rate-limit/token-refresh/reveal-key 内部用.
Dashboard UI 加 "click to reveal & copy" 按钮触发显式 reveal-key 流程.
HIGH 3 (dashboard write default-allow): 同 HIGH 1 模式. /dashboard/api/auth
GET 在公网 bind + 无 secret 配置时返 { required:true, locked:true }
让 UI 知道是被锁了不是免认证.
HIGH 4 (gRPC parser exception crashed Node): connectParser.push() +
drain() 抛异常没在 data event handler 里 catch, Node 默认进程退出.
包 try/catch 路由到 onError, 关 HTTP/2 stream (NGHTTP2_CANCEL).
Connect 逻辑错误 frame 也补 close. unary path parser.drain() 同样
try/catch. gzip 解压失败原本静默 continue 吃数据, 现在抛错.
HIGH 5 (LS port adoption credential exfil): 启动时 42100 已被占用就
直接当 LS 用 -> 把账号 protobuf 元数据 (含 apiKey) 发过去. codex 第一版
加 gRPC-shape probe, 但 codex 第二审实测 server: custom-grpc-decoy
一行 nginx 配置就过. 完全禁用接管, 端口冲突就跳到下一个空闲端口起
新 LS. probeLanguageServerPort 函数留着 (未来非 spoofable 校验可重启用).
MED 6 (v2.0.16 schema-compact 回归): stripSchemaDocs KEEP allowlist
把 additionalProperties (object 形态) 和 \$ref/\$defs 一并剥了. map 工具
和带 \$ref 的工具丢 schema. 修法: additionalProperties 保留 false 和
object 形态, 仅丢 true; \$ref 在 strip 前 inline-resolve, sibling 字段保留;
循环引用替换成 {type:'object'} 占位 (codex 第一版留 \$ref literal 但
\$defs 已被 strip 导致 dangling pointer).
LOW 7-9: extractToken() Bearer 大小写不敏感 + 拒绝逗号折叠 + 不再把
raw Authorization 当 key. forbidden-words 测试覆盖到 v2.0.16 新增的
schema-compact / skinny tier. safeEqualString 提到 auth.js export
统一一处实现.
测试: 269/269 pass (v2.0.16 的 253 + 本版新增 16). 新增覆盖 fail-closed
逻辑/IPv4-mapped loopback/timing-safe compare/Bearer 解析/probe 函数/
Connect 异常处理/\$ref + additionalProperties 三态/cycle 占位无 dangling.
工作流: codex 高 reasoning 全项目审 -> codex full-auto 实现 -> codex 互审
+ 我并发审 -> 二审找出 6 个 follow-up gap (含 config.host 半接通 +
autoAdd:false 登录回归 + LS spoof 实测 + cycle dangling \$ref) -> 收掉
全部 follow-up. codex 互审实测起 HTTP/2 decoy 验证 LS spoofability 是
本轮最有价值的发现, 我自己只推理没实测.
Co-authored-by: codex (audit) <noreply@openai.com> D
dwgx committed
33dce27ae453bc83c191bcbafd4eafc13b06fef9
Parent: 7bbb8c3