1. 从一次代码 review 事故说起为什么 AI 写代码需要安全审计技能1.1 那一晚的接口代码看起来干净实际全是洞先交代一下背景。我在做一个内部数据查询系统为了赶进度用 AI 辅助写了个“按用户名搜索用户”的接口。AI 完成得很快代码结构也漂亮路由、服务、数据库访问分得清清楚楚还有错误处理。第一次 review 没看出任何问题代码就这么合进了开发分支。结果第二天要做安全自查我把这段代码翻出来仔细看冷汗直接下来了。用户名参数被字符串拼接进了 SQL 查询语句输入框没有任何校验登录接口的 JWT 密钥直接以明文写在配置文件里。三个问题随便哪个被真实请求打到线上都是灾难性的。那一刻我意识到单靠“事后人眼 review”来兜底 AI 生成代码的安全问题根本不现实。1.2 通用对话里的“帮我检查安全性”为什么不可靠可能有人会说那你让 AI 自己检查一遍不就行了我试过而且试过很多次。把同样的代码丢回对话框里说“帮我检查安全性”它能列出一些常见的套路化问题但结论经常是“整体安全性良好未发现明显高风险问题”。原因其实不复杂通用模型的知识是概率性的它默认输出的是“看起来合理的代码”而在没有明确审计指令时模型并不会自动开启安全分析模式。安全审计这件事本质上需要的是一整套规则驱动的检查流程。要看数据流从哪里进、到哪里落库要分辨哪些 API 是安全的参数化用法、哪些是危险的拼接写法要判断密钥是来自环境变量还是硬编码。这些检查点非常多靠每次临时聊天让 AI 自由发挥结果方差极大。同一个接口换个提问方式它可能就漏掉一半问题。1.3 低频高价值知识应该沉淀在哪里再往深一层想安全知识属于典型的“低频但高价值”知识。不是每个开发任务都涉及认证鉴权但一旦涉及漏掉一个关键检查点就可能出大事。而人脑在赶工时恰恰最容易忽略这类低频知识。我于是有了一个明确想法把安全审计做成一个可复用的“技能包”让 agent 在合适时机自动加载、执行、输出结构化报告。它有边界、有清单、有固定产出格式不依赖模型当时的心情。这就是security-audit-skill的由来。2. 把安全审计做成 skill机制选择和目录设计2.1 skill 的本质按需加载的操作手册不是常驻提示词先说清楚 skill 到底是什么。现在主流的 agent 工作台里skill 是一个独立目录里面放着“什么时候用、怎么用、有哪些检查规则、输出什么格式”的说明通常核心文件叫SKILL.md。当用户请求出现安全审计、代码评审、安全性检查这类关键词时agent 会主动去读这个目录按里面的流程执行。这个机制最关键的一点是按需加载。普通 system prompt 是每次对话都吃上下文窗口的写多了浪费写少了不够用。而 skill 平时不占上下文需要时才被读取用完即走。这对于规则量极大的安全审计场景尤其重要——一份完整的安全清单可能几千字如果每次都常驻在上下文里其他任务的执行质量都会被拖累。2.2 skill 和 agent 的区别执行者与知识包的关系讲 skill 绕不开一个问题skill 和 agent 到底什么关系两者经常放在一起讨论但职责不同。agent 是“行为的执行者”它负责理解任务、拆解步骤、调用工具、反馈结果强调的是自主决策能力。skill 则是一个“可复用的知识包和流程包”它本身不思考、不决策只是把某类任务的执行方法固化下来。一个 agent 可以挂多个 skill比如代码生成 skill、测试用例 skill、安全审计 skill。反过来一个 skill 也可以被不同 agent 复用。安全审计 skill 挂在编码 agent 上就是写完代码顺带自查挂在一个独立的 code review agent 上就是专职评审员。同样的规则不用重写第二遍。2.3 security-audit-skill 的目录结构与文件职责我的初始版本目录设计长这样security-audit-skill/ ├── SKILL.md ├── rules/ │ ├── owasp-top10.md │ ├── python.md │ ├── javascript.md │ ├── java.md │ └── general.md ├── scripts/ │ └── collect_file_list.py └── templates/ └── audit_report.md每个文件各司其职。SKILL.md是入口负责告诉 agent“什么场景触发、怎么编排流程、报告什么格式”。rules/目录放具体漏洞分类特别是按开发语言拆分因为不同语言的注入点和危险 API 差异非常大。scripts/里的辅助脚本负责在审计开始前收集项目文件清单避免 agent 漏看文件。templates/定义了报告模板强制审计结论落在统一结构里方便人快速扫描。这个结构的核心思路入口文件指挥流程规则库决定质量报告模板约束输出。三者缺一不可。3. SKILL.md 的核心设计审计规则分层与报告标准3.1 触发条件和执行流程编排SKILL.md的第一步是触发条件。我设定的触发词包括“安全审计”“代码评审”“安全性检查”“security audit”“review security”等等。触发之后agent 不是直接上手找漏洞而是按固定流程走范围收集读取项目文件列表找出需要审计的源码文件排除 lock 文件、构建产物和第三方依赖目录。数据流识别从入口点开始追踪用户可控数据的流向记录哪些数据最终进入了数据库查询、文件操作、命令执行或模板渲染。按规则逐项扫描对照rules/下的清单而不是自由发挥。扫描时记录“文件、行号、匹配到的模式、风险等级”四要素。输出报告按模板生成报告注明每个问题的证据行和修复方向。这四步里第二步最容易被忽略。很多安全问题不是单独一行代码触发而是“用户输入一路传递到了危险函数”。如果不做数据流追踪只看局部代码很容易漏掉跨文件的绕过场景。3.2 按语言拆解的检查项清单规则库是我花时间最多的部分。不同语言、不同框架危险写法完全不同。最初版本直接列了一张大通表后来发现效果很差Python 的审计重点和 JavaScript 不一样Java 重点在于反序列化JavaScript 重点在于原型链污染和命令拼接。一张表什么都列就是什么都查不细。我按语言拆成独立规则文件每个文件里又是一个小清单。以 JavaScript/Node.js 为例核心检查项包括检查项风险级别判定要点SQL 拼接高字符串模板直接传入query()或execute()命令执行高exec/spawn/execSync的参数包含用户输入JWT 硬编码密钥中密钥出现在配置文件或常量中而非环境变量敏感信息日志中console.log输出包含 password/token/secret 字段CORS 通配配置中Access-Control-Allow-Origin直接设置*且伴随便携带凭证危险文件操作高用户输入拼接到文件路径未做目录归一化校验后端语言之外的另一个大块是模板渲染和前端框架。React 的dangerouslySetInnerHTML、Vue 的v-html以及各种模板引擎的关闭自动转义指令都值得单独列规则。前端注入问题在后端审计里经常被漏因为检查视角不同。3.3 报告模板固定结构降低误判、方便人工复核报告模板解决的是“审计结果如何使用”的问题。早期版本让 AI 自由输出审计结论结果五花八门有时列成散文有时分条但没给定位有时直接给一段“整体安全性良好”的空话。后来我下了死规定输出必须长这样## 审计范围 - 目录src/ - 文件数12 - 审计语言JavaScript / Node.js ## 发现的问题按严重程度降序 ### 高危 - [SQL 注入] src/routes/user.js:17 证据username 直接拼接进 SELECT 语句 修复改用参数化查询 ### 中危 - [硬编码密钥] src/config.js:3 证据JWT_SECRET 以字符串字面量存在源码中 修复改用环境变量 process.env.JWT_SECRET ### 低危 - [敏感信息日志] src/routes/user.js:24 证据打印整个 user 对象 修复只输出 userId禁止输出 passwordHash ## 未发现问题的检查项 - 路径穿越未发现用户输入进入文件路径 - 反序列化未使用 eval 或 Function 构造器固定格式最大的好处是可复核。每条结论都能看到文件、行号、证据和修复方向人工 reviewer 不用重新翻全项目直接按报告的定位逐条确认就行。这个模板后来也成了我所有内部工具的统一规范。4. 实测一轮用 security-audit-skill 审一个登录接口4.1 构造一个典型的不安全示例仅用于演示为了说明完整链路我构造了一个极简但真实感很强的 Node.js 登录接口。下面这段代码包含三类问题严格来说是“反面教材”请勿复制到生产环境// src/routes/login.js const express require(express); const db require(../db); const jwt require(jsonwebtoken); const { exec } require(child_process); const router express.Router(); router.post(/login, (req, res) { const { username, password } req.body; // 问题1SQL 字符串拼接 const sql SELECT * FROM users WHERE username ${username} AND password ${password}; db.query(sql, (err, result) { if (err) return res.status(500).send(server error); if (result.length 0) return res.status(401).send(unauthorized); // 问题2硬编码 JWT 密钥 const token jwt.sign({ id: result[0].id }, my-super-secret-key, { expiresIn: 7d }); // 问题3命令执行 敏感信息日志 exec(nslookup ${username}, (execErr, stdout, stderr) { console.log(user login info:, { username, password, token }); }); res.json({ token }); }); }); module.exports router;这段代码不长但三类典型问题全都占齐了用户输入未经过滤直接进数据库查询、密钥硬编码在源码中、命令执行接口还顺带把密码打进了日志。我在日常审计里见过类似模式的真实代码远不止一次。4.2 完整调用过程与产出报告我把这段代码放进一个项目目录在 agent 里发起指令“对 src/ 下的代码做一次安全审计使用 security-audit-skill。”agent 按 SKILL.md 的流程走完四步后产出的报告如下## 审计范围 - 目录src/ - 文件数1 - 审计语言JavaScript / Node.js ## 发现的问题按严重程度降序 ### 高危 - [SQL 注入] src/routes/login.js:9 证据username 和 password 未经处理直接拼接进 SQL 查询字符串 修复改用参数化查询如 SELECT * FROM users WHERE username ? AND password ? - [命令注入] src/routes/login.js:20 证据用户可控的 username 被传入 exec()可能造成任意命令执行 修复移除该处命令调用或改用白名单校验输入格式后再执行 - [硬编码密钥] src/routes/login.js:16 证据JWT 密钥 my-super-secret-key 以字面量形式出现在源码中 修复读取环境变量 process.env.JWT_SECRET并确保环境变量已在部署平台配置 ### 中危 - [敏感信息泄露] src/routes/login.js:21 证据日志打印了 password 和 token 字段 修复日志中只保留 userId密码和 token 一律禁止打印 ## 未发现问题的检查项 - 路径穿越未发现用户输入进入文件路径 - 反序列化未使用 eval 或 Function 构造器 - CORS 配置未配置全局通配这份报告的价值不在于说了三件“有经验的人一眼能看出”的问题而在于它把所有问题按可执行的优先级摆出来了。我在实际使用中直接把这份报告转给了同事对方零成本就理解了问题在哪、怎么改。这比我口头说一句“你这代码有注入”要高效得多。4.3 修复建议如何落地改改完再看一轮审计报告只是第一步真正检验 skill 价值的是“改完之后的第二轮审计”。我把代码按报告修了一版// 修复后关键片段 const sql SELECT * FROM users WHERE username ? AND password ?; db.query(sql, [username, password], (err, result) { const token jwt.sign({ id: result[0].id }, process.env.JWT_SECRET, { expiresIn: 7d }); // 日志只记录非敏感字段 console.log(user login success:, { userId: result[0].id }); });修复后的代码没有命令执行、没有硬编码密钥、没有敏感日志。但这里有个更重要的点不要信任一次审计结果。我让 agent 对修复后的版本再做一遍安全审计确认原问题全部消失、没有引入新问题后才把代码合入主干。这个“改、审、再改、再审”的循环比任何一次审计本身都更能体现技术的可靠性。5. 真实使用中最容易翻车的几个点误报、漏报和上下文截断5.1 上下文截断文件大时审计范围会自动缩水实测过程中我踩过最深的坑是上下文截断。当项目规模超过一定量级agent 不可能一次读完所有文件它会做取舍而取舍的结果往往是“重点文件读了非重点文件一眼带过”审计范围就悄悄缩水了。解决方法是前面提到过的scripts/collect_file_list.py。在进入审计流程前先用脚本全量收集文件列表排除掉node_modules、dist、build、package-lock.json这些无关内容然后把剩余文件分批塞进审计范围。这个方法不保证每个文件都被精读但至少不会有文件被“遗忘”。我在实际使用中还会在报告最开头强制输出“审计覆盖率”低于 80% 时直接拒绝给出“整体安全”结论。5.2 框架差异带来的误报重灾区误报是另一个大麻烦。起初规则库写得比较粗看到字符串拼接就报 SQL 注入结果在模板引擎和 ORM 层大量误伤。比如 Sequelize 的where: { username: username }明明是参数化查询在早期版本里会被误判为拼接Vue 模板里{{ user.name }}是自动转义的安全写法也被误报过 XSS。后来我调整了策略规则里明确列出“安全写法白名单”和“危险模式黑名单”。看到db.query(SELECT ... ?, [param])这类带占位符的调用直接归类为安全不再输出告警。看到字符串模板拼接进 query 函数才算命中高风险。这套“白名单优先”的思路把误报率压低了至少一半。5.3 用检查点分级和置信度把误报率压下来还有一个实用技巧给每条审计结论附上置信度。置信度高的走高危路径置信度低的标记为“疑似模式需人工确认”。比如直接看到整个用户对象被console.log打印这是确定的置信度高看到一个变量从请求进来、中间经过两层函数、最后进了exec()这种跨文件推断即使规则命中置信度也应该标中因为可能存在变量改写或白名单校验规则判断不到。分级直接的收益是让人工 reviewer 可以把精力放在高危高置信度的问题上不会因为一堆“疑似”噪声产生审计疲劳。也正因如此我在报告模板里给每个问题都加了“置信度”字段默认值从 0 到 1低于 0.7 的结论必须附带“需要人工确认”的说明。这套机制上线后团队对审计报告的信任度明显提高不再有人抱怨“报告全是废话”。6. 后续迭代方向从审计报告到自动修复6.1 直接生成 diff 补丁修复建议不再只停留在文字当前版本的 security-audit-skill 主要输出了“问题、证据、修复方向”但实际开发中开发人员拿到报告还得自己动手改。下一步的明确演进方向是让 skill 在审计完成后直接生成可应用的补丁文件。思路是给报告模板增加一个新的输出字段“建议补丁”格式统一为标准 diff。比如审计发现 SQL 拼接就把db.query(sql, ...)自动改写为参数化版本并生成 diff人工确认后直接git apply打上补丁。这个方向我已在小范围内验证过对单一文件、单一模式的问题自动生成补丁的成功率相当高但涉及跨文件数据流重构的修复还是需要人来决策不能强推。6.2 接入 pre-commit 和 CI 的轻量安全门槛另一个值得做的方向是把 skill 变成自动化管道的一环。不是每次让 agent 在对话里执行而是挂在 pre-commit 钩子或者 CI 流水线里对 diff 文件做增量安全扫描。如果本次提交新引入了高危问题直接拦截中低危问题打回给提交人确认。这个方案的本质是把“人追着报告跑”变成“规则卡在流程里”。我在一个大一点的项目里测过接入增量扫描后合并请求里新出现的高危问题数量明显下降因为问题在被提交之前就被技能包拦住了而不是等到 code review 时才暴露。这条路还能继续往细做比如接依赖漏洞库、密钥正则扫描等。6.3 规则库持续积累每次审计都是对 skill 的一次训练最后再说一个我特别认同的设计理念规则库不是写完就固定的它应该在每次真实审计中持续增长。我自己的习惯是每审计完一个新项目我都会看一眼报告找出“这次被漏掉、但后来被人发现的问题”把新问题对应的模式写进规则库。跑过 10 个项目之后skill 就比初始版本成熟了很多——多出来的规则全部来自真实世界的代码而不是从漏洞文档里搬来的教条。从项目启动到现在我最大的体会是安全审计技能的核心价值不在于第一次跑出了多少个高危问题而在于它每次都用同样的标准、同样的流程做事不给漏检留机会。如果你也在用 agent 写业务代码给自己配一个这样的技能包长期来看比多写几百行功能代码更值得。
企业数字化 ERP 产品动态
相关推荐
Python变量入门课:从赋值到命名,零基础也能轻松掌握 第 1 课学会了让电脑开口说话,调用print()就能把想说的内容显示在屏幕上。但从第 2 课开始,很多孩子会第一次遇到一个抽象概念:怎么让电脑“记住”一个东西。这堂课讲的变量,就是编程里从“让电脑按顺序做事”跨到“让电脑管理信息… · 2026/9/23 5:08:01
知名旅游网站速查手册:版本升级后API全变?3步搞定 知名旅游网站速查手册:版本升级后API全变?3步搞定 老铁们,有没有经历过这种崩溃时刻?项目跑得好好的,稍微升级一下依赖库,或者换了个框架版本,结果一运行,满屏红字。那个熟悉的 getBySelector 没了, query… · 2026/9/23 5:07:55
嵌入式AI日志:从废话生成到故障预判的三层穿透改造 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/23 5:07:55
3个技巧搞定leave过去分词,告别高频面试题翻车 3个技巧搞定leave过去分词,告别高频面试题翻车 版本升级后 API 全变了?别慌,这就像你刚学会用 Python 2 写脚本,突然被扔进 Python 3 的环境, print… · 2026/9/23 6:37:09
2026最新中国神仙体系:破解项目烂尾的底层逻辑 2026最新中国神仙体系:破解项目烂尾的底层逻辑 看了一堆教程还是不会写项目?这是不是你的真实写照?2026最新的技术栈更新飞快,但很多开发者依然卡在从“Demo”到“生产环境”的最后一公里。… · 2026/9/23 6:37:03
技术实战专栏:从原理到生产环境的深度解析 1. 专栏定位与核心价值这个专栏不是快餐式的技术速成手册,而是一位在技术一线摸爬滚打多年的实践者,将踩过的坑、验证过的方案、深夜调试得出的经验,用系统化的方式呈现的技术手记。不同于官方文档的"应该怎么做",这里更… · 2026/9/23 6:37:03
OpenReplay 消息二进制协议与 MOBS 代码生成器:从 Schema DSL 到多语言产物的完整指南 OpenReplay 消息二进制协议与 MOBS 代码生成器:从 Schema DSL 到多语言产物的完整指南 【免费下载链接】openreplay Session replay, cobrowsing and product analytics you can self-host. Best for reproducing issues and iterating on your product. 项目地址… · 2026/9/23 6:36:57
微信提示音修改实战:3步搞定性能优化与自定义逻辑 微信提示音修改实战:3步搞定性能优化与自定义逻辑 很多开发者背熟了 AudioContext 的 API,却卡在“为什么我在真机上没声音”或者“为什么切换提示音时卡死”的泥潭里。这不仅是语法问题,更是工程落地的性能优化难题。微信提示音修改看… · 2026/9/23 6:36:57
Orphanin FQ肽段特性与实验操作全解析 1. 项目概述:Orphanin FQ (Nociceptin)肽段研究Orphanin FQ(又称Nociceptin)是一种由17个氨基酸组成的神经肽,作为内源性配体作用于NOP受体(Nociceptin Opioid Peptide receptor)。这个特定序列"FGGFT… · 2026/9/23 6:36:57
3招搞定手机怎么下载微信面试难题实战项目解析 3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29