封面信息图

独立开发者或者三五人的微型团队,最大的研发隐患是什么?既不是前端 UI 没对齐,也不是某个 CSS 动画不够丝滑,而是缺乏专职的代码审计员与 DBA。当你一个人或者拉着合伙人通宵赶双 11 促销版本时,大脑处于极度疲劳状态,极容易在 ORM 查询或者后端 API 接口里写下两大致命隐患:

  1. 为了图省事,用字符串模板直接拼了一句原生 SQL,埋下了毁灭性的 SQL 注入漏洞;
  2. 在 for...of 循环里随手 await db.select().from(userProfiles)...,在几十上百条列表循环中引入了隐蔽的 N+1 慢查询,本地测试只有 2 条数据丝滑顺畅,一旦线上大促几千个用户并发访问,数据库连接池瞬间被打爆,CPU 直接飙升到 100%。

很多商业级的 SAST 代码扫描工具(比如 SonarQube、Snyk)要么笨重臃肿难配置,要么规则死板产生海量误报,独立开发根本没精力维护。今天蔓蔓就来分享我们团队内部搭建的大模型代码审计机器人(PR Reviewer Agent):通过 GitHub Actions 自动化挂载,利用大模型对 Git Diff 进行深度语义与调用链路分析,精准定位潜在安全风险并在 PR 对应代码行直接发表审查建议与红线阻断。


审查架构:为什么传统正则做不到精准检测?

为什么传统的 ESLint 或者静态正则匹配很难揪出 N+1 查询?因为在现代全栈开发(比如 Next.js / Nuxt 结合 Drizzle ORM、Prisma 或 TypeORM)中,数据库调用往往被包装在 Repository 层或者 Service 函数内部:

// 典型隐蔽的 N+1 慢查询陷阱
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)]
         │
    ┌────┴────────────────────────┐
    ▼                             ▼
[发现严重安全/N+1隐患]      [审查通过 / 仅普通优化]
    │                             │
    ├─ 在 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
): Promise<VulnerabilityReport[]> {
  const prompt = `
你是一位极为严谨的生产级架构安全专家与高级 DBA。请审查以下 Git Patch 代码差异:
文件名: ${filename}

代码 Patch 内容:
${patch}

审查核心红线规则(只关注以下三项,其他格式、拼写等琐事一律忽略):
1. 【SQL 注入风险 (SQL_INJECTION)】:
   - 严禁使用字符串拼接(如 \`SELECT * FROM users WHERE id = \${id}\`)直接执行 SQL。
   - 严禁在 ORM 的 raw 查询中未经参数化直接拼接外部入参。
2. 【N+1 慢查询风险 (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-failed,GitHub 分支保护规则直接禁用了合并按钮。这次拦截避免了一次潜在的大促高并发宕机惨剧。


总结

对独立开发者而言,AI Agent 最有价值的落脚点不是替代你写无聊的 HTML,而是成为你身后的全天候安全架构师与守护哨兵。

小团队没有资源请人轮流值守,但花 20 分钟配置一个针对 SQL 注入与 N+1 慢查询的 GitHub Actions 审查机器人,就能让你的每一个生产提交多一道坚不可摧的安全护城河。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐