ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AI生成代码的安全护栏:从提示词到自动化扫描的落地实践

AI生成代码的安全护栏:从提示词到自动化扫描的落地实践 你是不是也习惯了这样写代码需求一句话AI 帮你补半屏遇到报错直接贴给 AI几秒钟就拿到修复版一个人顶得上以前三个人的产出速度。这是真的爽但我最近越来越觉得AI 写代码越快安全这条线反而越容易被勒断。原因很简单人为写的代码写的人至少对业务上下文、数据流向、边界条件有感觉AI 写出来的代码往往只对“这条 prompt 要怎么满足”有感觉。它不了解你的系统全貌不认识你的用户数据也不会为一次越权调用负责。你越快接受它给的代码越没时间看清它挖的坑。所以我说在让 AI 加速之前有几条安全缰绳必须提前套上否则迟早翻车。这篇文章不聊泛泛的“注意安全”就聊我在实际项目里怎么给 AI 写代码这件事上安全护栏的包括提示词层面的约束、依赖层面的锁定、密钥层面的扫描、权限层面的收敛以及最后落地的自动化检查。每一条都是我自己踩过坑之后总结出来的做法你可以直接照着抄。1. 为什么 AI 写得越快安全包袱反而越重1.1 代码产出的“加速度”填补了审查的“减速带”先算一笔账没 AI 的时候一个后端接口从写出来到合入要经历自己 review、同事 review遇到可疑逻辑还会翻半天文档确认边界条件。整个过程是天然的减速带代码合入速度慢问题暴露的概率也相对可控。有 AI 之后接口生成只要几十秒。你从生成到合入可能只花五分钟因为信任它“上下文完整”的假象。问题在于AI 生成的代码质量看概率而安全审查的强度没跟上以前十倍产出配三倍审查还能兜住现在一百倍产出配零点五倍审查基本是裸奔。我的实际体感是AI 生成的代码在“功能正确性”上确实不错但在“安全防御”上经常是一张白纸。比如生成一个文件上传接口功能完全正常但它不会主动校验文件类型不会限制文件大小更不会考虑文件名路径穿越。在 AI 眼里这些都不影响“接口能不能跑通”。所以第一条缰绳不是技术工具而是流程上的认知AI 写得越快你越要强制自己用更慢的节奏去审查它产出的东西绝不能因为“它看起来能跑”就跳过安全评估。1.2 AI 生成的代码会引入哪些典型安全漏洞我把 AI 写代码常见的安全漏洞做了个归类基本集中在下面几类注入类AI 生成 SQL 拼接、命令行拼接、正则表达式时很容易直接拼字符串。你稍微放松 prompt 约束它就给你来一段fSELECT * FROM users WHERE name {name}等于把数据库裸奔给用户。越权类AI 不清楚你的权限模型生成接口时不会自觉加“当前用户只能操作自己的资源”这类判断结果就是水平越权满大街。敏感信息硬编码AI 为了让你“.env.example 和配置文件能跑通”, 经常把sk-xxx、password123456、AccessKey直接写进示例代码你要是 CTT 不加思考地复制等于把钥匙挂门口。依赖投毒AI 引用的第三方库可能是真实存在的也可能是它“幻觉”出一个接近真实包名的库。更危险的是它推荐的库版本可能已经存在已知 CVE。逻辑绕过AI 写验证码校验、支付逻辑、频率限制时经常只覆盖“正常路径”直接跳过了“异常路径”。比如它生成的限流代码可能只在入口层做一次绕过去就能随便刷。这五类漏洞有个共同特点静态看代码时它们都“能跑”只有出事了才知道这里有个坑。所以后面几节讲的所有护栏本质上都在对付这五类问题。1.3 供应链风险是隐藏最深的一条暗线如果说上面五类漏洞是“代码层面的问题”供应链风险就是“生态层面的问题”。AI 生成代码必然大量引用第三方依赖而它的知识库是有时效和覆盖盲区的。举个例子让 AI 写一个解析 YAML 的 Python 工具它可能会推荐pyyaml搭配yaml.load()。问题在于yaml.load()如果不指定Loader在部分版本下可以反序列化出任意 Python 对象直接造成 RCE 风险。AI 并不会在注释里提示你“这个用法有历史安全坑”它只会给出最常见的写法。再比如AI 推荐 npm 包时可能把lodash写成lodahs或者推荐一个功能相似但维护已停滞的库。你安装时未必注意包名用了半年才发现这个包每周下载量只有几十次漏洞也没人修。所以我说供应链安全是 AI 时代最容易被忽视的暗线AI 生成的代码本来就是一个拼接体它拼进来的每块积木质量如何你得自己查。2. 缰绳一提示词约束与人工审查的双重闸门2.1 给 AI 的提示词就要立好规矩很多人觉得提示词只是“描述需求”其实提示词也是安全策略的第一落点。我给团队定的几条 prompt 约束实测下来效果很明显要求 AI 在涉及安全的关键位置主动加注释比如# SECURITY_REVIEW: 此处需要校验用户权限这样后续 code review 一眼就能看到高风险点。禁止 AI 在示例代码中直接使用真实密钥、密码和 Token可以用your_api_key_here这类占位符。要求 AI 生成 SQL 时使用参数化查询而不是字符串拼接生成命令时使用列表形式传给 subprocess而不是 shellTrue。要求 AI 自己说明潜在安全风险哪怕是一句话“该代码未做输入长度限制生产环境建议增加校验。”注意prompt 约束不是万能的它只是提高了 AI 输出的安全底线。真正的安全还是得靠后续的扫描和人工兜底千万别以为“我在提示词里说了要安全”就万事大吉。我自己的模板一般是这样的你是一个资深后端工程师精通安全编码规范。请帮我生成一个文件上传接口要求 1. 使用参数化 SQL如适用 2. 不使用任何硬编码密钥敏感信息一律读取环境变量 3. 禁止使用 eval、shellTrue、exec 等危险函数 4. 涉及权限校验时使用当前登录用户的身份信息进行判断禁止直接信任前端传入的 userId 5. 如果代码中存在安全敏感点必须在注释中标注 SECURITY_REVIEW 6. 生成完成后简要列出你认为可能存在安全风险的地方这样生成的代码虽然不能保证零漏洞但至少把“最常见的坑”提前堵上了。后面审查代码时你也能更快定位到 AI 自己都觉得没把握的地方。2.2 强制人工审查AI 代码必须走 PR 流程有了 AI就能跳过 PR 直接推送主干这是我这几年见过的最危险的习惯。AI 生成代码之后至少要经过一轮人审再合入这条规矩不能破。人审的重点不是“逐行阅读”而是“带着清单去审”。我现在 review AI 代码时一定会过一遍这几个点输入从哪来、信不信得过所有来自用户输入的数据都假设不可信追着看它的流向。输出要不要脱敏返回给前端的数据里有没有多余的敏感字段AI 经常把整张表 select 出来全量返回。有没有鉴权和限流这个接口是不是任何人都能调频率限制在哪做依赖来源清不清楚引用的每个库包名、版本、维护状态我都得看。异常路径有没有覆盖AI 只写了正常流程那数据库连不上、请求超时、参数非法呢提示不要试图审查 AI 生成的每一行代码那样你又会变成瓶颈。正确的姿势是抓大放小用它生成的代码跑测试和扫描但涉及资金、用户隐私、系统权限的模块一定要人工重点看。2.3 一个“看似无害”的代码片段可能埋了什么坑去年我见过一个案例至今印象深刻。同事让 AI 写一个“导出用户列表为 CSV”的功能AI 生成了一段看起来人畜无害的代码import csv from django.http import HttpResponse def export_users(request, user_ids): response HttpResponse(content_typetext/csv) response[Content-Disposition] attachment; filenameusers.csv writer csv.writer(response) for uid in user_ids: user User.objects.get(iduid) writer.writerow([user.username, user.email]) return response单看这段代码功能完全正确。但我一看就发现了三个问题CSV 注入如果 username 字段里包含cmd| /C calc!A0这样的内容导出后用 Excel 打开可能触发公式执行。越权直接根据传入的 uid 去查用户没校验这个 uid 是否属于当前请求者有权导出的范围。性能User.objects.get()在循环里逐条查询数据量一大直接打爆数据库。AI 不会告诉你“这个写法用 Excel 打开有风险”因为它根本不知道你的数据会被什么工具打开。人工审查的价值就在这AI 提供的是语法正确的代码你得提供“环境上下文”。3. 缰绳二依赖锁定与供应链安全扫描3.1 锁死不等于永远不升级而是“可追溯地升级”很多人对依赖锁定有误解觉得锁死版本就是一劳永逸。实际上锁死不等于不升级而是升级过程要可控、可追溯。在 Python 侧我要求项目必须提交requirements.txt或poetry.lock并且锁定到具体小版本不能只写numpy1.20这种范围。在 Node 侧必须提交package-lock.json或yarn.lock。没有 lock 文件的项目我会在 CI 里直接挂红灯。锁定版本的意义不只是“复现构建”更在于你得知道线上跑的是哪个版本的代码。一旦某个依赖爆出 CVE你才能快速定位“我们的服务是否受影响”而不是翻开三年前的安装记录慢慢查。升级方面我的原则是“定期升而不是随手升”。每周或每两周看一次依赖更新报告把安全相关的升级单独挑出来评估测试功能升级可以慢一点。这样既不会因为版本滞后裸奔也不会因为频繁升级引入新的兼容问题。3.2 用自动化扫描盯住已知漏洞手动查 CVE 是查不过来的必须上自动扫描。我常用的组合是Pythonpip-audit扫描依赖已知漏洞简单直接。Nodenpm audit或yarn audit注意设置门槛不能只看 warn。通用 SCASnyk、Trivy、OWASP Dependency-Check 都可以选一个用熟就行。在 CI 里可以这样写- name: 依赖安全扫描 run: | pip-audit --formatjson --fail-onhighnpm audit --audit-levelhigh提示--fail-onhigh的意思是至少 high 级别的漏洞会让流水线失败。有些团队图省事直接把 audit 结果当参考、不阻断结果漏洞报告越堆越多最后成了废纸。还不如一开始就设置一个可接受的门槛。我实测下来pip-audit对 Python 项目的覆盖率不错npm audit对 Node 的覆盖面也可以但它们依赖各自官方漏洞库存在盲区。更稳妥的做法是“双保险”本地用包管理器的内置 auditCI 里再挂一个 SCA 工具互相补盲区。3.3 警惕“包名钓鱼”与版本投毒AI 时代的供应链攻击里有一种特别阴的玩法叫“包名钓鱼”。攻击者故意注册一个和知名库只差一个字母的包名比如requests和requets然后上传到 PyPI/npm 公共仓库等待开发者手滑安装。AI 在生成代码时如果知识库里有这些相似名称它完全可能推荐到错误的包。所以我现在规规矩矩做三件事安装新依赖前先查一下这个包是否存在下载量多少维护者是谁。优先使用已知的大版本和近三个月仍在维护的版本避免 2018 年之后没更新过的老古董。对 AI 推荐的依赖做一次“存疑审查”这个包的功能是它自己实现的还是必须引第三方库如果必须引有没有更主流的替代品一句话AI 能为你节省写代码的时间但省不了“这个依赖能不能信”的尽职调查时间。这个时间不能省省了迟早要加倍还。4. 缰绳三密钥管理与敏感信息防泄漏4.1 代码里出现一切硬编码密钥都是事故前兆AI 生成代码时为了让你“开箱即用”特别喜欢在配置示例里塞SECRET_KEYyour-secret-key、passwordadmin123、api_keysk-xxxx这类占位符。你复制到真实环境如果忘记换掉那就是事故。更隐蔽的是 AI 生成 Dockerfile 或 CI 配置时的写法它可能把环境变量直接写在docker run命令里或者把 token 放在 pipeline 脚本的明文字段里。这些都会在日志、历史记录里留下久久不散的痕迹。我们的项目里现在有一条硬性规定代码仓库中禁止出现任何形式的真实密钥包括私钥、密码、Token、连接串发现一个记录一次。这条规定不是靠人盯而是靠自动化扫描落实。4.2 让密钥扫描成为提交前的一道关卡我团队里现在用 Gitleaks 和 TruffleHog 扫密钥配合 git-secrets 做本地的 Git hook效果不错。具体做法是本地安装git-secrets在.git/hooks/pre-commit里加一道检查检测到高置信度的密钥模式直接拦截提交。CI 里再跑一次 Gitleaks作为兜底防止有人绕过本地 hook。定期用 TruffleHog 扫整个 Git 历史把历史提交里的漏网之鱼抠出来。如果你用的是 GitHub/GitLab也可以开启平台自带的服务端密钥扫描。但我不建议只靠平台因为平台只管“已知格式”的密钥内网地址、自定义格式的敏感串它经常识别不出来。4.3 一个真实翻车现场把 AccessKey 写进了前端代码我在一个外包项目里接过一次烂摊子前端调用对象存储时直接在前端 JS 里嵌入了 AccessKey ID 和 AccessKey Secret前端代码又是打包分发到用户浏览器的。结果上线没几天就发现存储桶里被人传了一些不明境外木马账单直接爆表。等我们紧急轮换密钥、清理存储桶、把前端改造为后端代理签名时已经损失了钱也差点丢了客户信任。这个事故的核心教训就一条AI 帮你生成“能用的代码”时永远只考虑功能不考虑分发边界。它不会知道前端代码最终会落到用户手里于是把密钥写在里面。你作为开发者必须自己掌握这个边界。注意如果你发现自己已经不小心把密钥提交到了 Git 仓库哪怕后来删掉了历史记录里还在。正确做法是立即撤销密钥并重新生成而不是试图用“删除提交”来补救。密钥一旦进过仓库就要默认它已经泄露。5. 缰绳四最小权限与运行环境隔离5.1 账号权限往最小给别怕麻烦AI 写代码的另一大危险是它生成的运行时配置经常给你“管理员待遇”。比如 AI 生成的 Dockerfile 里可能直接以 root 运行应用数据库连接用的可能是有 DROP 权限的超级账号云厂商的 API 密钥也可能绑定了极高权限的 IAM 角色。我见过一个典型的例子一个数据分析平台需要用 AI 生成 SQL 查询结果 AI 给的连接配置用的是数据库管理员账号。它的理由是“这样最不容易出错什么查询都能跑”。事实也确实如此但代价是任何人拿到这个连接串就能删库。正确的做法是分级授权查询类功能只用只读账号权限范围限制到具体库表。写入类功能用只写账号或按业务拆分的账号限制 DDL 权限。管理类功能绝不放给业务服务永远留在运维侧。放到云厂商上同理每个服务用一个独立的角色/凭证权限按最小集授予哪怕某个服务被攻破攻击者也只能在这个角色的权限范围内活动捅不破天。5.2 容器与沙箱给 AI 写的代码一个“隔离笼子”AI 生成的应用如果跑在裸金属或直接跑在宿主机上风险是最大的。你必须给它套一个容器或沙箱。而且容器内部还要继续收敛权限以非 root 用户运行进程别用默认 root。文件系统尽量只读只有必要目录挂载可写卷。关掉不必要的网络连接容器内只有一个监听端口暴露给外部。设置 CPU、内存、文件句柄等资源限额防止代码失控拖垮宿主机。这些配置不会让 AI 写的代码“更安全”但会把它出事时的爆炸半径限制在可控范围内。就像你实验室里做化学实验把易爆品放进防爆柜——不会让它更稳定但万一出事不会波及整栋楼。我给团队定的 Dockerfile 基线长这样FROM python:3.12-slim RUN useradd --create-home --uid 1000 appuser WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY --chownappuser:appuser . /app # 应用以普通用户身份运行 USER appuser EXPOSE 8000 CMD [uvicorn, app.main:app, --host, 0.0.0.0, --port, 8000]注意USER appuser这一行是精髓。很多 AI 生成的 Dockerfile 都会漏掉这一行默认用 root 跑等于把容器当作宿主机来用隔离效果直接清零。5.3 数据库账号也要分级别让业务代码直连管理员对于 AI 写代码的项目数据库账号分级尤其重要。原因是 AI 会生成大量动态查询而你没时间逐个审查每条 SQL 的边界。这时候如果账号本身就是最小权限的它就算用 AI 查错了也闹不出大乱子。我目前的做法是每个服务单独建库、单独建账号不共用一个高权限账号。服务账号只授权本服务需要的表、视图、存储过程。生产环境禁止在配置里明文写连接串统一走环境变量或密钥管理。数据库开启慢查询和审计日志如果 AI 生成的代码出现了全表扫描日志里能看到以便及时修复。这里要特别提醒一点很多团队为了让 AI 更容易生成正确的 SQL把数据库 schema 全文贴进 prompt。这个做法很高效但意味着 AI 的返回里可能包含表结构信息而你的聊天记录会留在第三方服务器上。涉及敏感业务的库建议把 schema 做脱敏处理再提供给 AI 使用。6. 缰绳五自动化扫描与持续安全测试6.1 静态扫描SAST应该跑在提交之前AI 写代码速度越快静态扫描就越要前置。你不能等到代码合入主干再扫那时候问题已经进版本了回滚成本高。最理想的是在 IDE 阶段就跑本地提交时再跑一次CI 阶段再兜底一次。我团队现在的 IDE 插件组合是 Ruff 加 Bandit。Ruff 负责代码风格和常见错误Bandit 专注安全相关的 Python 模式检测比如硬编码密码、eval调用、subprocess使用不当等。每次保存文件IDE 自动跑一遍有 warning 就提示有 error 就先别提交。在 CI 阶段我更强调整体的扫描覆盖。以下是一段基础的 GitLab CI 安全扫描配置可以直接参考security-scan: stage: test script: - bandit -r app/ -f json -o bandit-report.json || true - pip-audit --formatjson --fail-onhigh || true - gitleaks detect --report-format json --report-path gitleaks-report.json || true artifacts: when: always paths: - bandit-report.json - gitleaks-report.json有人会觉得“这扫描挂了还继续执行干嘛”其实我是故意用|| true的目的是让扫描工具跑完并生成报告方便开发者在 MR 页面看到完整结果。后续环节会根据报告内容决定是否阻塞合入比直接中断流水线更灵活。6.2 依赖风险入库建立软件物料清单依赖扫描如果只是“报个错就完”那价值会越来越低。真正有用的做法是把依赖和风险纳入资产管理也就是常说的 SBOM软件物料清单。我现在的做法是每个服务在流水线里生成一份 SBOM 文件里面记录着这个服务目前使用的所有依赖及其版本然后把 SBOM 提交到一个集中管理系统。这样一旦爆出某个通用的 CVE我可以秒查“哪些服务在用这个库”而不是靠人肉翻代码。生成 SBOM 的工具很多Python 用cyclonedx-bom或pip-licensesNode 用cyclonedx/cyclonedx-npm。SBOM 不需要太复杂能包含包名、版本、许可证和来源就是及格。关键是把这个流程固定到发布流程里不然第一版有后面就忘了。6.3 Agent 类场景的额外安全约束最近的 AI 编程工具越来越往 Agent 方向发展不光生成代码还能帮你直接跑命令、改文件、提交代码。这个方向的提升效率是真明显但也把安全风险推到了新高度。我使用 Agent 类工具时一定会加这些限制给它一个独立的只能访问当前项目的目录禁止读取/etc、~/.ssh、~/.aws等敏感路径。设置“命令执行批准”模式任何有副作用的命令都必须经我确认后才执行。将仓库里允许 Agent 修改的文件范围限定在源码目录涉及 CI 配置、部署脚本、密钥文件的地方全部设为只读。禁止 Agent 在代码中引入它无法解释的依赖。如果你的 Agent 工具支持“网络访问白名单”或“命令白名单”尽量打开。不要让它拥有和你本人完全相同的权限否则一个 prompt 被注入它可能替你执行危险操作。6.4 一套可直接落地的安全基线配置清单最后把我给团队制定的“AI 写代码安全基线”整理成清单你可以直接抄类别检查项建议工具触发时机静态安全分析高置信度漏洞模式扫描Bandit / Ruff / SemgrepIDE、提交前、CI密钥扫描扫描历史与当前代码中的密钥Gitleaks / TruffleHog / git-secrets提交前、CI、定期全量依赖漏洞扫描已知 CVE 与异常依赖pip-audit / npm audit / Snyk提交前、CI、定期软件物料清单生成并归档依赖清单CycloneDX 系列工具构建发布时代码审查人工按清单审查 AI 生成的高风险模块团队内部 PR 流程合入前权限收敛非 root 运行、最小 IAM 权限、分级 DB 账号Dockerfile / IAM 策略 / 数据库配置环境搭建时安全测试接口层安全测试重点验证鉴权与限流OWASP ZAP / 自建脚本发布前每一项都很简单真正难的是把它们都串成流水线并保证每次都跑不因为“这次改动小”而跳过。改一行代码也是一次攻击面变化不要给安全留下“特例”。6.5 常见问题排查速查表问题 1AI 生成的代码引用了不存在的库怎么防不要直接安装。先在 pip/npm 官方源搜一遍确认包名和下载量再看最后一次更新时间。如果这个包最近几个月没有版本更新说明维护者可能已经跑路了换一个更主流的选择。问题 2CI 里npm audit报了一堆 low怎么办low 级别的可以先记录在案暂不阻断。但如果是 high/critical别犹豫直接升级或找替代方案。如果 npm audit 给出的升级建议是“Major 破坏性升级”那就把升级拆出来单排一轮别夹在紧急需求里硬上。问题 3本地扫描没问题怎么线上还是被扫出漏洞大概率是“扫描的代码”和“线上的代码”不一致。常见原因是 CI 没有在发布前拉最新代码或者构建镜像时使用了未锁定的依赖版本。优先检查构建过程是否严格使用了 lock 文件。另一个可能是扫描工具的规则库没有更新记得给扫描工具设置定时升级策略。问题 4AI 生成的代码里有一大堆 try-except 空吞异常安全吗不安全。空吞异常意味着报错被隐藏调用方拿到的是“未知成功”后续流程在错误假设下继续跑越权、数据损坏等问题常常由此引发。AI 之所以爱这么写是因为它不知道特定业务里吞掉异常会有什么后果。你在 review 时看到空 try-except果断让它补上日志和兜底策略别因为“它写的代码能跑”就放过去。问题 5密钥已经扫出来了但历史提交里还有怎么处理先把密钥撤销置废再清理历史。清理历史用git filter-repo或 BFG然后把所有引用旧提交的分支强推。但记住本地克隆过仓库的人手里的历史并不会自动消失所以最终兜底还是“撤销密钥重新生成”而不是期望删除记录能找回安全。我在自己的开发流里已经习惯把上面这几条缰绳当成和“写代码”本身同等重要的事来做了AI 生成的代码先过一遍提示词约定再跑一遍本地扫描提交时密钥检查和依赖检查双把关CI 再兜底一轮。这套流程跑顺之后我的团队既体验了 AI 带来的高速产码也没再因为越权、密钥或依赖问题出过安全事故。建议大家也从最小可落地的组合做起比如先把 Gitleaks 和 Bandit 接上剩下的一步步补安全这件事本来就不是一次性的它是跟着你产出的每一行代码走的。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进