
一聊到 AI 写代码大家眼睛都亮了可一聊到代码审查团队的表情就变得微妙起来。我最近跟几个技术负责人交流大家有个共同的感受AI 编程助手让一次迭代的代码产出量翻了好几倍可是 Review 还是那几个人还是那双眼睛滚动 diff 看到一半就开始走神。一次 Pull Request 动辄上千行新增纯靠人肉盯质量根本兜不住。这种背景下把 AI 拉进代码审计流程几乎成了唯一解。这篇文章想聊的就是我在 Gitee 上把这件事落地的一套组合拳以 Gitee 代码托管和 Pull Request 审查流程为底座用大模型做增量代码审计最后把审查结果以结构化评论的形式打在 PR 上。不是泛泛谈概念而是把我调研过的工具、最终选型的理由、搭建流程、提示词设计、踩过的坑全部摆出来给准备在自己团队里搞 AI 代码审计的同学一份可以照着抄的作业。这里的“代码审计”不是指上线前的专家级渗透测试而是更贴近日常的工程实践在代码合并进主干之前对增量代码做质量与安全检查。这套东西适合谁个人开发者、三五人的小团队、中大型团队的技术负责人和安全同学都能用上只是切入方式和工具选型不太一样。1. 为什么把代码审计底座放在 Gitee 和 PR 上1.1 传统代码审查正在失效先说一个很多人不愿意承认的事实传统的人工 Review 模式正在被 AI 编程带来的代码产量碾压。以前一个后端团队一天能产出一千行有效代码就算不错了现在有了 AI 辅助一个人开几个会话一天生成几千行完全不奇怪。代码量的增长是指数级的而人能投入的审查时间是线性的这个缺口只会越来越大。传统静态扫描工具呢它们靠的是规则库匹配对已知问题敏感但面对业务逻辑漏洞、越权风险、资源泄露这类需要理解上下文的问题基本无能为力。我见过不少团队上 SonarQube结果误报率高到开发直接把报告当垃圾邮件处理。规则引擎查得出“变量名不规范”查不出“这个接口鉴权校验可以绕过”后者才是真正要命的地方。另一个问题是规则的滞后性。现在的攻击手法、依赖漏洞、框架特性变化太快规则库的更新永远追不上实际战场。而且传统工具对增量代码的支持通常很笨重动辄要求全量扫描扫描一次半小时开发早把分支合并了。1.2 PR 就是天然的审计闸口既然人工审不过来、规则引擎审不准那 AI 该在什么位置介入我的答案是 Pull Request 阶段。PR 本质上是代码从个人分支进入主干之前的最后一道闸口它天然具备几个非常适合做自动化审计的特质。第一审查粒度天然是“增量”的。PR 携带的 diff 精确描述了这次改动改了什么审计只需要盯着新增和修改的行不需要把整个仓库翻一遍。这既是效率优势也是准确率优势AI 在有限上下文里聚焦审查比让它读全仓库然后找问题靠谱得多。第二审计意见可以自然留痕。PR 评审意见会跟随这次合并永久保留下来谁改的、谁审的、审出了什么、怎么改的一查就有。这个留痕特性对团队的质量反思和复盘极其宝贵比在 IM 群里聊两句就消失要好上一万倍。第三它给了自动化一个明确的“拦截点”。如果 AI 审查发现问题可以在 PR 上标注严重级别严重的直接阻止合并不严重的作为建议提醒。这个能力只有落在 PR 这种强流程节点上才成立落在 IDE 里那只是个人提醒没有人能保证每个开发者都会看。1.3 Gitee 做底座的优势在哪选 Gitee 做底座不是因为“国产替代”这种场面话而是有几个非常实在的理由。国内团队的代码托管访问速度快、稳定这一点对需要频繁拉取 diff 和回写评论的自动化任务来说很关键。Gitee 的 OpenAPI 覆盖得比较全仓库、PR、评论、Webhook 这些能力都开放出来了而这恰好是搭建 AI 审计机器人的前提。Gitee 的 Webhook 机制支持 PR 创建、更新等事件实时推送到外部服务这意味着审计可以做到“事件驱动”PR 一开机器人立刻响应不需要定时轮询体验上非常接近 GitHub Actions 的感觉。再加上 Gitee 本身也内置了流水线能力如果不想自建服务可以在流水线里编排审计步骤。还有一点很容易被忽略Gitee 上的很多开源项目本身就是很好的测试样本。做 AI 审计工具评测时可以直接拉一批真实仓库来跑看看工具在现实场景里的误报率和漏报率比自己在 demo 项目里自嗨有价值得多。2. AI 代码审计工具有哪些怎么选2.1 三类工具和它们的定位我把现在市面上能接触到的 AI 代码审计方案分成三类每一类的定位和适用场景差得挺远。第一类是通用 AI 编程助手内置的审查能力比如通义灵码、CodeGeeX、Copilot 这类 IDE 插件。它们的好处是接入成本低装个插件就能用你提交代码前它能自动帮你扫描一下当前文件的问题。坏处也很明显审查范围通常局限在当前文件或函数拉不起整个 PR 的上下文而且审查规则是厂商定死的你很难把团队自己的编码规范嵌进去。第二类是专业的 AI 代码审计平台国内外都有一些安全厂商把大模型能力和漏洞知识库结合起来做成独立的审计产品。这类工具的特点是报告规范、有 CWE 映射、能出合规文档适合安全团队驱动、有明确合规要求的场景。但它们的接入成本不低通常需要把你的仓库授权给它而且价格不便宜对小团队来说性价比不高。第三类就是我自己最终选的方向自建 PR 审查 Agent。用 Gitee 的 Webhook 和 OpenAPI 做底座把 diff 拉下来调用大模型 API 做分析和审查再把结果以评论形式写回 PR。这个方案的工程成本最高但灵活性也最大规则完全自己定数据可以控制在自己手里成本按调用量算非常透明。2.2 我的选型自建 PR 审查 Agent为什么放着现成的工具不用非要自己搭一套主要是三个考量。流程可控性。现成工具是“我用它的规则审我的代码”自建则是“我用我的规则审我的代码”。团队规范这种东西只有自己定义得出来比如什么算严重问题、哪些目录不审、输出格式长什么样只有自建才能把这些细节写死在提示词和代码里。数据安全也是实打实的痛点。把公司私有仓库的完整代码交给第三方审计平台很多团队在合规层面是过不去的。自建方案里代码只会在自己的服务器和模型 API 之间流转而且可以用脱敏、白名单等方式进一步控制风险至少这个流程的每个环节是你自己能把控的。成本预期也更重要。专业审计平台通常是按仓库数或座位数收费不管代码规模多大先交一笔不小的钱。大模型 API 是按 token 收费的我们只审增量 diff一次 PR 的成本往往就是几毛钱。我算过一笔账一个十人团队、每天三五十个 PR跑国内主流大模型 API一个月成本也就百元量级这个性价比是专业平台很难做到的。2.3 关于 MCPAI Agent 正在把手伸进代码托管平台最近“AI Agent”这个概念特别热具体到代码审计这件事上有一个技术动向值得关注现在不少 Agent 框架和工具链开始支持 MCPModel Context Protocol可以把 Gitee 的仓库、PR、Issue 这些数据源作为外部工具暴露给大模型。什么意思呢就是模型不仅能看 diff 文本还能直接通过协议去查询 PR 状态、拉取评论、查看文件内容甚至调用接口去回复评论。这意味着什么过去我们写死一个机器人逻辑它只能按预设脚本执行“拉 diff、调模型、发评论”这条固定路线。而有了 MCP 之后Agent 可以在一定程度上自己决定审查路径发现一个问题先查一下相关文件的历史改动再决定要不要深入检查。这种自适应审查的深度是传统脚本很难做到的。不过我要提醒一句MCP 是一把双刃剑。权限给大了Agent 可能做出你意想不到的操作。我目前的做法是只开放只读权限给 Agent 的 MCP 连接写评论仍然走独立的、带审批逻辑的通道。自动化可以聪明但授权必须保守。3. 在 Gitee 上落地一套 PR 审查机器人3.1 整体工作流设计我把整套系统拆成了六个环节PR 事件触发、Webhook 接收与校验、数据拉取、diff 分片、AI 审计、结果回写。核心设计原则是“事件驱动、异步处理、分级报告”。事件驱动好理解就是靠 Gitee 的 Webhook 推送 PR 事件来启动审计而不是定时全量扫描。异步处理是指 Webhook 接收到请求后立刻返回成功把耗时的审计任务放到后台线程或消息队列里慢慢跑避免因为处理超时导致 Gitee 反复重试推送。这套工作流看起来很常规但里面最关键的决策在 diff 分片和结果回写这两个环节。分片决定了 AI 能不能在有限的上下文窗口里看清问题回写决定了审查结果能不能被开发者真正用起来。很多半途而废的 AI 审计项目都是栽在这两个细节上。整个过程里我强烈建议把审计任务做成可重入的。幂等性处理要到位同一个 PR 如果被 Webhook 重复推送系统要能识别出来不要产生两份重复的审查评论。3.2 关键环节一Webhook 接入与安全校验Gitee 的仓库设置里可以添加 Webhook选择推送类型为 Pull Request 事件。拿到 Webhook URL 之后需要配置一个密钥Gitee 在推送请求时会对 payload 做签名接收端用同一个密钥校验请求合法性。这一步千万别省否则任何人都能伪造请求往你的服务里灌数据诱导 AI 审计一些恶意构造的 diff。我用 Python 写接收端时核心逻辑大概是这样的import os import hmac import hashlib import threading from flask import Flask, request app Flask(__name__) WEBHOOK_SECRET os.environ[GITEE_WEBHOOK_SECRET] def verify_signature(payload: bytes, signature_header: str) - bool: # 用 HMAC-SHA256 校验请求来源字段名以 Gitee 当前文档为准 expected hmac.new( WEBHOOK_SECRET.encode(), msgpayload, digestmodhashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, signature_header) app.route(/webhook/gitee, methods[POST]) def gitee_webhook(): body request.get_data() if not verify_signature(body, request.headers.get(X-Gitee-Token, )): return forbidden, 403 event request.get_json() if event.get(hook_name) pull_request and event.get(action) in (open, update): repo event[repository][full_name] pr_number event[pull_request][number] # 丢到后台线程处理避免 webhook 同步等待超时 threading.Thread(targetrun_ai_review, args(repo, pr_number), daemonTrue).start() return ok拿到 Webhook 事件之后先不要急着处理把事件里的仓库名、PR 编号、触发动作落一条日志这是排查问题时的第一手线索。我踩过一个坑Gitee 在 PR 首次创建和后续更新时都会推送事件如果只监听一种事件容易出现“更新了代码但机器人没反应”的情况。3.3 关键环节二diff 拉取与智能分片审计的素材是 diff而 diff 的质量直接决定了审计质量。通过 Gitee OpenAPI 拉取 PR 的文件改动列表和补丁内容时需要注意接口返回的 diff 可能极大。一次大型 PR 改动几千个文件、几万行代码都有可能直接把整个 diff 塞给模型不仅会爆上下文窗口而且审计质量会急剧下降模型在超长文本里注意力会分散。我采用的策略是“按 hunk 分片 重叠区设计”。在 diff 格式里每个标记就是一个 hunk对应原文件和新文件各一段内容。先把同一个文件的所有 hunk 聚在一起如果总量没超阈值我一般设 8000 字符就整个文件作为一片如果单个文件太大就按 hunk 边界切分每一片在头尾保留大约 50 行重叠上下文避免跨 hunk 的逻辑被切断。分片后需要做行号映射。diff 里的行号有旧文件和新文件两套你要明确告诉模型请基于新文件 侧的行号输出评论。同时在程序里记录每一片对应的新文件起始行号模型输出的相对行号加上这个偏移量才是能在 PR 评论里落地的真实行号。def split_diff(file_diff: dict, max_chars: int 8000, overlap_lines: int 50): # 按 hunk 聚合再按大小切片返回时带上行号偏移 hunks parse_hunks(file_diff[patch]) # 从 patch 文本解析 hunk chunks, current, current_len, start_line [], [], 0, hunks[0][new_start] for h in hunks: if current_len len(h[text]) max_chars and current: chunks.append({new_start: start_line, text: \n.join(current)}) current, current_len [], 0 start_line h[new_start] current.append(h[text]) current_len len(h[text]) if current: chunks.append({new_start: start_line, text: \n.join(current)}) return chunks分片完成之后还有一个容易忽略的动作过滤。有些文件根本不需要 AI 审计比如 lock 文件、生成的 protobuf 代码、第三方 vendor 目录。拉完文件列表先做一轮过滤既能省 token又能减少噪音。这个过滤规则我建议放在配置中心而不是写死在代码里团队可以随时增删非常实用。3.4 关键环节三提示词与结果回写diff 准备好了接下来就是给大模型出题。提示词的质量决定了审计的天花板这个我在下一节展开细讲。模型输出之后需要解析成结构化评论回写到 PR。我采用的输出协议是 JSON 数组每条评论包含文件路径、行号、严重级别、问题类型、描述和修复建议。程序解析 JSON 后逐条调用 Gitee OpenAPI 发到 PR 对应位置。评论回写有一个细节Gitee 的 OpenAPI 对调用频率有限制几十条评论同时并发提交很容易触发限流。所以我在评论提交模块里加了信号量做并发控制同时做好了失败重试。如果某条行级评论因为行号无效等问题提交失败就把这条评论降级为 PR 级的汇总评论至少保证问题被看见了而不是悄悄丢掉。3.5 权限设计机器人必须有边界给机器人开账号时权限一定要遵循最小化原则。我给审查机器人创建了一个独立账号只授予它读取仓库和提交 PR 评论的权限合并分支、修改代码这些权限一律不给。有人开玩笑说哪天 AI 觉得代码不够好自己把合并按钮点了这个风险不是不存在权限设计好了就不会发生。还要考虑一个边界AI 审查结果不应该直接成为合并的硬性阻断器。我的实践是只有 high 级别的安全问题才会触发 PR 的“审查未通过”状态medium 和 low 级别的问题一律只做提示。为什么这么做因为 AI 的误报率不可能降到零一旦它频繁阻断合并开发者就会产生对抗情绪想办法绕过机器人那整个审计体系就形同虚设了。AI 审出问题、人来做最终决策这个格局最好。4. 让 AI 审得更准提示词与规则调优4.1 一套可复用的审计提示词很多人直接拿“请审查这段代码是否有问题”去问大模型得到的大概率是一些正确的废话——要注意空指针、要考虑边界条件、建议增加日志。这不是模型笨是你的问题太笼统。审计提示词至少要包含四个要素角色定义、审查规则、输出格式、禁止项。我自己用的模板大概是这样的你是团队资深代码审查员。请对下面的代码变更进行增量审查。 审查规则 1. 只报告本次 diff 新引入的问题历史遗留问题不要提。 2. 优先关注注入与越权、敏感信息泄露、并发与资源泄露、错误处理缺失、边界条件错误。 3. 不要仅凭风格偏好提建议除非与主流工程实践严重冲突。 4. 每条问题必须给出可执行的修复建议。 输出要求 - 只输出 JSON 数组不要输出 Markdown 或解释性文字。 - 每条 JSON 包含file文件路径、line新文件真实行号、severityhigh/medium/low、type问题类型、message一句话描述、suggestion具体修复建议。 - 没有问题时输出 []不要编造。 --- 以下是代码变更diff {patch_data}这套提示词的关键在于“只审增量”和“结构化输出”。只审增量能避免模型把仓库里存在已久的历史债务翻出来一堆陈年旧账刷屏不仅没价值还容易引发团队反感。结构化输出则直接决定了后面评论回写的自动化程度解析 JSON 比解析自然语言稳得多。4.2 降低误报的三个技巧误报率高是 AI 审计被人诟病最多的点。我的实测经验是三个技巧能显著降低误报率。给模型喂仓库上下文。很多误报是模型不懂项目背景导致的比如项目里存在一个全局的权限拦截器模型看到某个接口没有单独做鉴权就报了越权但实际整个目录都在拦截器保护下。把 README、接口文档、目录说明塞进提示词的“背景信息”区模型理解上下文后再审误报会少一大截。让它先判断再报告。在提示词里加一句“如果是跨函数或跨文件的逻辑先说明你的推断链再定性为问题”。模型在推理链条完整时给出的结论可信度比直觉判断高很多。实测这个技巧能把 high 级别的误报率降低一半以上。引入严重级别阈值和路径白名单。不要奢望 AI 每条都准重点是 high 级别要准。把 low 和部分 medium 的问题压在后台只让 high 级别的结论暴露给开发者看起来“AI 很靠谱”实际上是把不靠谱的部分过滤掉了。这种做法不是自欺欺人而是把 AI 当工具用的正确姿势它的价值是帮你筛出最值得人关注的那几个点而不是替代人做全部判断。4.3 成本与效果的平衡计算老板们都会问一句“这玩意儿跑起来一个月烧多少钱”这里需要把账算清楚。假设一次 PR 有 500 行新增、200 行删除附带上下文行diff 文本大概在 500 行左右。按中文场景每个汉字约 1.5 token、英文代码约 4 字符 1 token 来估算一次 PR 的 diff 大约是 7000 到 9000 token。加上提示词和模型输出一次完整审计大概消耗 12000 到 15000 token。国内主流大模型 API 的价格以当前市面中位水平来看一天跑 30 个 PR折算下来大概几块钱到十几块钱。对比请一个人专门做代码走查的工资这个成本几乎可以忽略。做成本控制时关键策略是只审新增行、过滤掉无关文件、压缩不必要的上下文。全仓扫描式审计无论效果还是成本都是灾难这也再次印证了“PR 增量审查”这个架构选择的正确性。5. 实战踩坑记录与排障速查5.1 五个真实踩过的坑第一个坑是 Webhook 回调超时导致的重复处理。最开始我把审计逻辑全写在 Webhook 回调函数里同步执行一个复杂 PR 可能要跑两三分钟Gitee 那边早就等不及了于是反复推送同一事件我这边也重复审计、重复评论。后来改成先应答、后台异步处理再配合幂等设计这个坑才算填上。第二个坑是编码乱码。有些历史仓库里的文件还是 GBK 编码拉下来直接按 UTF-8 解析一堆乱码喂给模型审计结果可想而知。处理方式是在拉取文件内容时检测编码必要时做转换实在转换不了的直接丢弃这一片不审总比误报好。第三个坑是大模型“一本正经地编行号”。模型输出的行号经常跟实际 diff 对不上可能是它数错了上下文行也可能是它记得行号但没注意新旧文件的差异。我在解析 JSON 时加入了行号合法性校验超过文件有效行数的评论直接降级为文件级评论不让错误行号出现在 PR 里误导人。第四个坑是评论刷屏导致开发者集体无视机器人。一开始我让 AI 把发现的每个问题都单发一条评论结果中等规模的 PR 能刷出四五十条评论开发者直接崩溃。后来改成 high 级别即时评论medium 级别按文件汇总成一张表low 级别只进后台报表评论量降下来了处理率反而上去了。第五个坑是密钥泄露的隐患。调试阶段我在日志里顺手打印了 Webhook 的 payload 和模型 API 的返回里面带着 Token 信息还好是内网环境及时发现。以后所有密钥一律走环境变量日志里对敏感字段打码开发环境用 mock 数据。5.2 常见问题速查表把实操中最容易遇到的问题整理成一张速查表排查问题的时候对照着看能少走很多弯路。症状可能原因解决办法Webhook 请求总是超时回调里同步执行耗时逻辑先返回 200后台异步处理同一个 PR 出现多份重复评论事件重复推送且未做幂等以 PR 编号commit SHA 作为任务唯一键AI 评论的行号在代码里找不到模型幻觉或新旧文件行号混淆输出后做行号范围校验超范围降级为文件级评论审查结果不被开发者重视评论太多太碎、没分级按严重级别分流high 才即时展示模型返回乱码或 JSON 解析失败提示词里没约束输出格式明确“只输出 JSON 数组”失败时降级重试拉取 diff 时被限流并发请求过多或触发接口限制加并发控制失败指数退避重试5.3 给新手的落地建议如果你现在正准备在团队里搞这套东西我给三条实在的建议。先在内部小仓库跑两周别一上来就推到业务主仓库拿一些历史 PR 回放一遍看看 AI 审出的问题里有多少是真实的、多少是误报顺便把提示词规则调顺。从“仅提示”模式开始不要一上来就让 AI 直接阻断合并先让团队习惯它的存在再逐步收紧策略。保留人工审批的最终决定权AI 是助手不是裁判这个认知从一开始就要在团队里建立起来。关于工具选型如果你只是个人开发者或者团队不到十人确实没必要自己搭全套装个 IDE 插件或者用现成平台更划算。但如果你在十人以上的团队有规范化的 PR 流程也在意数据不出内网那自建这套东西的长期价值是值得投入的。最后说点个人感受。这套系统跑了一个季度我最大的体会不是“AI 审出了多少 bug”而是它把团队代码 Review 的讨论质量拉高了一个台阶。以前 Review 会议大部分时间花在“这个代码风格不对”“这里应该加注释”这类低水平问题上现在 AI 把这些基础问题都拦掉了人的讨论集中在真正的设计取舍和架构合理性上。代码审查这件事的本质从来不是找茬而是团队对工程质量达成共识的过程。AI 能把最低标准守住剩下的还是人和人之间的那点默契。