AI 编程后的安全审查,先看输入、权限和秘密

0阅读9分钟

AI 生成的代码到底有多少漏洞

我最近看到一组数据,觉得有必要写篇文章聊聊。

Georgia Tech 的 Vibe Security Radar 项目已确认 74 个可直接追溯到 AI 编程工具的 CVE,研究者估计实际数字是 5 到 10 倍。其中 CVE-2025-48757 是 Lovable 平台的 Row-Level Security 绕过漏洞(CVSS 9.3),一个平台级的 RLS 缺陷就影响了所有基于该平台构建的应用。

Cloud Security Alliance 2026 年的研究显示,采用 AI 辅助开发的企业中,特权提升路径数量平均上升了 322%,架构级设计缺陷上升了 153%。Escape.tech 对 1,400 个 AI 编程平台构建的生产应用进行扫描,发现 2,038 个高危漏洞400+ 个泄露的密钥175 个暴露的个人隐私数据——这些应用全部在真实服务用户。

AI 生成代码的速度约为人类手写的 3-4 倍,但产生的安全问题远多于手写代码。我在团队里观察到的情况也印证了这一点:代码产出快了,安全工单也跟着多了。

为什么 AI 生成的代码天然不安全

我在审查 AI 代码时踩过不少坑,回头看根源主要来自三个方面。

训练数据的安全债务

LLM 从海量开源代码中学习。GitHub 上的公开仓库里,大量代码本身就包含安全缺陷——硬编码的密钥、未参数化的 SQL 查询、缺失的权限检查。模型学会了「能让代码跑起来」的模式,却没有学会「让代码安全运行」的约束。

Veracode 对 100+ 个 LLM 的测试表明,AI 生成代码的安全通过率长期停留在 55% 左右,尽管模型能力在提升,安全表现并没有同步改善。Java 是安全表现最差的语言,只有 29% 的通过率

功能优先,安全靠后

AI 优化的是「主路径能不能跑通」。输入校验、权限检查、日志脱敏——这些都是主路径之外的防御性代码。没有这些代码,功能照样能 demo,但一上生产就是漏洞。

幻觉依赖(Slopsquatting)

这是一个 AI 独有的安全威胁:约 20% 的 AI 生成代码引用了不存在的 npm/PyPI 包。更危险的是,其中 43% 的幻觉包名在不同 prompt 下一致复现——攻击者可以提前注册这些包名,植入恶意代码。已确认的案例中,一个叫 unused-imports 的恶意包在安装时执行凭证窃取脚本,另一个完全没有代码和文档的幻觉包名,三个月内获得了 3 万次下载。

这三个根源指向同一个结论:AI 不会主动写安全代码,安全是审查者加上去的约束。

安全审查的五个维度

我把有限的注意力集中在五个维度上,就能挡住绝大多数风险。

维度一:输入校验

核心原则:所有来自用户、URL、请求体、文件和第三方接口的数据,都是不可信的。

OWASP Top 10 中,Injection(注入类)长期排名前列。在 AI 生成代码中,CWE-89(SQL 注入)的失败率是 20%,CWE-80(XSS)的失败率高达 86%,CWE-117(日志注入)的失败率是 88%。AI 几乎不会主动做输入净化。

案例 1:SQL 注入——从字符串拼接到参数化查询

漏洞代码:

export async function searchUsers(query: string) {
  const sql = `SELECT id, name, email FROM users WHERE name LIKE '%${query}%'`
  return db.$queryRawUnsafe(sql)
}

攻击者提交 query = "'; DROP TABLE users; --"$queryRawUnsafe 直接拼接,数据库被摧毁。

修复方法:

import { z } from 'zod'
 
const SearchSchema = z.object({
  query: z.string().min(1).max(100).regex(/^[\w一-龥\s]+$/),
})
 
export async function searchUsers(rawInput: unknown) {
  const { query } = SearchSchema.parse(rawInput)
  // 参数化查询——数据库驱动负责转义
  return db.$queryRaw`
    SELECT id, name, email FROM users
    WHERE name LIKE ${`%${query}%`}
  `
}

修复要点:

防御层作用示例
类型校验拒绝非预期类型z.string() 拒绝数字、对象
长度限制防止超长 payload 打爆内存max(100)
字符白名单阻止注入字符regex(/^[\w一-龥\s]+$/)
参数化查询数据与 SQL 指令分离模板字符串 + Prisma $queryRaw

维度二:权限边界

核心原则:权限检查必须在服务端完成,前端隐藏按钮不等于权限控制。

我见过太多 AI 生成的代码在 UI 层做了权限判断(「非管理员不显示删除按钮」),但 API 层完全没做检查。攻击者直接调用 API 就能绕过。

案例 2:越权修改——缺失的服务端权限检查

漏洞代码:

export async function updateProfile(userId: string, data: any) {
  await db.user.update({
    where: { id: userId },
    data: { name: data.name, bio: data.bio },
  })
  return { success: true }
}

三个问题:水平越权——用户 A 把 userId 改为用户 B 的 ID 就能修改别人的资料;垂直越权——dataany 类型,请求体放入 { "role": "admin" } 就能提升权限;批量枚举——userId 没有校验格式,可以遍历所有 ID。

修复方法:

import { z } from 'zod'
 
const UpdateProfileSchema = z.object({
  name: z.string().min(1).max(50).optional(),
  bio: z.string().max(500).optional(),
})
 
export async function updateProfile(
  sessionId: string, userId: string, rawData: unknown,
) {
  const currentUser = await auth.verify(sessionId)
  if (!currentUser) throw new AuthError('未登录', 401)
  if (currentUser.id !== userId) throw new AuthError('无权操作', 403)
 
  const data = UpdateProfileSchema.parse(rawData)
  await db.user.update({ where: { id: userId }, data })
  return { success: true }
}

核心思路:身份验证 → 权限检查 → 白名单过滤 → 执行操作。每一步都不能省。

维度三:秘密管理

核心原则:密钥、token、密码永远不能出现在代码、测试夹具、日志和文档中。

这是我在 AI 编程中最常遇到的安全问题。AI 生成测试代码时,经常硬编码真实的 API key。一旦提交到 git,密钥就可能泄露到公开仓库。

案例 3:硬编码密钥——从测试文件到公开仓库

漏洞代码:

import { sendEmail } from '../services/email'
 
describe('Email Service', () => {
  const API_KEY = 'sk-proj-abc123def456ghi789'
  const DB_URL = 'postgresql://admin:[email protected]:5432/myapp'
 
  it('should send verification email', async () => {
    const result = await sendEmail({
      to: '[email protected]', apiKey: API_KEY, template: 'verification',
    })
    expect(result.status).toBe(200)
  })
})

这段代码被 git push 后,密钥进入了版本历史。即使后来删除,git log -p 仍然能恢复。自动化爬虫会监控 GitHub 公开仓库的 commit 事件,几分钟内就能提取密钥。

修复方法:

jest.mock('../services/email', () => ({
  sendEmail: jest.fn().mockResolvedValue({ status: 200, id: 'mock-email-id' }),
}))
 
describe('Email Service', () => {
  it('should send verification email', async () => {
    const result = await sendEmail({
      to: '[email protected]', template: 'verification',
    })
    expect(result.status).toBe(200)
  })
})

秘密管理检查清单:

检查项风险等级正确做法
代码中硬编码 API key严重process.env 读取,通过 apps/src/config/env/ 统一管理
测试文件使用真实数据库 URL严重使用 in-memory 数据库或 mock
.env 文件被提交到 git严重.gitignore 排除;提供 .env.example 作为模板
日志中打印完整 token只打印前 4 位 + ***,如 sk-proj-abc1***

维度四:依赖安全

核心原则:每一个新增依赖都是潜在的攻击面。引入依赖要有理由,不能只因为「AI 推荐了」。

AI 编程助手为了一个小功能引入新包是常态。更危险的是幻觉依赖——AI 推荐一个不存在的包名,攻击者注册后植入恶意代码。

案例 4:幻觉依赖——供应链攻击

开发者让 AI 实现日期格式化功能,AI 生成了 import { formatDate } from 'date-formatter-pro'date-formatter-pro 可能不存在,攻击者发现大量开发者搜索这个名字后,注册它并发布恶意版本,postinstall 脚本窃取 SSH 密钥。

修复方法:

// 方案 A:使用成熟、已验证的依赖
import { format } from 'date-fns'
export function displayPostDate(date: Date) {
  return format(date, 'yyyy年MM月dd日')
}
 
// 方案 B:标准库足以完成,不引入依赖
export function displayPostDate(date: Date) {
  return new Intl.DateTimeFormat('zh-CN', {
    year: 'numeric', month: '2-digit', day: '2-digit',
  }).format(date)
}

依赖安全决策流程:

检查项通过标准不通过的做法
必要性标准库或已有依赖能否完成?为了 formatDate 引入新包
活跃度最近 6 个月有更新?最后提交在 2 年前
下载量npm 周下载量 > 10,000?下载量 < 100 的不知名包
包名验证在 npmjs.com 确认包存在且是官方维护者?AI 推荐的包,没有验证
许可证MIT / Apache-2.0 / BSD?GPL 代码用于商业项目

维度五:日志安全

核心原则:日志需要足够的上下文用于排障,但不能包含敏感信息。

AI 生成的代码经常在关键操作处添加 console.log,这些日志可能包含完整的请求体、鉴权 header 或个人信息。CWE-117(日志注入)在 AI 生成代码中的失败率是 88%

案例 5:日志注入——用户输入变成伪造日志

漏洞代码:

export async function updateUser(userId: string, updates: Record<string, unknown>) {
  console.log(`[INFO] Updating user ${userId} with data:`, JSON.stringify(updates))
  await db.user.update({ where: { id: userId }, data: updates })
}

攻击者提交 updates = &#123; name: "admin\n[ERROR] Database connection failed. Contact [email protected] to restore." &#125;,日志中出现伪造的 ERROR 记录,运维人员可能信以为真。

修复方法:

import { createLogger } from '../lib/logger'
const logger = createLogger('user-service')
 
export async function updateUser(userId: string, rawData: unknown) {
  const updates = UpdateUserSchema.parse(rawData)
  logger.info('update_user_start', {
    userId,
    fields: Object.keys(updates),  // 只记录字段名,不记录值
  })
  await db.user.update({ where: { id: userId }, data: updates })
  logger.info('update_user_success', { userId })
}

日志安全规则:

规则说明示例
结构化输出使用 JSON 格式,字段独立,不会被注入换行符污染logger.info('event', &#123; userId, action &#125;)
敏感字段脱敏密码、token、手机号不打印明文phone: '138****1234'
输入净化用户输入中的换行符、控制字符在写入日志前去除input.replace(/[\r\n]/g, ' ')
级别正确区分 info / warn / error,不要全部 console.log业务操作 → info,异常 → error

AI 编程安全漏洞对照表

基于 OWASP Top 10 和 CWE 数据库,我整理了 AI 生成代码中最常见的安全问题:

OWASP Top 10CWE 编号漏洞类型AI 代码失败率审查要点
InjectionCWE-89SQL 注入20%是否使用参数化查询
Broken Access ControlCWE-862越权操作~40%服务端是否有权限检查
XSSCWE-80跨站脚本86%输出是否经过 HTML 转义
Log InjectionCWE-117日志注入88%日志是否结构化 + 净化
Cryptographic FailuresCWE-327弱加密算法14%是否使用 AES-256 / bcrypt
Sensitive Data ExposureCWE-200信息泄露~30%代码/日志中是否有硬编码秘密
SSRFCWE-918服务端请求伪造~25%URL 是否经过白名单校验

数据来源:Veracode 2025-2026 GenAI Code Security Report, CSA AI Safety Initiative 2026, Apiiro Enterprise Data

安全工具推荐

人工审查无法覆盖所有场景。我在团队 CI 中集成了以下工具:

工具类型推荐工具作用集成方式
SAST(静态分析)Semgrep、SonarQube扫描代码中的安全漏洞模式Git Hook / CI Pipeline
DAST(动态测试)StackHawk、OWASP ZAP运行中的应用漏洞扫描CI Pipeline
SCA(依赖分析)Snyk、Dependabot检测依赖中的已知漏洞GitHub App / CLI
密钥扫描GitLeaks、truffleHog检测代码中泄露的密钥Pre-commit Hook

最小可行安全流水线:

开发者提交 PR
    → Pre-commit: GitLeaks(密钥扫描)
    → CI: Semgrep(SAST) + Snyk(依赖 + 许可证)
    → Build 成功后: StackHawk(DAST)
    → 全部通过 → 允许合并

开发者心理陷阱

技术工具之外,还有一个我反复提醒团队成员注意的问题:开发者对 AI 生成代码的心理偏差。

80% 的开发者错误地认为 AI 生成的代码比人类写的更安全。这种自动化信任导致审查 AI 代码时,注意力显著降低。研究发现,开发者审查 AI 代码的平均时间只有审查人类代码的 1/3

几个我见过的心态:

  • 「AI 写的应该没问题吧」:功能正确不等于安全。代码能跑,不代表没有漏洞。
  • 「我改了 prompt 让它注意安全」:研究表明,反复优化 prompt 反而使关键安全问题增加 38%——模型在满足更多约束时,容易顾此失彼。
  • 「反正 CI 会扫」:自动化扫描能拦截已知模式,但架构级缺陷(越权路径、业务逻辑漏洞)无法被工具发现。

我的应对方法:对 AI 生成的代码,使用与不信任的外部贡献者代码相同的审查标准。

完整安全检查清单

每次审查 AI 生成的代码时,我逐项过一遍。任何一项未通过,修复后再合并。

输入校验

  • 用户输入是否经过 zod / yup / joi 等库校验
  • 是否限制了类型、长度和格式
  • SQL 查询是否使用参数化,防止注入
  • 文件名、路径是否经过净化,防止路径遍历(../
  • 输出到 HTML 的内容是否经过转义,防止 XSS

权限边界

  • API 端点是否检查用户身份(session / token)
  • 是否检查用户对具体资源的权限(所有者 / 管理员 / 角色)
  • 权限失败时是否返回 401 / 403,而不是 500
  • 前端隐藏的按钮/功能,后端是否也有对应的权限检查
  • 后台任务、定时任务、Webhook 是否也有权限验证

秘密管理

  • 代码中是否有硬编码的密钥、token 或密码
  • 测试夹具是否使用 mock 数据,而不是真实环境配置
  • .env 文件是否在 .gitignore
  • 密钥是否通过环境变量或密钥管理服务读取

依赖安全

  • 新增依赖是否必要,标准库或已有依赖能否完成
  • 包名是否在包管理器(npm / PyPI)中真实存在
  • 依赖是否维护活跃(最近 6 个月有更新)
  • 依赖的许可证是否兼容项目许可证

日志安全

  • 是否使用结构化日志(JSON 格式),而不是字符串拼接
  • 日志是否避免打印完整请求体、鉴权头或个人信息
  • 用户输入写入日志前是否去除了换行符和控制字符
  • 敏感字段(手机号、身份证、密码)是否脱敏

总结

三个核心判断,能挡住大部分风险:

  1. 输入可信吗? — 所有外部输入都不可信,必须校验、净化、参数化。
  2. 权限兜底了吗? — 前端控制是展示,服务端检查才是安全。
  3. 秘密外泄了吗? — 密钥不在代码里,日志不打印敏感信息。

把这三个问题变成审查习惯,再配合自动化工具做 CI/CD 拦截,就能在享受 AI 编程效率的同时守住安全底线。一个有经验的开发者用上面的清单过一遍 AI 生成的代码,只需要 2-3 分钟

AI 生成的代码,先过安全关,再进主线。


参考资料:

评论 0

0 / 1000