gstack 代码评审安全专项(Security Specialist)清单解析:从认证绕过到攻击面扩张的多层防线
本文围绕 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 做结构化审查。其核心评审路径分两步:
- Step 4 Critical pass(主评审):应用 CRITICAL 类别,覆盖 SQL & Data Safety、Race Conditions & Concurrency、LLM Output Trust Boundary、Shell Injection、Enum & Value Completeness,再叠加若干 INFORMATIONAL 类别。
- 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_LINES 由 git 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 的派发逻辑一一对应:
- review/specialists/security.md(本文主体)
- review/specialists/testing.md
- review/specialists/maintainability.md
- review/specialists/performance.md
- review/specialists/data-migration.md
- review/specialists/api-contract.md
- review/specialists/red-team.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 |
是 | CRITICAL 或 INFORMATIONAL(与主评审的 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 校验未检查过期时间
从安全设计原则看,这六条覆盖了认证与授权的两类典型缺陷:
- 默认策略错误:「默认放行、显式封禁」在任何权限系统里都是危险的,正确范式是 fail-closed——未命中任何规则即拒绝,并把匿名请求显式排除在敏感路由之外。
- 信任用户的输入作为身份边界: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 的subprocesslist 参数),而不是把用户输入拼进 shell 字符串。 - 模板注入:用户内容只能作为数据渲染,绝不能作为模板本身编译。
- SSRF:fetch/redirect/webhook 目标必须校验协议与目标地址白名单(如仅允许 https + 固定域名),并注意 DNS rebinding 这类绕过手段。
- 路径穿越:用规范化后的路径做前缀与归属校验(如 path.resolve 后校验仍位于允许的根目录内)。
- 头注入:禁止将未过滤的用户输入(尤其是换行符)拼入 Set-Cookie 或自定义响应头。
七、密码学误用(Cryptographic Misuse)
密码学是「看似可用、实则脆弱」的高发区。清单给出五条具体检查点:
- 对安全敏感操作使用弱哈希算法(MD5、SHA1)
- 用可预测随机源(
Math.random、rand())生成令牌或密钥 - 对密钥、令牌或摘要使用非常量时间比较(
==) - 硬编码加密密钥或 IV
- 密码哈希缺少盐值(salt)
这些点各自对应一个明确的攻击面:
| 检查点 | 典型反例 | 正确实践 |
|---|---|---|
| 弱哈希 | 用 MD5/SHA1 存密码或做签名 | 密码使用 bcrypt/argon2/scrypt 等专用 KDF;消息完整性用 SHA-256 及以上 |
| 可预测随机 | Math.random() 生成重置令牌、API key |
使用 CSPRNG(crypto.randomBytes、secrets.token_*) |
| 非常量时间比较 | 用 == 比较签名/令牌 |
使用语言内置的恒定时间比较函数(crypto.timingSafeEqual 等),防止时序侧信道 |
| 硬编码密钥/IV | 源码中出现静态密钥、固定 IV | 密钥走密钥管理系统或环境注入;IV 每次加密随机生成 |
| 密码哈希缺盐 | 直接哈希明文密码 | 加随机盐,且由 KDF 内建处理(如 bcrypt 自动加盐) |
评审时要特别注意:这些缺陷往往藏在「看起来工作正常」的代码里,例如重置信令牌用了时间戳种子、JWT 密钥写死在配置注释中、AES 使用了可预测的固定 IV。专项评审的价值就是逐条把它们从「能跑」的代码里挑出来。
八、密钥与敏感信息暴露(Secrets Exposure)
第五类关注敏感数据在不该出现的地方出现:
- 源码中出现 API key、令牌或密码(包括注释里)
- 密钥被写进应用日志或错误消息
- URL 中出现凭据(query 参数或 URL 中的 basic auth)
- 敏感数据出现在返回给用户的错误响应中
- 明文存储本应加密的 PII
这一类评审的三条主线:
- 搜索线索要广:不仅扫赋值语句,还要扫注释、示例代码块、测试夹具、前端打包产物(硬编码密钥极易以「临时方便」的形式混入)。
- 间接泄露同样致命:日志打点、异常消息透传、把第三方服务报错原样抛给用户,都可能把内部凭据或数据库细节泄露给攻击者。
- 加密与脱敏预期:凡是数据模型里标注「应加密」的字段以明文落库、凡是应在日志中脱敏的字段原样输出,都属于需要上报的类别。
gstack 仓库自身在 scripts/jargon-list.json、lib/redact-engine.ts 等处存在大量与「敏感信息处理、redaction、审计」相关的工程实践,侧面印证了这类问题的普遍性与重视程度;对普通业务仓库做安全评审时,应同样把「日志/错误/URL」三个外泄出口作为必查位置。
九、经逃生舱位触发的 XSS(XSS via Escape Hatches)
现代前端框架默认做输出转义,因此 XSS 高发点集中在显式关闭转义的逃生舱上:
- Rails:对用户可控数据使用
.html_safe、raw() - React:用
dangerouslySetInnerHTML渲染用户内容 - Vue:用
v-html渲染用户内容 - Django:对用户输入使用
|safe、mark_safe() - 通用:用未净化数据做
innerHTML赋值
逃生舱检查的本质是:每一个绕过框架默认转义的调用点,都构成一次手动安全审计义务。遇到上述 API 时,评审者应追溯数据源头:
- 数据是否最终来自用户输入、URL 参数、数据库中的第三方内容?
- 在此之前是否经过上下文敏感的净化(如 DOMPurify 等基于解析器的清洗,而非正则黑名单)?
- 即使来源「看起来可信」,是否可能被间接污染(如存储型 XSS 中管理端输入最终渲染到用户端)?
若答案是「逃生舱 + 不可信来源」,即为一条确定的 XSS 发现。注意:这类问题光靠自动化扫描器很难排除,框架感知的代码评审(检查 dangerouslySetInnerHTML、v-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」视角运行的二次审查,专门寻找其他专项之间的夹缝、跨类别组合问题与集成边界故障。安全性发现由此成为整条流水线风险升级的触发器,进一步印证了它作为「保险型专项」的设计意图。
十二、在项目中使用这套评审体系
要在真实代码库上运行这套安全评审,建议路径如下:
- 配置 gstack:参照仓库根目录的 CLAUDE.md 与 SKILL.md 完成 skill 接入,使
/review(以及/plan-*-review、/ship等)可被正常调用。 - 发起评审:在待合入分支上运行
/review。流水线会先探测平台与基线分支(gh/glab/git 原生回退),再计算 diff 规模与 scope 信号。 - 确保安全专项上场:涉及认证或大后端改动时安全专项会自动派发;若不放心,在请求中追加
--security或--all-specialists强制带上(review/SKILL.md)。 - 按类别复核:将子代理输出的安全发现对照本文七类清单复核严重度与置信度,再走 Fix-First 决策。
- 把安全审查沉淀为学习:gstack 的 learnings 机制支持将「模式命中却漏报」「低置信度但真实」的校准事件写回,让后续专项越审越准。
安全专项清单本身(review/specialists/security.md)是一份与具体框架解耦的通用核对表,Rails、Django、Express、React/Vue 等项目均可直接套用;配套的 testing.md 与 red-team.md 则分别从「安全行为的测试护栏」与「对抗性寻找漏网之鱼」两个方向补全了同一闭环。
<输出文章>
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00