1. 从“看代码对不对”到“看边界在哪”AI 生成代码审查的思维转变用 AI 写代码这件事现在基本没有哪个团队能绕开了。不管是补全一个工具函数、生成一段正则、还是让 Agent 直接改好几个文件AI 编程工具已经深度嵌进了日常开发流程。但随之而来的一个尴尬现实是代码能跑不代表代码安全。我见过太多项目CI 全绿、测试全过、上线也没炸结果某天被人从一条没设防的输入路径捅穿回头一查问题代码正是几个月前 AI 顺手生成、大家扫了一眼觉得“逻辑没问题”就合进去的那一段。这就是我想聊的核心AI 生成代码的安全审查重点不是逐行判断“这段代码写得对不对”而是先搞清楚“这段代码站在哪条信任边界上”。传统代码审查的思维是“找 bug”而 AI 生成代码的审查思维应该是“找边界”——因为 AI 写出来的代码语法正确率极高、逻辑自洽度极高它最大的风险恰恰藏在那些“看起来完全合理”的地方它默认输入是干净的、默认调用方是可信的、默认环境是理想的。这篇文章适合三类人看一是日常用 AI 编程、需要对自己提交的代码负责的一线开发者二是负责代码评审、需要给 AI 生成代码把关的技术负责人三是正在把 AI 编程引入团队流程、想建立审查规范的工程效能同学。我会把“三条信任边界”这套方法拆开讲透配上可直接抄的检查清单、实操步骤和踩坑记录让你从今天起审查 AI 代码时脑子里先浮现的不是“这行对不对”而是“这条边界在哪”。所谓信任边界Trust Boundary说白了就是数据从“不可信”流向“可信”的那道关口。生活里类比一下小区大门、单元门、你家防盗门这是三道边界。外卖小哥能进小区大门但不该进你家卧室。代码也一样用户输入、外部接口返回、文件内容、环境变量这些都是“外卖小哥”它们可以进入系统但每跨过一道边界就必须被检查一次身份和权限。AI 生成代码最常犯的错就是把这三道门全拆了让数据一路畅通无阻地走到核心逻辑里。下面我按三条边界逐层展开每条边界都会讲清楚它守的是什么、AI 容易在哪破防、怎么审、怎么补。2. 第一条信任边界外部输入进入系统的入口2.1 这条边界到底在守什么第一条边界是外部不可信数据进入程序内部的第一道关口。典型来源包括HTTP 请求参数、请求体、Header、URL 路径、上传的文件、WebSocket 消息、消息队列里的消息、第三方 API 的返回、甚至是从数据库里读出来但当初就是用户填的字段。这些东西有一个共同特征它们的值完全由外部决定你无法假设它们长什么样。AI 生成代码在这条边界上的破防方式非常典型。你让它写一个“根据用户 ID 查询订单”的接口它大概率会给你这样的东西def get_order(request): order_id request.args.get(order_id) order db.query(fSELECT * FROM orders WHERE id {order_id}) return order逻辑上完全正确功能上完全能跑测试用order_id123也完全通过。但这条代码把第一道边界直接拆了——用户传进来的order_id没有经过任何校验就拼进了 SQL。AI 不是不知道 SQL 注入这回事而是在它生成代码的那一刻它的“默认假设”是输入是良性的。这就是为什么审查 AI 代码时第一眼要看的不是逻辑而是所有外部输入在进入系统时有没有被拦下来。2.2 AI 生成代码在入口边界的四类高频问题我把实际审查中遇到最多的问题归成四类你可以直接拿这个当检查表用。第一类是类型与格式未校验。AI 经常直接拿输入去做运算或索引比如int(user_input)、arr[index]、data[key]它默认这些值存在且类型正确。实际上一旦输入是空、是字符串、是超长内容轻则报错重则越界。第二类是注入类风险。SQL 拼接、命令拼接、模板拼接、路径拼接这四类是最常见的。AI 生成字符串拼接代码的倾向非常强因为它“读起来直观”。比如os.system(ping host)、open(/data/ filename)这些都是把外部输入直接送进了敏感操作。第三类是长度与数量未限制。AI 很少主动给你加max_length、分页上限、数组长度上限。一个没有上限的输入配合一个没有上限的循环就是一个现成的资源耗尽入口。第四类是编码与特殊字符未处理。比如把用户输入直接塞进日志、塞进 HTML、塞进 CSV导致日志伪造、XSS、CSV 注入。AI 生成日志代码时几乎从不做转义因为它觉得“日志嘛记下来就行”。2.3 审查入口边界的实操方法审查这条边界我习惯用一个固定动作顺着数据流走一遍找到每一个“外部值第一次被使用”的位置看它前面有没有一道校验。具体操作分三步。第一步定位所有入口。在 Web 项目里入口就是路由处理函数、消息消费回调、定时任务里读取的外部配置。你可以用搜索的方式快速定位搜request.、req.body、event[、params、query这类关键词把所有外部值的来源列出来。第二步对每个入口值做“三问”。问它有没有做类型校验是不是我期望的类型、有没有做范围校验长度、数值区间、枚举值、有没有做净化处理转义、参数化、白名单。三个问题里只要有一个是“没有”这条边界就是破的。第三步优先用“结构性防御”而不是“补丁式防御”。什么意思与其在每个使用点去转义不如在入口处统一收口。比如所有数据库操作强制走参数化查询所有文件路径强制走白名单目录校验所有命令执行强制走参数数组而不是字符串拼接。AI 生成的代码往往是“哪里用哪里拼”你要做的是把它改成“入口统一净化内部只信净化后的值”。提示审查 AI 代码时不要被“它用了 ORM 所以安全”这种想法骗到。我见过 AI 生成 ORM 代码时用raw()或extra()把用户输入拼进去的ORM 只是工具边界还是得自己守。2.4 一个真实的入口边界修复案例之前审过一段 AI 生成的图片处理接口功能是“根据用户传的文件名读取服务器上的模板图并合成”。AI 写出来是这样def compose(request): name request.args.get(template) path os.path.join(TEMPLATE_DIR, name) img Image.open(path) # ... 合成逻辑功能没问题测试也没问题。但name可以是../../etc/passwd这类路径穿越内容也可以是任意系统文件路径。修复方式不是简单加个replace(.., )而是在入口处做白名单校验import os from pathlib import Path ALLOWED {poster_a, poster_b, poster_c} def compose(request): name request.args.get(template, ) if name not in ALLOWED: raise ValueError(invalid template) path Path(TEMPLATE_DIR) / f{name}.png # 再做一次真实路径归属校验防止软链接绕过 if not str(path.resolve()).startswith(str(Path(TEMPLATE_DIR).resolve())): raise ValueError(path escape) img Image.open(path)这里有两个细节值得说一是白名单优于黑名单因为黑名单永远列不全二是解析后的真实路径校验因为软链接可以让resolve()之前看起来正常的路径指向别处。这两点 AI 基本不会主动帮你写得靠审查时补上。3. 第二条信任边界内部模块之间的调用契约3.1 内部边界为什么反而更危险第二条边界是系统内部模块与模块、服务与服务之间的调用契约。很多人会觉得“内部调用嘛都是自己人不用那么严”。恰恰相反AI 生成代码在内部边界上的问题最隐蔽也最难查因为内部边界没有天然的“外部输入”提醒你这里需要校验。打个比方第一道边界是小区大门有保安第二道边界是单元门通常也有门禁但内部模块调用就像“邻居之间借东西”你默认邻居是可信的。问题是AI 生成的代码经常把“邻居”和“陌生人”混在一起——它会把一个本该只接受内部可信数据的函数暴露成一个谁都能调的公共方法或者在模块间传递数据时默认上游已经校验过了结果上游根本没校验。3.2 AI 在内部边界上的典型破防模式第一种是信任传递断裂。A 模块从外部拿到数据校验后传给 B 模块但 AI 在生成 B 模块时又假设 B 的输入是可信的于是 B 内部不再校验。一旦有人绕过 A 直接调 B比如新增了一个调用方、或者 B 被暴露成了 APIB 就成了裸奔状态。这种问题在微服务和分层架构里特别常见。第二种是权限校验缺失。AI 生成业务方法时往往只关注“这个操作怎么做”不关注“谁有资格做”。比如一个delete_user(user_id)方法AI 会老老实实把删除逻辑写对但它不会主动加“当前登录用户是否有权删除这个 user_id”的判断。这个判断在 AI 看来属于“业务规则”不属于“代码逻辑”所以它默认不写。第三种是返回值与异常契约不一致。AI 生成的两个模块一个返回None表示失败另一个抛异常表示失败还有一个返回{code: -1}。当它们被 AI 串起来时错误处理就乱了异常可能被吞掉失败可能被当成成功继续往下走。第四种是共享状态被隐式修改。AI 生成代码时喜欢用全局变量、类属性、缓存对象来“省事”结果一个模块改了共享状态另一个模块读到脏数据。这类问题在并发场景下尤其致命。3.3 审查内部边界的核心动作画契约表审查这条边界我的做法是给每个跨模块调用画一张契约表。表里至少包含四列调用方、被调方、传入数据的可信级别、被调方是否自行校验。下面是一个示例调用方被调方数据可信级别被调方是否校验风险API 层订单服务已校验否低定时任务订单服务未校验否高内部工具脚本订单服务未校验否高订单服务支付服务已校验是低这张表一画出来问题立刻现形订单服务假设所有调用方都校验过了但定时任务和工具脚本根本没校验。修复方案有两个方向要么在订单服务入口补校验防御性编程要么强制所有调用方走统一的校验层。我一般推荐后者因为前者会让每个服务都重复校验维护成本高。3.4 权限校验AI 最容易漏的一环内部边界里最需要人工补的是权限校验。AI 生成代码时你给它一个“删除评论”的需求它会写def delete_comment(comment_id): comment Comment.get(comment_id) comment.delete()逻辑完美但它不知道“只有评论作者或管理员才能删”。你得手动补上def delete_comment(comment_id, current_user): comment Comment.get(comment_id) if comment is None: raise NotFound() if comment.author_id ! current_user.id and not current_user.is_admin: raise PermissionDenied() comment.delete()这里的关键认知是权限校验不是业务逻辑的附属品而是安全边界的一部分。AI 不会主动帮你加因为它把“谁能做”当成了需求描述之外的东西。审查时你要专门问一句“这个操作谁有资格触发代码里体现了吗”注意权限校验要放在数据读取之后、敏感操作之前。我见过 AI 生成的代码先执行了删除再检查权限这种顺序错误在审查时一定要盯住。3.5 内部边界的防御性编程原则补内部边界我遵循一个原则不信任任何调用方包括自己。具体落地就是三条每个对外暴露的服务方法入口处做一次轻量校验类型、非空、范围成本很低但能挡住大量问题。权限判断统一收口到一个鉴权函数不要散落在各个业务方法里否则 AI 下次生成新方法时又会漏。模块间传递的数据用明确的结构比如 dataclass、DTO而不是裸 dict这样字段缺失和类型错误能在早期暴露。这三条看起来是“多此一举”但实测下来它们能把内部边界的安全问题挡掉八成以上。AI 生成代码的速度越快这种“结构性防御”的价值就越高因为你不可能逐行去审 AI 写的每一段逻辑但你可以审“边界有没有守住”。4. 第三条信任边界AI 生成代码本身的信任级别4.1 把 AI 当成一个“不可信的贡献者”第三条边界最容易被忽略因为它不是代码里的边界而是流程上的边界你到底该给 AI 生成的代码多高的信任级别我的观点很明确把 AI 当成一个能力很强、但完全不可信的贡献者。它像一个刚入职、技术很好、但完全不了解你们系统历史包袱和安全规范的新人。你会让新人直接往主分支推代码吗不会。你会让他写完自己 review 一遍就上线吗也不会。那 AI 生成的代码凭什么待遇不一样这条边界守的是“AI 输出进入代码库”这个关口。它包含三个层面AI 生成的内容是否被正确理解、是否经过独立验证、是否走了和人类代码同等的审查流程。4.2 AI 生成代码的三个信任陷阱第一个陷阱是理解幻觉。AI 生成的代码读起来太顺了顺到你觉得自己“看懂了”其实你只是“读通了”。这两者差别很大。读通是语法和逻辑层面没障碍看懂是你能说清楚它在边界情况、异常情况、并发情况下会怎么表现。我踩过的坑就是一段 AI 生成的缓存逻辑我扫了一眼觉得没问题结果它在缓存穿透时会把空值也缓存进去导致后续请求全部命中空缓存。这个细节我当时根本没“看懂”只是“读通”了。第二个陷阱是测试通过幻觉。AI 生成的代码配上 AI 生成的测试很容易形成“自证清白”的闭环。测试全过但测试本身可能只覆盖了 happy path边界情况一个没测。更糟的是AI 生成的测试有时会“迎合”实现实现有 bug测试也跟着错两边一起错看起来还很和谐。第三个陷阱是依赖幻觉。AI 生成代码时会引入它“记得”的库和 API但版本可能不对、方法可能已废弃、行为可能和你项目里的版本不一致。它不会去查你的requirements.txt它凭的是训练时的记忆。4.3 建立 AI 代码的独立验证流程针对这三个陷阱我建议的流程是AI 生成人工理解独立验证同等审查。四步缺一不可。“人工理解”这一步我有个具体做法让写代码的人或审代码的人用自然语言复述这段代码在三种情况下的行为——正常输入、边界输入、异常输入。如果复述不出来说明没真懂打回去重看。这个动作看起来笨但极其有效它逼着人从“读通”进入“看懂”。“独立验证”这一步关键是测试要独立于实现来写。不要让 AI 同时生成实现和测试而是先明确测试用例尤其是边界和异常用例再让实现去满足测试。或者更简单人工补几个 AI 没想到的边界测试专门打它的默认假设。“同等审查”这一步是流程上的硬要求AI 生成的代码必须走和人类代码完全一样的 PR 流程、一样的 review 标准、一样的 CI 检查。不能因为“这是 AI 写的应该没问题”就放松。恰恰相反我建议对 AI 生成的代码提高审查标准尤其是涉及第一条和第二条边界的地方。4.4 一个可落地的 AI 代码审查清单下面这张清单是我实际在用的你可以直接拿去改。它按三条边界组织每条边界几个必查项。边界检查项通过标准入口边界所有外部输入是否校验类型/范围/格式每个入口值都有明确校验入口边界是否存在字符串拼接进 SQL/命令/路径全部改为参数化或白名单入口边界输入长度和数量是否有限制有明确上限内部边界跨模块调用的数据可信级别是否明确有契约表或注释说明内部边界敏感操作前是否有权限校验每个敏感操作都有鉴权内部边界错误处理契约是否一致统一异常或统一返回结构AI 信任边界审查者能否复述代码的边界行为能复述正常/边界/异常三种情况AI 信任边界测试是否覆盖边界和异常有独立于实现的边界测试AI 信任边界是否走了同等或更严的审查流程PR 流程完整CI 通过这张表不用每次都全查但涉及核心链路、涉及用户数据、涉及资金或权限的代码我建议逐项过一遍。实测下来一张表过一遍大概多花十到二十分钟但能挡掉的问题价值远超这个时间。5. 三条边界的协同从单点检查到体系化防御5.1 边界之间会互相“甩锅”单独看每条边界都好理解但实际项目里问题往往出在边界之间的衔接处。最典型的“甩锅”场景是入口边界觉得“我校验过了内部随便用”内部边界觉得“入口都校验了我不用再校验”结果中间某个环节数据被转换、被拼接、被缓存校验过的值变成了没校验的值两边都以为对方守着实际上门是开的。举个我遇到过的例子入口处校验了user_id是数字然后把它放进缓存 key缓存层从 key 里解析出user_id时用的是字符串拼接结果缓存 key 可以被构造出冲突导致 A 用户读到 B 用户的数据。入口校验没错缓存层也没“错”错在两者之间的信任传递断了。所以审查时除了看每条边界还要看数据在边界之间流动时可信级别有没有发生变化。一旦数据被重新组装、被序列化再反序列化、被放进共享存储再取出它的可信级别就应该被重新评估。5.2 用“数据流图”把三条边界串起来我的做法是画一张简单的数据流图不用工具纸上画就行从外部输入开始标出数据经过的每个模块、每次转换、每次存储然后在图上标出三条边界的位置。标完之后重点看两件事一是有没有数据流绕过了某条边界二是有没有数据在跨边界时可信级别被悄悄提升。这个动作在审查 AI 生成的大块代码时特别有用因为 AI 生成的代码往往是“局部正确、全局割裂”的单看每个函数都没问题串起来就有漏洞。画图能逼着你从全局视角看边界。5.3 把边界检查固化进流程单次审查靠人盯长期靠流程。我建议把三条边界的检查固化到几个关键节点提交前用静态检查工具扫一遍注入类、路径类、命令类风险这类工具对 AI 生成的拼接代码命中率很高。PR 审查时用上面的清单过一遍重点看权限校验和边界校验。上线前对涉及外部输入和敏感操作的接口做一次针对性的边界测试专门打异常输入。定期复盘把线上发现的问题回溯到三条边界上看是哪条边界失守然后补规则。这套流程跑顺之后AI 生成代码的审查会从“每次都要重新想”变成“按清单走”效率和覆盖率都会明显提升。6. 实操中踩过的坑与排查技巧6.1 几个印象深刻的真实坑第一个坑是AI 生成的“安全代码”反而更危险。有次让 AI 写一个防注入的查询它确实用了参数化但参数化的方式是把用户输入做了escape之后再拼接。看起来是“处理过了”实际上escape函数是它自己编的逻辑有漏洞。这个坑的教训是AI 说它做了安全处理你要看它具体怎么处理的不能只看它“提到了安全”。第二个坑是异常被静默吞掉。AI 生成的代码里try/except经常写成except Exception: pass或者except: return None。这在边界上极其危险因为校验失败、权限失败、连接失败全被吞了程序继续往下走用错误的数据做危险的事。审查时看到宽泛的except一定要问“这里吞掉异常之后程序会怎么继续”。第三个坑是默认配置太宽松。AI 生成配置文件时倾向于给“能跑通”的默认值比如 CORS 允许所有来源、调试模式默认开启、超时时间设得很长、重试次数不设上限。这些默认值在开发环境没问题上了生产就是边界漏洞。6.2 常见问题速查表现象可能失守的边界排查方向接口能查到别人的数据内部边界权限检查数据查询是否带当前用户过滤特殊字符导致报错或异常行为入口边界检查输入校验和转义服务被大量请求拖垮入口边界检查长度、频率、并发限制缓存数据串号边界间信任传递检查缓存 key 构造和解析错误被忽略后继续执行内部边界契约检查异常处理和返回值契约依赖升级后行为异常AI 信任边界检查 AI 引入的 API 是否匹配当前版本6.3 几条压箱底的经验第一条经验审查 AI 代码先看它“假设了什么”再看它“做了什么”。AI 的假设往往藏在它没写的地方——没写校验、没写鉴权、没写异常处理这些“没写”才是风险所在。第二条经验边界检查要“宁可错杀”。对 AI 生成的代码我倾向于把校验加得比“必要”更严一点。因为 AI 生成代码的成本极低多写几行校验的代价很小但漏掉一个边界的代价可能很大。第三条经验把每次踩的坑变成一条检查规则。我有个习惯每次线上或测试发现 AI 代码的问题就把它抽象成一条检查项加进清单。时间长了清单越来越厚但审查反而越来越快因为大部分坑已经被规则挡住了。第四条经验不要指望 AI 自己守边界但可以让 AI 帮你查边界。审查时我会把三条边界的检查清单直接喂给 AI让它先自查一遍然后我再人工过。AI 自查能发现一部分明显问题人工复查负责兜底。这个组合比纯人工快也比纯 AI 稳。7. 写在最后的一点个人体会这套“三条信任边界”的方法我用了一年多最大的感受是它把 AI 代码审查从“凭感觉”变成了“有抓手”。以前审 AI 代码容易陷入“逐行看逻辑”的疲劳战看久了就麻木觉得哪哪都没问题。现在有了边界这个视角审查变成了“找关口、看关口有没有人守”目标明确也不容易漏。另外一个体会是AI 生成代码的安全问题本质不是 AI 的问题而是流程的问题。AI 只是把“写代码”这件事加速了它没有改变安全的基本规律数据从不可信到可信必须过边界跨边界必须校验。这个规律在 AI 出现之前就成立AI 出现之后只是被放大了——因为代码产出速度变快边界失守的概率和影响都被放大了。所以我的建议是别把精力花在“怎么让 AI 生成更安全的代码”上那个方向收益有限因为 AI 的默认假设你很难完全控制。把精力花在“怎么在流程上守住三条边界”上这个方向收益确定而且可积累。清单可以越来越完善规则可以越来越细团队可以越来越熟练。这才是应对 AI 编程时代安全挑战的可持续打法。最后分享一个小技巧如果你刚开始用这套方法别一上来就全项目铺开。挑一个涉及用户数据或敏感操作的模块用三条边界过一遍把发现的问题和补的规则记下来。跑通一个模块之后你会对“边界在哪”有手感再推广到其他模块就顺了。安全审查这件事手感比理论重要而手感只能靠一次次实操练出来。