1. 从一个真实的安全审计翻车现场说起去年帮一个朋友看他们团队刚上线两周的 Node.js 服务代码仓库里跑过两轮静态扫描SonarQube 报告干干净净依赖漏洞扫描也没报高危。结果上线第十天数据库里多了一批来路不明的管理员账号。事后复盘问题出在一个谁都没注意的地方一个内部用的调试接口鉴权中间件写成了if (req.headers[x-debug] true) return next()而这个 header 在网关层是允许透传的。静态扫描扫不出这种逻辑漏洞依赖扫描更不可能发现因为漏洞不在依赖里在业务代码的意图里。这件事之后我开始认真思考一个问题coding agent 能不能承担一部分安全审计的工作不是替代专业的安全工程师而是把那些人眼容易漏、规则引擎扫不到的逻辑层问题交给一个能读懂代码语义的 agent 去过一遍。security-audit-skill这个标题本质上就是在讨论这件事——把安全审计能力封装成一个可复用的 skill挂载到 coding agent 上让 agent 在写代码、改代码的同时顺手做一轮有语义理解能力的安全检查。这篇文章适合三类人看一是正在做 AI coding agent 工具链的工程师想知道安全审计这个 skill 该怎么设计二是团队里负责代码质量、想引入自动化安全审查的开发负责人三是自己写 side project、没有专职安全同学、但又不想上线就被人拖库的独立开发者。我会从 skill 的定位、审计维度的拆解、prompt 与工具链的设计、误报处理、以及实际落地时踩过的坑这几个角度把这件事讲透。核心关键词security-audit-skill、security-audit、coding-agent会贯穿全文但我不打算写成一份说明书而是按我实际搭这套东西的顺序来讲。先说结论免得你看到一半发现方向不对security-audit-skill 的价值不在于发现所有漏洞而在于用可解释的方式把高风险代码模式稳定地标记出来并给出修复建议。它的定位是第一道语义过滤器不是最后一道防线。想清楚这一点后面所有的设计取舍都会顺很多。2. security-audit-skill 到底该审计什么把安全拆成 agent 能执行的维度很多人一上来就想让 agent检查代码安不安全这个指令太虚了agent 会给你一堆泛泛而谈的建议比如建议使用参数化查询建议对输入做校验全是正确的废话。真正能落地的做法是把安全审计拆成一组具体、可判定、有明确触发条件的审计维度每个维度对应一类代码模式。下面这张表是我实际用的维度清单你可以直接拿去改。审计维度典型触发模式风险等级agent 可判定性鉴权绕过调试开关、header 透传、条件短路高高注入类字符串拼接 SQL/命令/模板高高敏感信息硬编码密钥、token、连接串字面量高高越权访问资源 ID 直接来自请求参数高中反序列化不可信数据进入反序列化函数高中路径穿越用户输入拼接文件路径中高日志泄露敏感字段写入日志中中依赖风险已知问题版本、废弃包中高加密误用弱算法、固定 IV、ECB 模式中高竞态条件检查与使用分离TOCTOU中低这张表里最关键的一列是agent 可判定性。不是所有安全问题都适合交给 agent。比如竞态条件需要理解并发时序agent 静态读代码很难稳定判断硬做只会制造大量误报。而鉴权绕过、注入、硬编码这三类触发模式明确、上下文局部agent 判定的准确率就高得多。我的建议是第一版 skill 只做可判定性高的维度把中低难度的先放一放等误报率压下来了再逐步扩展。2.1 为什么鉴权绕过要放在第一位回到开头那个案例。鉴权绕过之所以危险是因为它不违反任何语法规则也不触发任何已知漏洞特征。代码能跑、测试能过、扫描不报但攻击者只要知道那个 header 的存在就能长驱直入。这类问题的共同特征是存在一条正常情况下不该走通的代码路径但代码里没有任何显式约束阻止它走通。agent 要识别这类问题靠的不是模式匹配而是语义推理读到一个条件分支问自己这个条件在什么情况下为真为真之后跳过了哪些检查这些检查是不是安全关键这个推理链条恰恰是 coding agent 擅长而规则引擎不擅长的。所以我在设计 skill 时把鉴权相关的检查单独拎出来给了它最高的优先级和最详细的 prompt 指引。2.2 注入类问题的判定边界注入类问题看起来最好判定——找字符串拼接嘛。但实际做下来边界比想象中模糊。比如# 情况 A明显有问题 query SELECT * FROM users WHERE name name # 情况 B看起来有问题实际安全 query SELECT * FROM users WHERE name %s % escaped_name # 情况 C看起来安全实际有问题 query fSELECT * FROM users WHERE name {name}情况 B 用了%格式化但escaped_name已经过转义实际安全情况 C 用了 f-string看起来现代但直接把name插进去了照样注入。光看语法形式判断不了必须看数据流。所以 skill 里对注入的判定我要求 agent 必须追踪变量的来源这个变量是从请求参数来的还是从常量、配置、内部计算来的只有来自不可信输入 未经净化 进入危险函数三个条件同时满足才报高危。这个三条件规则把注入类的误报率从最初的 40% 压到了 10% 以内。2.3 硬编码密钥一个容易被低估的重灾区硬编码密钥这件事很多团队觉得我们不会犯。但我实际审计过的仓库里超过六成存在至少一处硬编码的敏感信息只是有的在测试代码里、有的在注释里、有的在已经废弃的分支里。agent 做这件事的优势在于它可以一次性扫完整个仓库包括那些没人看的角落而且不会因为这是测试文件就自动放过——测试文件里的真实密钥泄露出去一样是事故。这里有个实操细节判定硬编码不能只看字符串长得像不像密钥。sk-xxxxxxxx这种一眼假但a1b2c3d4e5f6这种十六进制串可能是密钥也可能是颜色值、哈希、测试 ID。我的做法是让 agent 结合变量名 上下文 字符串熵值三个信号综合判断变量名叫api_key、secret、token、password的熵值又高的才报。纯靠正则匹配[A-Za-z0-9]{32}会淹死在误报里。3. 把审计能力封装成 skillprompt、工具与执行流程的设计拆完维度接下来是工程实现。security-audit-skill作为一个挂载在 coding agent 上的能力单元它的结构可以拆成三部分触发机制、审计 prompt、工具调用。这三部分设计得好不好直接决定 skill 是能用还是好用。3.1 触发机制什么时候该跑安全审计最偷懒的做法是每次提交都全量跑一遍但这样又慢又吵开发者很快就学会无视它。我的设计是分级触发增量触发agent 每次修改代码后只对改动的文件及其直接依赖做审计。这是默认行为快噪音小。全量触发手动命令触发或者每天定时跑一次覆盖整个仓库。敏感路径强制触发当改动涉及鉴权中间件、数据库访问层、文件操作、反序列化入口这些高危区域时无论改动多小都强制跑对应维度的审计。这个分级设计背后的逻辑是安全审计的成本要和改动的风险成正比。改一个文案没必要跑全量动一行鉴权代码值得多花几秒。我实测下来增量触发平均耗时 2-3 秒全量触发视仓库大小在 30 秒到几分钟不等开发者基本无感。3.2 审计 prompt 的写法从检查安全到扮演攻击者prompt 是 skill 的灵魂。我试过很多版本最后发现效果最好的一种写法是让 agent 扮演攻击者而不是检查者。区别在哪检查者视角的 prompt 是请检查以下代码是否存在安全问题。 agent 会给你一份不痛不痒的清单。攻击者视角的 prompt 是你是一个攻击者你拿到了这个服务的访问权限但只有普通用户身份。请找出代码中任何能让你提升权限、读取他人数据、或执行任意代码的路径。 这个视角一换agent 的注意力会立刻聚焦到数据流和权限边界上而不是泛泛地谈最佳实践。我实际用的 prompt 骨架大致是这样简化版你正在对一个代码变更做安全审计。你的身份是攻击者目标是找到可利用的路径。 审计范围{变更的文件列表} 审计维度{本次启用的维度如鉴权绕过、注入、硬编码} 对每个维度按以下步骤推理 1. 找到该维度相关的代码入口点 2. 追踪不可信数据的来源和流向 3. 判断是否存在数据未经净化就进入危险操作的路径 4. 如果存在给出具体的利用场景和修复建议 输出要求 - 只报你能构造出具体利用场景的问题 - 每个问题必须包含文件位置、代码片段、利用路径、修复建议 - 如果某个维度没有发现问题明确说未发现不要凑数注意最后一条不要凑数。这是压误报的关键。agent 有很强的讨好倾向你让它找问题它就会努力找出问题哪怕问题很牵强。明确告诉它没发现就说没发现能砍掉一大半噪音。3.3 工具调用让 agent 能动手验证光靠读代码agent 的判断始终是推测。要提升准确率得给它工具。我在 skill 里挂了三类工具代码搜索工具让 agent 能跨文件追踪一个函数的调用链。比如发现一个鉴权函数agent 可以搜谁调用了它有没有地方绕过了它。依赖查询工具查一个包在当前版本有没有已知问题。这个直接对接公开的漏洞数据库即可。配置读取工具读网关配置、环境变量定义、部署清单判断某个 header 是否真的会被透传、某个开关在生产环境是否为真。第三类工具特别重要。开头那个案例里如果 agent 只能读代码它会看到x-debug这个 header 判断但不知道网关会不会透传它只能报疑似。有了配置读取工具agent 能确认网关配置里x-debug在透传白名单中就能把疑似升级为确认。从疑似到确认就是 skill 从玩具到工具的分界线。3.4 执行流程一次完整的审计长什么样把上面几块拼起来一次完整的 security-audit-skill 执行流程是这样的收集变更拿到本次改动的文件列表和 diff。识别高危区域扫描 diff判断是否触及鉴权、数据访问、文件操作等高危路径。选择审计维度根据高危区域启用对应的审计维度子集。逐维度推理对每个启用的维度按攻击者视角的 prompt 做推理必要时调用工具验证。汇总去重把各维度的发现合并去掉重复项按风险等级排序。生成报告输出结构化报告每条包含位置、利用路径、修复建议。这个流程里第 2 步和第 3 步是很多人会忽略的。不是每次审计都要跑所有维度。改一个前端组件跑注入审计纯属浪费。精准地选择维度既省时间又减少噪音。4. 误报处理security-audit-skill 能不能活下去的关键我见过太多安全工具死在误报上。第一周大家还新鲜第二周开始有人抱怨又误报第三周直接加白名单忽略第四周这个工具就名存实亡了。误报率是安全审计 skill 的生命线比漏报率还重要。漏报你顶多没发现一个问题误报会让整个工具被弃用。4.1 误报的三个主要来源做下来误报基本来自三个地方第一上下文缺失。agent 看到一段拼接 SQL 的代码但不知道这个变量其实来自内部枚举不是用户输入。这类误报靠追踪数据来源能解决大半。第二语义歧义。比如一个函数叫checkPermissionagent 看到它返回true就认为有权限但实际这个函数的语义是检查是否需要权限返回值含义相反。这类误报要靠读函数实现不只看函数名来避免。第三过度保守。agent 不确定的时候倾向于报出来宁可错杀。这类误报要靠 prompt 里明确只报能构造利用场景的问题来压制。4.2 分级输出把确定和疑似分开我的做法是把输出分成两级confirmed能构造出具体利用路径的和suspected模式可疑但无法确认的。confirmed 的问题必须修suspected 的问题供参考。这样开发者不会被一堆疑似淹没同时也不会漏掉真正危险的。实际报告长这样[CONFIRMED] 鉴权绕过 - src/middleware/auth.js:42 利用路径攻击者发送 header X-Debug: true中间件直接放行 绕过后续所有权限检查。网关配置确认该 header 会透传。 修复建议移除调试分支或将其限制在非生产环境且校验来源 IP。 [SUSPECTED] 注入风险 - src/dao/user.js:88 模式字符串拼接进入 query 函数。 待确认变量 userId 的来源未在本次变更范围内需人工确认是否为用户输入。这个分级让报告的可信度大幅提升。confirmed 的条目开发者点开就能复现自然愿意修。4.3 反馈闭环让 skill 越用越准误报处理不是一次性的得建立反馈机制。我在 skill 里加了一个简单的反馈通道开发者可以对任何一条报告标记误报或确认这些标记会被记录下来。定期回顾这些标记就能发现 prompt 或规则的系统性偏差。比如有一阵子agent 频繁把测试文件里的 mock 密钥报成硬编码。我看了反馈记录发现是 prompt 里没区分测试和生产。于是在 prompt 里加了一句测试文件中的 mock 数据如果明显是占位符如test-key、xxx不报。误报立刻降下来了。这种基于真实反馈的迭代比一开始就把 prompt 写到完美要现实得多。5. 落地时踩过的坑与几条硬核经验前面讲的都是设计层面的东西这一节讲实操。这套 skill 我从零搭到团队里稳定用起来中间踩的坑不少挑几个最有代表性的说。5.1 坑一agent 会脑补不存在的代码早期版本里agent 经常报告一些代码里根本没有的问题。比如它说第 50 行存在 SQL 注入但那个文件只有 30 行。原因是 agent 在推理时会根据常见模式想象出一段代码然后基于想象的内容做判断。解决办法是强制 agent 引用原文。要求每条报告必须附带从文件里精确复制的代码片段且片段必须能在文件里找到。如果 agent 引用的代码找不到这条报告直接丢弃。这个约束加上之后脑补问题基本消失。5.2 坑二大仓库里 agent 会迷路仓库一大文件一多agent 的注意力会被稀释。它可能花大量时间读无关文件最后对真正危险的改动反而没看仔细。我的应对是严格限定审计范围只把变更的文件和它们的直接依赖喂给 agent其余的一律不给。如果 agent 需要看别的文件通过工具主动去取而不是一股脑塞给它。范围越小注意力越集中判断越准。5.3 坑三修复建议不能太教科书一开始 agent 给的修复建议都是使用参数化查询对输入做校验这种。开发者看了等于没看。后来我在 prompt 里要求修复建议必须给出可直接替换的代码。比如不要写使用参数化查询而是写// 修改前 const sql SELECT * FROM users WHERE id ${userId}; // 修改后 const sql SELECT * FROM users WHERE id ?; db.query(sql, [userId]);这个改动让修复建议的采纳率翻了一倍多。开发者要的不是你应该怎么做而是你直接告诉我改成什么。5.4 坑四别让 skill 拖慢开发节奏安全审计再重要也不能让开发者等。我定的硬指标是增量审计必须在 5 秒内出结果。超过这个时间开发者就会开始烦躁进而想办法绕过。为了达标我做了几件事审计维度按需启用、工具调用加缓存、报告只输出 confirmed 和少量高价值 suspected。性能是安全工具能否被接受的前提不是加分项。5.5 几条可以直接抄的经验最后把几条最实用的经验列出来你可以直接拿去用先做减法第一版只做鉴权、注入、硬编码三个维度跑顺了再加。攻击者视角写 prompt让 agent 想我怎么攻破它而不是它安不安全。强制引用原文报告里的代码片段必须能在文件里找到杜绝脑补。分级输出confirmed 和 suspected 分开别让开发者被噪音淹没。修复建议给代码直接给替换后的代码别给原则。建立反馈闭环让开发者能标记误报定期回顾迭代 prompt。性能优先增量审计 5 秒内出结果这是底线。这套东西搭起来之后我们团队上线前的安全自查从靠人肉 review 碰运气变成了agent 先过一遍人再看 confirmed 的条目。开头那种鉴权绕过的问题现在基本在提交阶段就会被拦下来。当然它替代不了专业的安全审计深度渗透测试、业务逻辑漏洞、架构层面的安全设计还是得靠人。但作为第一道语义过滤器security-audit-skill 已经证明了自己的价值——它把那些规则引擎扫不到、人眼容易漏的逻辑层问题用一种可解释、可复现的方式稳定地标记了出来。对我来说这就够了。