资讯动态

用大模型打造严苛的 PR 审查机器人:自动化拦截潜在 SQL 注入与 N+1 慢查询

发布时间:2026/10/13 14:06:40 来源:尧图企业网站定制
独立开发者或者三五人的微型团队最大的研发隐患是什么既不是前端 UI 没对齐也不是某个 CSS 动画不够丝滑而是缺乏专职的代码审计员与 DBA。当你一个人或者拉着合伙人通宵赶双 11 促销版本时大脑处于极度疲劳状态极容易在 ORM 查询或者后端 API 接口里写下两大致命隐患为了图省事用字符串模板直接拼了一句原生 SQL埋下了毁灭性的SQL 注入漏洞在for...of循环里随手await db.select().from(userProfiles)...在几十上百条列表循环中引入了隐蔽的N1 慢查询本地测试只有 2 条数据丝滑顺畅一旦线上大促几千个用户并发访问数据库连接池瞬间被打爆CPU 直接飙升到 100%。很多商业级的 SAST 代码扫描工具比如 SonarQube、Snyk要么笨重臃肿难配置要么规则死板产生海量误报独立开发根本没精力维护。今天蔓蔓就来分享我们团队内部搭建的大模型代码审计机器人PR Reviewer Agent通过 GitHub Actions 自动化挂载利用大模型对 Git Diff 进行深度语义与调用链路分析精准定位潜在安全风险并在 PR 对应代码行直接发表审查建议与红线阻断。审查架构为什么传统正则做不到精准检测为什么传统的 ESLint 或者静态正则匹配很难揪出 N1 查询因为在现代全栈开发比如 Next.js / Nuxt 结合 Drizzle ORM、Prisma 或 TypeORM中数据库调用往往被包装在 Repository 层或者 Service 函数内部// 典型隐蔽的 N1 慢查询陷阱 const orders await getPaidOrders(); const enrichedOrders await Promise.all( orders.map(async (order) { // 看起来只是调用了一个业务函数但内部其实发起了单条 SQL 查询 const user await getUserById(order.userId); const shipping await getShippingDetail(order.id); return { ...order, user, shipping }; }) );静态正则很难跨函数上下文判断getUserById究竟是纯内存缓存计算还是发起了网络 IO 数据库查询。而结合了大模型上下文推断能力的审查机器人能够理解函数语义、结合上下文上下文引用甚至推测出在Promise.all映射列表时对数据库造成的巨大并发风暴。整个审查流程由 GitHub Actions 自动化触发[开发者提交 Pull Request] │ ▼ [GitHub Actions 触发 workflow] │ ▼ [过滤 Diff: 提取 .ts, .js, .sql 服务端文件] │ ▼ [大模型审查引擎带特定安全与性能 Prompt] │ ┌────┴────────────────────────┐ ▼ ▼ [发现严重安全/N1隐患] [审查通过 / 仅普通优化] │ │ ├─ 在 PR 行级添加 Review 评论 └─ 给出通过 Badge 标签 └─ 打上 block-merge 标签阻断合并核心实现GitHub Actions 审查机器人核心脚本我们使用 TypeScript 编写一个轻量级的 GitHub PR 审查 Agent借助octokit/rest与 Google GenAI SDK 进行驱动// scripts/pr-reviewer-agent.ts import { Octokit } from octokit/rest; import { GoogleGenAI } from google/genai; const octokit new Octokit({ auth: process.env.GITHUB_TOKEN }); const ai new GoogleGenAI(); interface VulnerabilityReport { file: string; line: number; type: SQL_INJECTION | N_PLUS_ONE_QUERY | TRANSACTION_RISK; severity: CRITICAL | WARNING; reason: string; suggestedFix: string; } export async function reviewPullRequest(owner: string, repo: string, pullNumber: number) { // 1. 获取 PR 的文件变更列表 const { data: files } await octokit.pulls.listFiles({ owner, repo, pull_number: pullNumber, }); // 2. 仅过滤与数据库、数据层相关的后端代码文件 const targetFiles files.filter(f /\.(ts|js|mjs|sql)$/.test(f.filename) !f.filename.includes(test) f.patch // 确保有变更 patch ); if (targetFiles.length 0) { console.log(未发现服务端数据库相关变更跳过审查); return; } // 3. 逐个文件或批量组装 Diff 供大模型审查 for (const file of targetFiles) { const findings await analyzeDiffWithLLM(file.filename, file.patch!); if (findings.length 0) { for (const finding of findings) { // 在 GitHub PR 代码变更的具体行数上打出审查建议 await octokit.pulls.createReviewComment({ owner, repo, pull_number: pullNumber, commit_id: (await octokit.pulls.get({ owner, repo, pull_number: pullNumber })).data.head.sha, path: finding.file, line: finding.line, body: **[AI 安全与性能防线 - ${finding.type}]**\n\n**严重等级**: \${finding.severity}\\n\n**问题分析**: ${finding.reason}\n\n**推荐修复方案**:\n\\\typescript\n${finding.suggestedFix}\n\\\, }); } // 如果有 CRITICAL 级别隐患给 PR 增加警告标签 if (findings.some(f f.severity CRITICAL)) { await octokit.issues.addLabels({ owner, repo, issue_number: pullNumber, labels: [block-merge: safety-check-failed], }); } } } }精准 Prompt 策略如何把大模型调教成“魔鬼审查官”大模型代码审查最容易出现“废话连篇”的问题比如挑剔命名格式、建议拆分小函数等琐碎小事导致开发者直接忽略 PR 评论。我们的核心策略是极度聚焦致命缺陷严禁给与安全性能无关的鸡毛蒜皮建议// scripts/diff-analyzer.ts import { GoogleGenAI } from google/genai; import { VulnerabilityReport } from ./pr-reviewer-agent; const ai new GoogleGenAI(); export async function analyzeDiffWithLLM( filename: string, patch: string ): PromiseVulnerabilityReport[] { const prompt 你是一位极为严谨的生产级架构安全专家与高级 DBA。请审查以下 Git Patch 代码差异 文件名: ${filename} 代码 Patch 内容: ${patch} 审查核心红线规则只关注以下三项其他格式、拼写等琐事一律忽略 1. 【SQL 注入风险 (SQL_INJECTION)】 - 严禁使用字符串拼接如 \SELECT * FROM users WHERE id \${id}\直接执行 SQL。 - 严禁在 ORM 的 raw 查询中未经参数化直接拼接外部入参。 2. 【N1 慢查询风险 (N_PLUS_ONE_QUERY)】 - 严禁在 for、while、forEach、map、Promise.all 内部遍历执行数据库查询语句或 ORM 调用。 - 必须推荐使用 JOIN 批量查询或 WHERE id IN (...) 批量拉取后在内存中根据 Map 聚合并发填充。 3. 【长事务持有风险 (TRANSACTION_RISK)】 - 严禁在数据库事务tx.transaction内发起第三方 HTTP 请求如 Stripe API、外部 Webhook。这会导致数据库连接被长期占用直至连接池枯竭。 如果发现问题请提供精确的 Patch 行号注意必须是新增行或修改行对应的行数并给出最小可执行的重构代码。 输出格式要求为严格的 JSON 数组若没有发现上述三类问题则返回空数组 []。 ; const response await ai.models.generateContent({ model: gemini-2.5-flash, contents: prompt, config: { responseMimeType: application/json, }, }); try { return JSON.parse(response.text || []) as VulnerabilityReport[]; } catch (error) { console.error(解析大模型审查结果失败:, error); return []; } }生产拦截实测案例拯救了一次线上故障在某次双 11 前置营销活动迭代中我们团队的新功能需要给批量报名的用户发放折扣券。开发者提交的代码如下// 提交的代码片段 export async function claimDiscountsForUsers(userIds: string[], couponId: string) { const results []; for (const uid of userIds) { // ⚠️ 极其致命的隐蔽循环单条写入与查询 const user await db.query.users.findFirst({ where: eq(users.id, uid) }); if (user !user.hasClaimed) { await db.insert(userCoupons).values({ userId: uid, couponId }); results.push(uid); } } return results; }PR 刚刚推送到 GitHub 仅 18 秒审查机器人立刻在 PR 行上阻断并评论[AI 安全与性能防线 - N_PLUS_ONE_QUERY]严重等级:CRITICAL问题分析: 在for...of循环中逐条执行了findFirst和insert操作。当userIds达到 500 个以上时将产生 1000 次数据库网络往返高并发下将耗尽连接池导致网关 504 崩溃。推荐修复方案:// 改用批量 IN 查询与批量 INSERT const eligibleUsers await db.query.users.findMany({ where: and(inArray(users.id, userIds), eq(users.hasClaimed, false)), columns: { id: true }, }); if (eligibleUsers.length 0) { const insertData eligibleUsers.map(u ({ userId: u.id, couponId })); await db.insert(userCoupons).values(insertData); }同时PR 自动被打上了block-merge: safety-check-failedGitHub 分支保护规则直接禁用了合并按钮。这次拦截避免了一次潜在的大促高并发宕机惨剧。总结对独立开发者而言AI Agent 最有价值的落脚点不是替代你写无聊的 HTML而是成为你身后的全天候安全架构师与守护哨兵。小团队没有资源请人轮流值守但花 20 分钟配置一个针对 SQL 注入与 N1 慢查询的 GitHub Actions 审查机器人就能让你的每一个生产提交多一道坚不可摧的安全护城河。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑