ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

security-audit-skill:为编码智能体注入安全审计能力

security-audit-skill:为编码智能体注入安全审计能力

1. 从"security-audit-skill"这个名字说起:它到底在解决什么问题

第一次看到security-audit-skill这个命名,我的直觉是:这大概率是一个面向编码智能体(coding-agent)的技能包,而不是一个独立的扫描器。为什么这么判断?因为-skill这个后缀在当下的智能体生态里已经形成了约定俗成的语义——它代表一段可被智能体加载、调用、编排的能力单元,通常包含提示词模板、工具调用声明、执行流程定义这几样东西。而security-audit则明确了它的职责边界:安全审计。

把这两个词拼在一起,含义就很清楚了:让编码智能体具备执行安全审计的能力。这件事的价值在于,传统安全审计工具(SAST、SCA、密钥扫描)大多是"独立进程 + 报告输出"的模式,人要去读报告、判断优先级、再回到代码里改。而把审计能力做成智能体的 skill 之后,审计这件事就被嵌进了"写代码—审代码—改代码"的同一个闭环里,智能体可以在生成代码后立刻自查,也可以在 review 阶段主动扫描,甚至能结合上下文解释"为什么这行有风险"。

我之所以对这个方向感兴趣,是因为过去几年我见过太多团队的安全工具"装了但没人用"。SonarQube 挂在 CI 里,报告红了一片,开发看一眼就关掉;Semgrep 规则写了几十条,误报率高到大家直接忽略。问题不在于工具不好,而在于审计结果和修复动作之间隔了太远。security-audit-skill这类东西试图缩短的正是这段距离。

这篇文章适合三类人看:一是正在给自家 coding-agent 扩展能力的工程师,想知道一个安全审计 skill 该怎么设计;二是安全工程师,想理解智能体场景下的审计逻辑和传统 SAST 有什么不同;三是技术负责人,在评估"要不要把安全审计接进 AI 编码流程"。我会从能力边界、核心机制、实操落地、误报治理、进阶扩展几个角度把它拆开讲,尽量给到能直接抄的配置和踩过的坑。

需要先说明一点:security-audit-skill目前没有公开的、标准化的官方规范,不同团队实现差异很大。下面讲的内容,一部分来自我对这类 skill 的通用理解,一部分来自实际项目里落地类似能力的经验,涉及具体实现的地方我会标注哪些是"常见做法"、哪些是"我的取舍"。

2. 一个安全审计 skill 的能力边界:它能做什么,绝对不能指望它做什么

2.1 它擅长的是"模式识别 + 上下文解释",不是"形式化验证"

很多人对 AI 安全审计有个误解,觉得它能像定理证明器一样,把代码的所有执行路径都推一遍,然后给出"绝对安全"或"绝对有漏洞"的结论。这是不现实的。智能体做审计,本质上是基于模式识别和上下文推理:它见过大量漏洞样本,能识别出"这段代码长得像 SQL 注入",再结合变量来源、调用链、框架约定去判断风险是否真实存在。

这个定位决定了它的强项和弱项。强项是:能理解语义、能跨文件追踪数据流、能用自然语言解释风险、能给出符合项目风格的修复建议。弱项是:对复杂的控制流、加密算法正确性、并发竞态这类需要严格推理的问题,它的判断可靠性远不如专门的静态分析工具。

我一般会把审计任务分成三档,对应不同的处理策略:

风险类型典型例子适合交给 skill 吗原因
模式明确型硬编码密钥、eval拼接、innerHTML赋值非常适合特征明显,误报可控
数据流依赖型SQL 注入、命令注入、路径穿越适合,但需上下文要追踪污点传播,智能体有优势
逻辑与算法型权限绕过、加密误用、竞态条件谨慎使用需要形式化推理,易漏报

2.2 它和传统 SAST 的分工关系

我从来不认为 skill 是要取代 SAST。两者的定位完全不同。SAST 工具(比如基于 AST 和污点分析的引擎)的优势是确定性、可重复、覆盖全,它能把所有代码都过一遍,规则命中就是命中,不会因为"今天模型心情不好"而漏掉。缺点是误报多、不理解业务语义、修复建议生硬。

skill 的优势是语义理解、上下文感知、解释能力强,它能把 SAST 报出来的一堆告警做二次筛选,判断哪些是真问题、哪些是误报,还能结合项目实际给出可落地的修复。缺点是覆盖不稳定、有幻觉风险、成本比跑规则高。

所以我的实践方案是两层过滤:第一层用 SAST 或正则规则做全量粗筛,把可疑点捞出来;第二层用 security-audit-skill 做精筛和解释。这样既保证了覆盖率,又压低了误报,还省了 token 成本。这个组合我在几个项目里跑下来,效果比单用任何一层都好。

2.3 明确不该交给它的场景

有几类事情我坚决不交给 skill 做,这里也提醒一下:

  • 合规性判定:比如"这段代码是否符合某标准某条款",这需要人来做最终判断,skill 只能提供参考。
  • 密钥泄露的最终确认:skill 可以标记"这里疑似有密钥",但真伪、是否已泄露、影响范围,必须人工核实。
  • 生产环境的实时拦截:skill 是审计工具,不是 WAF,不要把它放在请求链路上做实时阻断,延迟和稳定性都扛不住。

提示:把 skill 定位成"审计助手"而不是"审计裁判",这个心态摆正了,后面很多设计决策都会顺。

3. 拆开看核心机制:一个审计 skill 内部到底跑了什么

3.1 输入层:喂给它的不只是代码

一个设计良好的 security-audit-skill,输入绝对不只是"一段代码"。我见过做得比较扎实的实现,输入通常包含这几块:

  • 待审代码:可以是 diff、单个文件、或整个仓库的相关片段。
  • 项目上下文:技术栈、框架版本、依赖清单。同样是query()调用,在 ORM 框架里和在裸 SQL 拼接里,风险等级完全不同。
  • 审计规则或关注点:这次是查注入、查密钥、还是查权限,聚焦范围能显著提升准确率。
  • 历史基线:之前审过的问题、已知的误报模式,用来做去重和抑制。

我特别想强调项目上下文这一块。很多团队做 skill 时只把代码丢进去,结果模型对框架约定一无所知,把框架已经做了转义的场景也报成 XSS,误报率直接爆炸。把框架信息、依赖版本一起喂进去,是压低误报最有效的手段之一。

3.2 分析层:从"看代码"到"追数据流"

分析层的核心动作,我总结为三步:定位可疑点 → 追踪数据流 → 评估可达性。

定位可疑点靠的是模式匹配,这一步和传统规则引擎类似,但智能体能理解变量命名和注释,能识别出"这个input变量其实来自用户请求"。追踪数据流是智能体的强项,它可以从一个 source(用户输入)出发,沿着赋值、传参、拼接一路追到 sink(危险函数),判断中间有没有经过净化。评估可达性则是判断这段代码在什么条件下会被执行,是不是死代码、是不是只有管理员能触发。

举个具体例子。假设代码里有这么一段:

def get_user(request): uid = request.GET.get("id") sql = "SELECT * FROM users WHERE id = " + uid return db.execute(sql)

一个合格的审计 skill 应该输出:uid来自request.GET(不可信 source),未经任何校验直接拼进 SQL 字符串(无净化),传入db.execute(sink),构成 SQL 注入。同时它应该建议用参数化查询改写。而一个不合格的实现可能只会说"这里用了字符串拼接,可能有注入风险",既没确认 source,也没确认 sink,这种输出对开发毫无价值。

3.3 输出层:报告要能直接指导修复

输出层是最容易被忽视、但最影响落地效果的一环。我见过太多 skill 输出一大段"该代码存在安全风险,建议修复"的废话。好的输出应该包含:

  1. 问题定位:文件、行号、涉及的变量。
  2. 风险类型与等级:明确是注入、XSS 还是密钥泄露,给出高/中/低。
  3. 数据流路径:从哪来、到哪去、中间经过了什么。
  4. 修复建议:最好直接给出改写后的代码片段。
  5. 置信度:明确告诉使用者这个判断有多确定,方便人工复核。

其中置信度这一项,是我强烈建议加上的。智能体审计必然有误报,与其假装自己很准,不如诚实地标注"高置信/需人工确认",让使用者知道哪些可以直接改、哪些要再看一眼。这个设计能极大提升团队对工具的信任度。

4. 落地实操:把 security-audit-skill 接进编码流程的完整步骤

4.1 环境与依赖准备

假设你要在一个基于智能体的编码工具里挂载这个 skill,准备工作大致分三块。第一块是确定 skill 的载体格式。目前主流做法有两种:一种是把 skill 写成一个带元数据的目录(含提示词、工具声明、示例),另一种是直接作为系统提示的一部分注入。前者更规范、可复用,后者更轻量、上手快。我一般推荐前者,因为安全审计的提示词往往很长,塞进系统提示会污染其他任务。

第二块是准备审计所需的工具。skill 本身通常不直接读文件,而是通过工具调用获取代码。你需要给它配好:文件读取工具、代码搜索工具(grep 类)、依赖清单读取工具。如果要做数据流分析,最好再配一个能查调用关系的工具。

第三块是准备规则库和基线。把团队关注的漏洞类型整理成清单,把历史误报整理成抑制规则。这两样东西是 skill 准确率的命脉,比提示词写得多漂亮都重要。

4.2 提示词结构的设计要点

提示词是 skill 的灵魂。我踩过的坑是:一开始把提示词写得太"全面",恨不得把所有漏洞类型都塞进去,结果模型注意力被稀释,每类都审得很浅。后来改成按需聚焦,一次审计只关注 2-3 类风险,准确率明显提升。

一个我常用的提示词骨架是这样的:

角色:你是一名代码安全审计员,负责识别 [本次关注的风险类型]。 输入: - 代码片段:{code} - 技术栈:{stack} - 依赖版本:{deps} 审计要求: 1. 对每个可疑点,必须确认 source(不可信输入来源)和 sink(危险操作)。 2. 追踪数据流,说明中间是否经过净化处理。 3. 输出格式:文件:行号 | 风险类型 | 等级 | 数据流路径 | 修复建议 | 置信度。 4. 无法确认 source 或 sink 的,标记为"需人工确认",不要臆断。 5. 不要报告纯风格问题,只报安全风险。 约束: - 如果代码中不存在本次关注的风险,明确回复"未发现"。 - 不要为了凑数而报告低置信度问题。

这里有几个细节值得说。"必须确认 source 和 sink"这条约束,能过滤掉大量"看到拼接就报注入"的误报。"无法确认就标记需人工确认"这条,给了模型一个诚实的出口,避免它硬编一个结论。"不要为了凑数而报告"这条,是针对模型"总想给点东西"的倾向做的抑制。

4.3 触发时机:什么时候调用这个 skill

skill 挂上去之后,什么时候触发是个关键设计。我实践下来有三种触发方式,各有适用场景:

  • 提交前自动触发:在代码提交或生成后自动跑一遍,只审 diff 部分。优点是及时,缺点是可能打断流程。
  • 按需手动触发:开发者主动调用,审指定文件或模块。优点是灵活,缺点是依赖自觉。
  • 定时全量触发:每天或每周对全仓库跑一次。优点是覆盖全,缺点是成本高、噪音大。

我的建议是以提交前自动触发为主,按需手动为辅。全量扫描交给传统 SAST,skill 专注在增量代码上做深度审计。这样成本可控,反馈也及时。

4.4 一个完整的调用示例

假设你的智能体支持通过命令调用 skill,一次典型的审计调用可能长这样:

# 审计当前分支相对主分支的改动 audit --skill security-audit-skill \ --scope diff \ --base main \ --focus injection,secrets \ --format markdown \ --output audit-report.md

跑完之后,报告里应该能看到类似这样的条目:

src/api/user.py:42 | SQL注入 | 高 | request.GET.id -> 字符串拼接 -> db.execute | 建议改用参数化查询:db.execute("SELECT * FROM users WHERE id = %s", [uid]) | 置信度:高

这种颗粒度的输出,开发拿到就能直接改,不需要再去理解"到底哪里有问题"。

5. 误报治理:让团队愿意用下去的关键

5.1 误报从哪来:三个主要来源

误报是安全审计工具的头号杀手。我观察下来,skill 的误报主要来自三个地方。

第一是上下文缺失。模型不知道框架已经做了转义,不知道这个变量其实经过了校验中间件,于是把安全的代码报成风险。这类误报占比最高,解决靠的是把上下文喂全。

第二是模式过度泛化。模型学到"字符串拼接 = 危险",于是把所有拼接都报一遍,包括拼接常量、拼接已经净化的值。这类误报靠提示词约束和示例来纠正。

第三是幻觉。模型凭空想象出一条不存在的数据流,或者把不相关的代码关联起来。这类误报最难治,只能靠置信度标注和人工复核兜底。

5.2 抑制机制:让误报不再重复出现

治理误报的核心思路是建立抑制基线。具体做法是:每次人工确认一个误报后,把它记录到抑制规则里,下次审计时自动跳过或降级。抑制规则的粒度可以很细,比如"文件 X 的第 Y 行,风险类型 Z,标记为误报"。

我一般会维护一个audit-suppressions.yaml,结构大致如下:

suppressions: - file: "src/utils/crypto.py" rule: "weak-hash" reason: "此处使用 MD5 仅用于非安全场景的缓存键计算,已人工确认" expires: "2025-12-31" - file: "src/api/legacy.py" rule: "sql-injection" line: 88 reason: "该参数已在上游中间件强制校验为整数,误报"

注意我加了expires字段。抑制规则不能永久有效,因为代码会变,今天安全的写法明天可能就不安全了。定期让抑制规则过期,强制重新审视,是个好习惯。

5.3 用置信度分层,而不是一刀切

与其纠结"这个到底是不是误报",不如用置信度把结果分层,让使用者自己决定怎么处理。我的分层策略是:

  • 高置信:source、sink、数据流都清晰,直接进修复队列。
  • 中置信:部分环节需要确认,进人工复核队列。
  • 低置信:仅模式匹配,无数据流支撑,只做记录不打扰。

这样团队不会被低置信的噪音淹没,高置信的问题又能被及时处理。实测下来,这个分层能让"有效告警占比"从三成提到七成以上。

6. 进阶玩法:把审计 skill 从"能用"做到"好用"

6.1 结合依赖清单做供应链审计

安全审计不只是审自己写的代码,依赖里的漏洞同样是重灾区。把依赖清单(比如package-lock.json、requirements.txt)作为输入喂给 skill,让它结合已知的漏洞模式做判断,能覆盖到供应链这一层。不过要注意,依赖漏洞的权威信息应该来自漏洞库,skill 的角色是解释影响和给出升级建议,而不是自己判断某个版本有没有漏洞。

6.2 让 skill 学会项目的"安全约定"

每个项目都有自己的安全约定,比如"所有数据库访问必须走 ORM"、"所有外部输入必须过校验层"。把这些约定写进 skill 的知识库,它就能判断代码是否违反了项目自身的规范,而不只是通用漏洞。这类"项目级审计"的价值往往比通用扫描更高,因为它贴合实际。

6.3 审计结果的趋势追踪

单次审计的价值有限,把多次审计的结果存下来做趋势分析,价值就上来了。比如"本周新增高危问题 3 个,修复 5 个"、"某模块连续三周有同类问题",这些趋势能帮团队发现系统性的安全短板。我一般会把每次审计结果落库,用一个简单的看板做可视化。

6.4 和修复环节打通

审计的终点是修复。如果 skill 能直接生成修复补丁,甚至自动提交 PR,闭环就完整了。但这里要非常谨慎:自动修复必须经过人工确认才能合并。我见过自动改代码把功能改坏的案例,安全修复引入功能 bug 是得不偿失的。稳妥的做法是 skill 生成建议补丁,人工 review 后再合并。

7. 我在实际落地中踩过的几个坑

第一个坑是提示词太长导致注意力涣散。前面提过,一开始我追求"大而全",结果每类风险都审得很浅。后来改成按需聚焦,一次只审 2-3 类,效果立竿见影。这个教训是:skill 的能力不是靠提示词堆出来的,而是靠聚焦换来的。

第二个坑是忽视 token 成本。全量审计一个中型仓库,token 消耗非常可观。我后来改成只审 diff、只审高风险模块,成本降了一个数量级,效果反而更好,因为噪音少了。

第三个坑是过度信任模型判断。早期我让 skill 直接输出"安全/不安全"的结论,结果有几次把真问题判成了安全,差点出事。后来改成只输出"发现的问题 + 置信度",把最终判断权交回给人,风险就控住了。

第四个坑是抑制规则没有过期机制。有段时间抑制规则越积越多,后来发现有些"已确认误报"的代码早就改了,抑制规则却还在,导致真问题被误抑制。加上expires字段后,这个问题才解决。

第五个坑是没有和团队沟通清楚定位。skill 刚上线时,有同事以为它能替代所有安全测试,结果发现它漏报了一些逻辑漏洞,就对整个方案失去信心。后来我明确跟大家说:这是辅助工具,覆盖的是模式明确和数据流类风险,逻辑漏洞还得靠人工和专项测试。定位说清楚了,接受度反而高了。

8. 关于这个方向,我个人的几点判断

security-audit-skill这类东西,本质上是把安全审计从"独立环节"变成"编码流程的内嵌能力"。这个方向我认为是对的,因为安全的根本问题是"发现太晚、修复太远",而把审计嵌进编码闭环,正好对症。

但它不会是银弹。智能体审计的可靠性受限于模型能力,短期内不可能替代专业的安全测试和人工审计。它最合适的定位是第一道过滤网:把明显的、模式化的问题挡在前面,让人工精力集中在真正复杂的逻辑漏洞上。

如果你正准备做类似的东西,我的建议是:先把能力边界划清楚,别贪大求全;把上下文喂全,这是准确率的命脉;把置信度和抑制机制做好,这是团队愿不愿意用的关键;最后,永远保留人工复核这一环,别让自动化冲昏头脑。

这套东西我前后迭代了大半年,从最初的"报一堆没人看"到现在的"报一个改一个",中间踩的坑基本都写在上面的章节里了。如果你也在做类似的事,希望这些经验能帮你少走点弯路。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进