首页
/ gstack 代码评审安全专项(Security Specialist)清单解析:从认证绕过到攻击面扩张的多层防线

gstack 代码评审安全专项(Security Specialist)清单解析:从认证绕过到攻击面扩张的多层防线

2026-09-06 18:01:57作者:谭伦延

本文围绕 review/specialists/security.md 展开。它是 gstack /review 多智能体评审流水线中 Security Specialist(安全专项评审员) 的核心检查清单:定义了这个专项在何种 diff 范围内被触发、按什么 JSON 结构上报问题,以及七大安全类别中 48 条具体检查项。读完本文,你将掌握在 AI 驱动的预合入(pre-landing)代码评审中,如何用一套可机器解析、可指纹去重、可自动修复的问题格式去捕获普通评审最容易漏掉的认证授权、密码学误用、注入变体、密钥泄露、XSS 逃生舱与反序列化风险,以及这些发现如何在 gstack 流水线中汇入 Fix-First 修复闭环。

一、背景:专项评审员在 /review 流水线中的位置

在 gstack 的 review/SKILL.md 中,/review 被定义为「Pre-landing PR Review」:对当前分支相对基线分支(base branch)的 diff 做结构化审查。其核心评审路径分两步:

  1. Step 4 Critical pass(主评审):应用 CRITICAL 类别,覆盖 SQL & Data Safety、Race Conditions & Concurrency、LLM Output Trust Boundary、Shell Injection、Enum & Value Completeness,再叠加若干 INFORMATIONAL 类别。
  2. Step 4.5 Review Army(专项评审大队):按 diff 的作用域信号(scope signal)派发若干**专项评审员(specialists)**子代理,并行独立审查,避免上下文偏置。

Security Specialist 就是第二步中按需派发的专项之一,其职责与主评审形成明确分工。security.md 开头的一句话界定了边界:

This checklist goes deeper than the main CRITICAL pass. The main agent already checks SQL injection, race conditions, LLM trust, and enum completeness. This specialist focuses on auth/authz patterns, cryptographic misuse, and attack surface expansion.

也就是说,主评审已经覆盖 SQL 注入、竞态条件、LLM 信任边界与枚举完整性,安全专项不再重复这些,而是把火力集中在四件事上:

  • 认证 / 授权模式(auth/authz patterns)
  • 密码学误用(cryptographic misuse)
  • 信任边界上的输入校验(input validation at trust boundaries)
  • 攻击面扩张(attack surface expansion)

这种「主代理兜底 + 专项深挖」的双层设计,是为了把容易漏检的安全问题从通用评审中剥离出来,交给一个持有专项清单、无历史偏见的独立子代理去逐条核对。

二、触发范围与派发时机:什么时候安全专项会上场

security.md 顶部定义了严格的作用域条件(Scope):

When: SCOPE_AUTH=true OR (SCOPE_BACKEND=true AND diff > 100 lines)

翻译成白话:当本次 diff 涉及认证鉴权,或者涉及后端且改动超过 100 行时,Security Specialist 才会被派发。这与 review/SKILL.md 中的派发逻辑完全一致,该逻辑还定义了完整的专项取舍规则:

信号 派发的专项
diff 行数 < 50 跳过全部专项,仅打印 Small diff ... specialists skipped
总是启用(50+ 行改动) Testing、Maintainability
SCOPE_AUTH=true Security
SCOPE_BACKEND=true AND DIFF_LINES > 100 Security、Performance
SCOPE_MIGRATIONS=true Data Migration
SCOPE_API=true API Contract
SCOPE_FRONTEND=true Performance、Design(使用 review/design-checklist.md

其中 DIFF_LINESgit diff --stat 计算(DIFF_INS + DIFF_DEL)。两个值得注意的设计:

1. 安全专项是「保险型」常驻专家。review/SKILL.md 的适配性门控(adaptive gating)中,普通专项如果连续 10+ 次派发零发现,会被打上 [GATE_CANDIDATE] 标记并自动跳过以节省成本。但 Security 与 Data Migration 被标记为 [NEVER_GATE]

Security and data-migration are insurance policy specialists — they should run even when silent.

即:即使它们长期「沉默」,也永远不允许因历史命中率低而被自动省掉——安全审查的价值恰恰在于「没发现问题 ≠ 不存在问题」,一次漏放就可能造成不可逆损失。

2. 支持人工强制派发。/review 提示词中追加 --security(或 --all-specialists)可以无视门控强制带上安全专项,见 review/SKILL.md。当你想对自己的敏感改动(如登录、支付、权限升级)做一次彻底的安全体检时,这是一个低成本的强制入口。

各专项清单文件

七个专项的清单分别存放在 review/specialists/ 目录下,与 SKILL.md 的派发逻辑一一对应:

三、输出协议:机器可解析的逐行 JSON

为了让主代理能够收集、合并多个专项的结论,Security Specialist 的输出必须严格遵守机器可解析的协议。清单文件顶部定义:

Output: JSON objects, one finding per line. Schema:
{"severity":"CRITICAL|INFORMATIONAL","confidence":N,"path":"file","line":N,"category":"security","summary":"...","fix":"...","fingerprint":"path:line:security","specialist":"security"}
Optional: line, fix, fingerprint, evidence, test_stub.
If no findings: output `NO FINDINGS` and nothing else.

字段语义拆解:

字段 必填 含义与取值
severity CRITICALINFORMATIONAL(与主评审的 P0/P1 体系不同,安全专项采用二档严重度)
confidence 1–10 的置信度整数
path 问题所在文件路径
category 固定为 security
summary 问题描述
specialist 固定为 security
line 问题所在行号
fix 建议修复方案
fingerprint 去重指纹,默认格式为 path:line:category
evidence 佐证(如触发代码原文)
test_stub 能复现该问题的测试骨架

值得注意的是,下游对 fingerprint 的使用非常关键。在 review/SKILL.md 的合并阶段,如果 fingerprint 字段缺失,主代理会回退计算为 {path}:{line}:{category};当多个专项命中同一指纹时:

  • 保留置信度最高的那条;
  • 打上 MULTI-SPECIALIST CONFIRMED (specialist1 + specialist2) 标签;
  • 置信度 +1(上限 10)

因此每个安全发现都附带稳定的 path:line:security 指纹,既能跨专项去重,也能在历史评审间做「之前跳过的问题今天是否被修复」的复查(见 Step 5.0 的 cross-review finding dedup)。

NO FINDINGS 协议同样重要:若专项一无所获,只输出 NO FINDINGS不得输出任何其他内容——没有前言、没有总结、没有评论。这是为了让主代理在收集阶段能可靠地按行解析(review/SKILL.md):碰到 NO FINDINGS 直接跳过该专项,其余每行按 JSON 解析,非 JSON 行丢弃。

四、信任边界上的输入校验(Input Validation at Trust Boundaries)

第一个类别针对系统信任边界——即外部数据第一次进入内部逻辑的那一层。检查点包括:

  • 用户在 controller/handler 层输入未经验证即被接受
  • 查询参数直接被拼进数据库查询或文件路径
  • 请求体字段未做类型检查或 schema 校验即被接受
  • 文件上传缺少类型 / 大小 / 内容校验
  • Webhook 载荷未做签名验证即被处理

这一类别的核心判断标准是:校验发生在何处、以何种强度发生。框架层(如 ORM、路由)自带的校验往往只能挡住「意外」,挡不住「蓄意」。例如 Webhook 场景,第三方回调的载荷只要缺少签名验证,攻击者就可以伪造事件触发任意副作用。文件上传同理,仅仅检查扩展名白名单是不够的,内容嗅探(如伪装成图片的 HTML/脚本)、大小与解压比(zip bomb)都应在服务端显式设防。

review/specialists/red-team.md 中「Exploit Trust Assumptions」的角度反推,这类漏洞的典型触发模式是「前端校验过、后端未校验」,以及「内部 API 假定只有我们自己的代码在调用」——安全专项在信任边界上要做的,正是假定每一个入口都可能被攻击者直接触碰。

五、认证与授权绕过(Auth & Authorization Bypass)

第二类是 gstack 判定 SCOPE_AUTH=true 就必须触发安全专项的原因。检查点:

  • 端点缺少认证中间件(需核对路由定义)
  • 授权检查默认「放行」(allow)而非「拒绝」(deny)
  • 角色提权路径(用户能修改自己的角色 / 权限)
  • 直接对象引用漏洞(用户 A 通过篡改 ID 访问用户 B 的数据)
  • 会话固定或会话劫持机会
  • 令牌 / API Key 校验未检查过期时间

从安全设计原则看,这六条覆盖了认证与授权的两类典型缺陷:

  1. 默认策略错误:「默认放行、显式封禁」在任何权限系统里都是危险的,正确范式是 fail-closed——未命中任何规则即拒绝,并把匿名请求显式排除在敏感路由之外。
  2. 信任用户的输入作为身份边界:IDOR 的根源在于把资源 ID 当作访问凭证;正确的做法是始终把「当前会话主体」与「资源归属」做交叉比对,而不是信任 URL 中的 ID 参数。角色字段同理:权限必须由服务端依据会话或受信存储推导,任何来自客户端请求体的 role/permission 字段都视为提权尝试。

会话固定/劫持与过期校验,则提醒评审者不仅要看「能不能登录」,还要看整个会话生命周期:登录后是否轮换 session ID、令牌过期是否真的被检查(很多实现只验签名不验 exp)。

六、超越 SQL 的注入向量(Injection Vectors, beyond SQL)

主评审已覆盖 SQL 注入,因此安全专项关注的是其余一切把用户可控输入拼入解释器/上下文的注入通道:

  • 通过携带用户可控参数的子进程调用造成命令注入
  • 模板注入(Jinja2、ERB、Handlebars)
  • 目录查询中的 LDAP 注入
  • 通过用户可控 URL(fetch、redirect、webhook 目标)造成的 SSRF
  • 通过用户可控文件路径造成的路径穿越(../../etc/passwd
  • 通过用户可控值写入 HTTP 头造成的响应头注入

六条注入面有一个共同的架构教训:凡是字符串最终被当作「代码、地址、路径或头字段」消费的地方,都必须做边界化处理。例如:

  • 命令注入:优先使用不经过 shell 的参数数组形式(如 Node 的 execFile、Python 的 subprocess list 参数),而不是把用户输入拼进 shell 字符串。
  • 模板注入:用户内容只能作为数据渲染,绝不能作为模板本身编译。
  • SSRF:fetch/redirect/webhook 目标必须校验协议与目标地址白名单(如仅允许 https + 固定域名),并注意 DNS rebinding 这类绕过手段。
  • 路径穿越:用规范化后的路径做前缀与归属校验(如 path.resolve 后校验仍位于允许的根目录内)。
  • 头注入:禁止将未过滤的用户输入(尤其是换行符)拼入 Set-Cookie 或自定义响应头。

七、密码学误用(Cryptographic Misuse)

密码学是「看似可用、实则脆弱」的高发区。清单给出五条具体检查点:

  • 对安全敏感操作使用弱哈希算法(MD5、SHA1)
  • 用可预测随机源(Math.randomrand())生成令牌或密钥
  • 对密钥、令牌或摘要使用非常量时间比较(==
  • 硬编码加密密钥或 IV
  • 密码哈希缺少盐值(salt)

这些点各自对应一个明确的攻击面:

检查点 典型反例 正确实践
弱哈希 用 MD5/SHA1 存密码或做签名 密码使用 bcrypt/argon2/scrypt 等专用 KDF;消息完整性用 SHA-256 及以上
可预测随机 Math.random() 生成重置令牌、API key 使用 CSPRNG(crypto.randomBytessecrets.token_*
非常量时间比较 == 比较签名/令牌 使用语言内置的恒定时间比较函数(crypto.timingSafeEqual 等),防止时序侧信道
硬编码密钥/IV 源码中出现静态密钥、固定 IV 密钥走密钥管理系统或环境注入;IV 每次加密随机生成
密码哈希缺盐 直接哈希明文密码 加随机盐,且由 KDF 内建处理(如 bcrypt 自动加盐)

评审时要特别注意:这些缺陷往往藏在「看起来工作正常」的代码里,例如重置信令牌用了时间戳种子、JWT 密钥写死在配置注释中、AES 使用了可预测的固定 IV。专项评审的价值就是逐条把它们从「能跑」的代码里挑出来。

八、密钥与敏感信息暴露(Secrets Exposure)

第五类关注敏感数据在不该出现的地方出现:

  • 源码中出现 API key、令牌或密码(包括注释里
  • 密钥被写进应用日志或错误消息
  • URL 中出现凭据(query 参数或 URL 中的 basic auth)
  • 敏感数据出现在返回给用户的错误响应中
  • 明文存储本应加密的 PII

这一类评审的三条主线:

  1. 搜索线索要广:不仅扫赋值语句,还要扫注释、示例代码块、测试夹具、前端打包产物(硬编码密钥极易以「临时方便」的形式混入)。
  2. 间接泄露同样致命:日志打点、异常消息透传、把第三方服务报错原样抛给用户,都可能把内部凭据或数据库细节泄露给攻击者。
  3. 加密与脱敏预期:凡是数据模型里标注「应加密」的字段以明文落库、凡是应在日志中脱敏的字段原样输出,都属于需要上报的类别。

gstack 仓库自身在 scripts/jargon-list.jsonlib/redact-engine.ts 等处存在大量与「敏感信息处理、redaction、审计」相关的工程实践,侧面印证了这类问题的普遍性与重视程度;对普通业务仓库做安全评审时,应同样把「日志/错误/URL」三个外泄出口作为必查位置。

九、经逃生舱位触发的 XSS(XSS via Escape Hatches)

现代前端框架默认做输出转义,因此 XSS 高发点集中在显式关闭转义的逃生舱上:

  • Rails:对用户可控数据使用 .html_saferaw()
  • React:用 dangerouslySetInnerHTML 渲染用户内容
  • Vue:用 v-html 渲染用户内容
  • Django:对用户输入使用 |safemark_safe()
  • 通用:用未净化数据做 innerHTML 赋值

逃生舱检查的本质是:每一个绕过框架默认转义的调用点,都构成一次手动安全审计义务。遇到上述 API 时,评审者应追溯数据源头:

  • 数据是否最终来自用户输入、URL 参数、数据库中的第三方内容?
  • 在此之前是否经过上下文敏感的净化(如 DOMPurify 等基于解析器的清洗,而非正则黑名单)?
  • 即使来源「看起来可信」,是否可能被间接污染(如存储型 XSS 中管理端输入最终渲染到用户端)?

若答案是「逃生舱 + 不可信来源」,即为一条确定的 XSS 发现。注意:这类问题光靠自动化扫描器很难排除,框架感知的代码评审(检查 dangerouslySetInnerHTMLv-html 等调用图)是主要防线。

十、反序列化(Deserialization)

最后一类是最容易被低估的「直接 RCE 通道」:

  • 反序列化不受信数据(pickle、Marshal、YAML.load、对可执行类型做 JSON.parse
  • 接受来自用户输入或外部 API 的序列化对象而未做 schema 校验

反序列化漏洞的风险在于,很多语言的序列化格式内嵌类型信息(Python pickle、Ruby Marshal、Java ObjectInputStream),恶意构造的载荷可以在反序列化过程中触发任意代码执行。YAML.load 的隐患也与此同源:部分 YAML 解析器默认支持标签实例化任意对象。

评审中的处置范式:

  • 只要序列化数据跨越信任边界(来自请求体、Cookie、外部 API),就应视为不可信;
  • 首选纯数据格式(JSON 仅保留标量/数组/对象)+ 严格的 schema 校验;
  • 若必须使用语言原生反序列化,则限定在可信来源(如内部消息队列),并明确记录信任假设。

十一、发现如何进入修复闭环:收集、去重、Fix-First 与红队升级

安全专项的发现并不止于「报告」,而是完整接入 review/SKILL.md 的后续管线:

收集与合并(Step 4.6)。 所有专项子代理并行完成(每个 Agent 调用显式传 run_in_background: false)后,主代理按指纹分组、保留高置信度版本、对跨专项命中的发现做 MULTI-SPECIALIST CONFIRMED 标记并置信度 +1。随后应用置信度门控:7+ 正常展示,5–6 加「Medium confidence — verify」警示,3–4 移入附录,1–2 直接抑制。合并后还会计算 PR 质量分:quality_score = max(0, 10 - (critical_count * 2 + informational_count * 0.5))

Fix-First 修复(Step 5)。 安全发现与主评审发现走同一套 Fix-First 启发式:AUTO-FIX 类直接修复并打 [AUTO-FIXED] 标记,ASK 类批量询问用户(A: 现在修复 / B: 知晓 / C: 误报)获得批准后再改。无论自动还是人工,都会在评审日志中按 {fingerprint, severity, action} 结构归档,便于后续评审复查「曾跳过的问题是否复发」。

红队升级(Red Team activation)。 review/specialists/red-team.md 定义了对抗性复审的激活条件:diff > 200 lines 或 security specialist 发现 CRITICAL 级问题。也就是说,只要安全专项报出一条 CRITICAL,流水线就会追加一次以「攻击者 + 混沌工程师 + 敌意 QA」视角运行的二次审查,专门寻找其他专项之间的夹缝、跨类别组合问题与集成边界故障。安全性发现由此成为整条流水线风险升级的触发器,进一步印证了它作为「保险型专项」的设计意图。

十二、在项目中使用这套评审体系

要在真实代码库上运行这套安全评审,建议路径如下:

  1. 配置 gstack:参照仓库根目录的 CLAUDE.mdSKILL.md 完成 skill 接入,使 /review(以及 /plan-*-review/ship 等)可被正常调用。
  2. 发起评审:在待合入分支上运行 /review。流水线会先探测平台与基线分支(gh/glab/git 原生回退),再计算 diff 规模与 scope 信号。
  3. 确保安全专项上场:涉及认证或大后端改动时安全专项会自动派发;若不放心,在请求中追加 --security--all-specialists 强制带上(review/SKILL.md)。
  4. 按类别复核:将子代理输出的安全发现对照本文七类清单复核严重度与置信度,再走 Fix-First 决策。
  5. 把安全审查沉淀为学习:gstack 的 learnings 机制支持将「模式命中却漏报」「低置信度但真实」的校准事件写回,让后续专项越审越准。

安全专项清单本身(review/specialists/security.md)是一份与具体框架解耦的通用核对表,Rails、Django、Express、React/Vue 等项目均可直接套用;配套的 testing.mdred-team.md 则分别从「安全行为的测试护栏」与「对抗性寻找漏网之鱼」两个方向补全了同一闭环。

<输出文章>

登录后查看全文
热门项目推荐
相关项目推荐