Skip to content

第 25 章 代码安全审查实战

前面章节讲的 /code-review/security-review 是 diff 级别的守门员;本章讲的是仓库级安全审查——拿到一个完整项目,如何在 1-2 小时内产出一份可信、可处置、风险分级统一的安全报告。素材全部来自真实审查,没有虚构案例。

25.1 结论:安全审查的核心是"维度并行 + 统一分级"

用 Claude Code 做仓库级安全审查,核心工作流只有四步:

text
Plan Mode 建立项目事实模型 → 并行子代理按维度审 → 统一风险分级 → 汇总报告

这条工作流之所以有效,是因为它解决了安全审查的三个根本矛盾:

  1. 维度矛盾:安全审查不是"通读代码",而是按鉴权、注入、配置、供应链、崩溃等正交维度分别扫。一个子代理一次只盯一个维度,覆盖率远高于"让 Claude 看一下这个项目安全不安全"。
  2. 分级矛盾:不同项目、不同审查者用的分级命名五花八门(P0/P1/P2 vs CRITICAL/HIGH/MEDIUM vs HIGH/MEDIUM/P0),导致横向不可比。本章给出一套统一定义。
  3. 可处置矛盾:发现 100 个问题但不排优先级,等于没发现。风险分级的价值不是分类,而是决定先修什么

本章所有案例数据来自四份真实审查:new-api(AI 网关)、CodeGraph(本地代码工具)、NTrace-core(网络追踪工具)、Clash Verge Rev(代理客户端),覆盖四类典型项目。

25.2 审查工作流四步详解

25.2.1 第一步:Plan Mode 建立项目事实模型

目标:在改任何代码之前,先让 Claude 只读理解项目,输出一份事实模型。这一步用 Plan Mode(第 07 章),严禁直接开审。

事实模型至少包含五项:

维度要回答的问题为什么要先搞清楚
系统入口HTTP 路由、CLI 命令、MCP 工具、WebSocket 端点各有哪些入口决定攻击面
信任边界哪些代码处理外部输入(URL/订阅/用户提交/网络响应)越过信任边界的地方就是审查重点
数据流凭据、Token、用户数据怎么存、怎么传、怎么销毁凭据生命周期是高危区
权限模型认证怎么做、授权怎么做、默认凭据是什么鉴权 fail-open 是最高频高危
依赖与构建安装脚本、CI 配置、二进制下载、依赖锁定供应链是常被忽略的一维

以 NTrace-core 为例,事实模型阶段会识别出:入口有 CLI、HTTP server、WebSocket、MCP 四类;信任边界包括用户指定的 trace 目标、第三方 IP 地理 API 响应、ICMP 原始包解析;凭据有 NextTrace API Token(写文件)、ipleo Token(硬编码)。这些信息直接决定了后面要派几个子代理、各盯什么。

25.2.2 第二步:按维度拆分并行子代理

目标:把审查拆成正交维度,每个子代理只盯一维,并行跑。这一步是第 19 章 Subagents 与第 20 章 Workflows 的实战预告,这里先用结论。

典型维度拆分(适用于大多数项目):

子代理审查范围典型高危模式
鉴权与认证登录、Token、Session、CORS、CSRFfail-open、默认凭据、CORS 反射、常量时间比较
注入与解析SQL、FTS、命令、正则、YAML/TOML、包解析拼接查询、ReDoS、边界检查缺失、Zip Slip
配置与凭据secrets、TLS、文件权限、默认值InsecureSkipVerify、0644 凭据文件、硬编码 Token
供应链与构建安装脚本、CI 权限、依赖锁定、二进制下载curl|sh 无 checksum、write-all、未 pin SHA
崩溃与资源nil 解引用、数组越界、无界缓存、goroutine 泄漏无 nil 检查、io.ReadAll 无 LimitReader、channel 写入竞态

new-api 的审查就是这套编排的范本:主代理负责架构与依赖,4 个子代理分别盯安全后端、代码质量、前端、架构,最终汇出 4 HIGH + 6 MEDIUM + 4 P0 + 6 P1。关键不是派了 4 个,而是 4 个的边界不重叠——鉴权归安全后端,nil panic 归代码质量,前端 new Function() 归前端,这样不会漏也不会重复。

25.2.3 第三步:统一风险分级

目标:把所有子代理的发现按统一标准分级。这一步最容易被忽略,却是横向可比的前提。详见 25.3。

25.2.4 第四步:汇总报告

目标:合并所有子代理输出,去重、排优先级、给处置建议。报告必须包含:

  • 项目概览(技术栈、规模、核心功能)
  • 问题汇总表(按级别计数)
  • 逐项清单(级别 + 文件:行号 + 问题 + 修复建议)
  • 正面评价(项目做得好的地方,避免只挑毛病)
  • 优先修复建议(分"立即修复"与"高优先"两档)

为什么必须包含正面评价:安全审查不是找茬,是建立信任。把项目做对的地方记录下来,既能校准审查者的判断(知道这个团队的安全意识水平),也能给修复者信心。NTrace-core 的审查就列了 13 条正面评价——subtle.ConstantTimeCompare、Cookie HttpOnly+SameSite、Token 文件 0600、sanitizeLogParam 日志防注入——这些做对了的地方,说明团队知道该怎么做,问题是执行不一致。

25.3 风险分级体系:CRIT/HIGH/MED/LOW/P0

四份真实审查用了三套命名,这是行业现状的缩影。本章强制统一为五级:

级别定义处置优先级典型例子(来自真实审查)
CRITICAL可被远程利用导致 RCE/SSRF/凭据泄露/完全控制,或安装即中招立即修复,阻断发布curl|sh 无 checksum、SSRF 可探元数据端点、CSP 完全禁用、硬编码共享 Token
HIGH需特定条件触发但有实质危害,或高危路径上缺一道防线本 sprint 修复CORS+Credentials 并用、Zip Slip、InsecureSkipVerify、无认证 WebSocket
MEDIUM需链式利用或影响有限,或防御纵深缺失排入 backlog弱 PRNG、Cookie 无 MaxAge、日志文件 0644、new Function() 计费表达式
LOW代码异味、效率问题、信息泄露轻微顺手修正则每次编译、命名不一致、依赖可精简
P0(崩溃)非安全但会导致 panic/进程退出/服务不可用与 CRITICAL 同优先级nil 解引用、数组越界、向已关闭 channel 写入

P0 单列的的理由:安全漏洞与崩溃是两类问题,但处置优先级相同——都会让服务不可用。new-api 的 4 个 P0(nil panic)虽然不是安全漏洞,但一旦触发就是 500 错误,比一个 MEDIUM 的 CORS 配置更该先修。

分级标准必须统一:本章四份审查里,Clash Verge Rev 用 P0/P1/P2/P3,NTrace-core 用 CRITICAL/HIGH/MEDIUM/LOW/P0,导致横向对比困难。正确做法是审查前先在 Plan 里写死分级定义,所有子代理引用同一张表,杜绝"我觉得这个是 HIGH"的主观判断。

25.4 按项目类型分组的审查重点与典型案例

不同类型的项目,攻击面截然不同。下面四类覆盖了大部分实战场景,每类配一个真实案例。

25.4.1 AI 网关:new-api(附 sub2api 对照)

项目:QuantumNous/new-api — 下一代 LLM 网关,聚合 40+ AI 提供商 技术栈:Go 1.25.1 | Gin | GORM | Redis | React 19 + React 18 规模:74 controller,58 service,41 model,42 Provider 适配器

AI 网关类审查重点:

维度重点new-api 的真实发现
鉴权Token 校验是否检查状态/过期/配额TokenAuthReadOnly 未检查 → HIGH(S4)
跨域CORS 与 Credentials 组合、WebSocket OriginAllowAllOrigins + AllowCredentials 并用 → HIGH(S1);WS CheckOrigin 全 true → HIGH(S2)
TLS是否允许禁用验证TLS_INSECURE_SKIP_VERIFY 环境变量可关 TLS → HIGH(S3)
默认凭据初始密码、Session Secret默认 root/123456 → MEDIUM(S8);SESSION_SECRET="random_string" 仅警告 → MEDIUM(S9)
前端执行计费表达式是否用 eval/Functionnew Function() 执行计费表达式 → MEDIUM(S6)

崩溃项(P0):4 个,集中在计费与用户控制器——GetTokenById 失败后仍访问 token 字段(两处 nil panic)、id.(int) 无 nil 检查、defer tx.Rollback 后显式 Commit 逻辑混乱。

处置建议:先修 B1/B2/B3(nil panic 直接导致 500)→ 再修 S4(鉴权 fail-open)→ S1/S2(跨站)→ S3(TLS)。sub2api 同类项目的主要风险是"部署 secrets 默认空",与 new-api 的"默认弱密码"是同一类问题——AI 网关的默认配置普遍偏宽松,审查时务必逐项检查默认值。

25.4.2 代理客户端:Clash Verge Rev v2.5.2

项目:基于 Tauri 2 + React 的 Clash Meta(mihomo)GUI 代理客户端 规模:~97,000 行(Rust 后端 ~19,000 行 + TypeScript 前端) 分级:5 P0 + 16 P1 + 28 P2 + 8 P3(按本章标准映射为 5 CRIT + 16 HIGH + 28 MED + 8 LOW)

代理客户端类审查重点:

维度重点真实发现
前端隔离CSP 是否启用、文件 scope 是否收窄CSP 完全禁用("csp": null)→ CRIT;文件系统 scope 用 ** → CRIT
供应链CI 权限、Actions pinning、二进制 checksumpermissions: write-all → CRIT;Actions 未 SHA pin、构建脚本下载无 checksum → HIGH
解压安全备份/订阅解压是否防路径遍历备份 Zip 解压 Zip Slip → HIGH
远程内容订阅 URL 是否防 SSRF、脚本沙箱是否完整远程订阅缺 SSRF 防护 → HIGH;Boa 脚本沙箱隔离不完整 → HIGH
凭据存储WebDAV/订阅凭据存哪WebDAV 凭据明文存 localStorage → HIGH;WebDAV 硬编码 danger_accept_invalid_certs(true) → HIGH

处置建议:代理客户端的攻击面与前三个类型完全不同——它的核心风险是"前端能干什么"。CSP 禁用 + 文件 scope ** 意味着一旦有任何 XSS(Markdown rehype-raw、SVG 图标都是入口),前端就能读写任意文件。修复顺序:先开 CSP + 收窄 scope → 再堵 XSS 入口 → 最后修供应链。

25.4.3 本地代码工具:CodeGraph v0.9.2

项目:@colbymchenry/codegraph — 本地代码智能系统(tree-sitter + SQLite + MCP) 语言:TypeScript(Node.js),~48K 行 分级:10 CRITICAL + 27 HIGH + 40 MEDIUM + 30 LOW + 4 P0

本地工具类审查重点:

维度重点真实发现
安装脚本curl|sh / irm|iex 是否有 checksuminstall.sh 与 install.ps1 均无校验 → 2 CRIT
查询注入FTS/SQL 是否参数化FTS5 查询注入(特殊字符过滤不全)+ 引号包裹绕过 → 2 CRIT
正则安全解析器正则是否防 ReDoSsvelte/vue/liquid [\s\S]*? 回溯 + C++ 检测正则 → 2 CRIT
资源边界缓存是否有淘汰MCP projectCache 无界增长 + Resolution 7 个 Map 无 LRU → 2 CRIT
配置解析YAML/TOML 是否用标准库Hermes 手工 YAML 解析器 + TOML 未转义控制字符 → 2 CRIT

正面评价(值得记录):SQL 全部参数化查询无注入;validatePathWithinRoot 路径防护完善;O_NOFOLLOW 防 /tmp 符号链接攻击;clamp() 限制所有 MCP 工具输入范围;TypeScript strict 模式。这说明团队安全意识不差,问题集中在"自己造轮子"——手工 YAML 解析器、手工 TOML 序列化器、自己写正则解析器,这些都该换标准库。

处置建议:本地工具的"安装即中招"是最优先级——install.sh/install.ps1 加 SHA256 校验是阻断级修复。其次堵 FTS5 注入(白名单替代黑名单)。ReDoS 和无界缓存可排第三档。

25.4.4 网络追踪工具:NTrace-core

项目:NTrace-core — NextTrace 网络追踪工具 语言:Go ~27,302 行 分级:11 CRITICAL + 22 HIGH + 24 MEDIUM + 18 LOW + 8 P0

网络工具类审查重点:

维度重点真实发现
SSRF用户指定目标是否限制内网normalizeTarget 接受任意 IP/域名,可探 169.254.169.254 → CRIT(C2);环境变量控制完整 URL → CRIT(C9)
认证server 模式是否要 TokenWebSocket 无认证 + 无连接数限制 → CRIT(C3);缓存清除端点无保护 → CRIT(C11)
TLS第三方 API 调用是否验证证书多处 InsecureSkipVerify: true → CRIT(C4)
凭据Token 怎么存怎么传明文 0644 文件 → CRIT(C5);硬编码 ipleo="NextTraceDemo" → CRIT(C8);Token 在 URL Query → CRIT(C10)
包解析原始包是否有边界检查ICMP/TCP 解析缺长度校验 → CRIT(C7),同时也是 P0 崩溃
MCP暴露操作是否有权限控制MCP 接口无细粒度权限 → CRIT(C6)

崩溃项(P0):8 个,全是边界检查缺失——ICMP 解码 nil 解引用、TCP 探测数组越界、MTR Runner nil channel、macOS net.ParseIP 返回 nil 后 .To4()、IPInfo strings.Fields(...)[0] 越界、DN42 PTR CSV row[3] 无边界。Go 的 nil panic 和切片越界是 P0 高发区,审查时凡是 data[offset:]arr[i]x.Field 链都要盯。

处置建议:网络工具的 SSRF 是阻断级——服务端 trace 接口必须做内网 IP 过滤(RFC 1918/4193/6598)+ 速率限制。硬编码 Token 与 InsecureSkipVerify 是"执行不一致"的典型:项目在 Token 文件安全上做对了 0600、符号链接拒绝、原子写入,却在另一处硬编码共享 Token,说明审查必须覆盖所有凭据路径,不能只看一处就放心。

25.5 /code-review、/security-review 与深度人工审查的分工

Claude Code 提供两个 Bundled Skill(第 13 章)与本章的深度审查形成三层防线:

层级工具范围适用场景局限
第一层/code-review当前 diff每次提交前只看变更,不看全局;质量向+少量安全
第二层/security-review待提交变更安全敏感改动仍是 diff 级,覆盖维度有限
第三层本章工作流整个仓库接手新项目、发布前审计、定期复审耗时长(1-2 小时),需人工编排

分工原则:/code-review/security-review日常守门员,拦的是"这次改动有没有引入新问题";本章工作流是全身体检,查的是"这个项目整体安全水位如何"。两者不可互相替代——只跑 /security-review 就认为项目安全,等于只看了今天改的几行,漏掉了所有历史遗留问题。

实际用法:日常开发每 PR 跑 /code-review --fix;涉及鉴权/凭据/网络的改动加跑 /security-review;接手新项目或发布大版本前,用本章工作流做一次全量审查。三层配合,才叫工程化。

25.6 并行子代理编排(预告第 19/20 章)

本章的并行审查依赖 Subagents(第 19 章)与 Workflows(第 20 章),这里给出最小可用编排:

text
主代理(Plan Mode):
  1. 建立项目事实模型
  2. 按维度拆分子代理任务
  3. 派发并行子代理(鉴权/注入/配置/供应链/崩溃)
  4. 等待全部返回
  5. 汇总去重,统一分级,输出报告

编排要点:

  1. 子代理任务要带维度定义:不要说"审一下安全",要说"审鉴权维度:检查登录、Token、Session、CORS、CSRF,引用 25.3 分级表"。
  2. 子代理只读:审查阶段不允许子代理改代码,改了就会污染事实模型。修复是另一阶段。
  3. 子代理返回结构化:要求每条发现带 级别 + 文件:行号 + 问题 + 修复建议,主代理汇总时才不用重新读代码。
  4. 主代理负责去重:鉴权子代理和注入子代理可能都报告了同一个 SQL 拼接,主代理要合并成一条。
  5. worktree 隔离(可选):对超大型项目,可用 worktree(第 19 章)让每个子代理在独立工作树里跑,避免互相干扰。

new-api 的审查就是这套编排的实战:主代理管架构与依赖,4 个子代理分头跑,最终汇成一份统一报告。关键不是派了几个子代理,而是边界不重叠、分级标准统一。

25.7 失败边界

以下三种做法会让审查失效,务必避免:

失败一:只跑 /security-review 就认为安全/security-review 是 diff 级,只看待提交变更,不看历史遗留。一个有 10 个 CRITICAL 的项目,只要这次 diff 没碰那些代码,/security-review 会干净通过。这是最常见的错觉——"工具没报错=项目安全"。正确认知:diff 级审查拦新增,仓库级审查查存量,缺一不可。

失败二:审查不读真实代码,凭记忆判断。审查记忆文件(如本章引用的四份)是 point-in-time 观察,标注了"47 天前"——代码可能已经改了。用 Claude Code 做审查时,必须让它 Read 真实代码、Grep 真实模式,而不是问"你记得 new-api 有什么安全问题吗"。记忆的作用是提供审查清单(知道要查哪些维度),不是提供审查结论。一旦结论脱离真实代码,就是幻觉。

失败三:风险分级标准不统一。四份真实审查用了三套命名,导致无法横向比较"new-api 和 NTrace-core 哪个更危险"。正确做法:审查前在 Plan 里写死本章 25.3 的五级定义,所有子代理引用同一张表。凡是子代理自己发明分级(比如"严重/中等/轻微")的,主代理汇总时必须映射回统一标准。

边界之外:Claude Code 的安全审查能覆盖 80% 的常见模式(注入、鉴权、配置、崩溃、供应链),但以下场景仍需人类专家:加密协议实现、侧信道攻击、内核态代码、零日漏洞利用。承认边界,把 AI 审查当作"大幅提升覆盖率的初筛",而非"替代专家的终审"。

25.8 小结

仓库级安全审查的四步工作流——Plan 建模、维度并行、统一分级、汇总报告——本质上是把"安全"这个模糊概念,拆成了机器可检查的正交维度可比较的离散级别。这与第 06 章的核心心法一脉相承:你定维度和分级,Claude 做扫描和初筛。

四类项目的审查重点各异,但底层模式相通:AI 网关盯鉴权 fail-open 与默认配置,代理客户端盯前端隔离与供应链,本地工具盯安装脚本与查询注入,网络工具盯 SSRF 与包解析边界。掌握这些模式,遇到新项目就能快速套用。

最后强调一句:安全审查的价值不在发现多少问题,而在先修哪个。一份分级统一、优先级清晰的报告,远比一份列了 100 条没排序问题的清单有用。这是工程纪律,不是技术能力。


上一章:第 24 章 RAG 知识库实战