文章给出生产级 AI 代码平台的安全框架,覆盖生成链路威胁建模、提示注入防护、模型凭据保护、代码沙箱和持续监控。其重点是同时审视进入模型的提示与离开模型的可执行代码,并梳理资产、入口和攻击者。
如果你已经搭建了一个 AI 生成代码平台,并且它现在开始承载真实用户流量,那么首先需要做的就是筑牢安全防线。简而言之,你需要对代码生成流水线进行威胁建模、防御 prompt injection、保护模型凭证、隔离运行每一段代码,并接入持续监控。下面是一份今天就能落地执行的具体检查清单。
我是一名后端工程师,负责在生产环境中运行 FastAPI、AI 和数据服务。接下来要讲的每一种故障模式,我几乎都亲身踩过坑,也为此付出了服务停机、数据泄露和开发工时浪费的代价。本文浓缩了这些来之不易的经验,重点讨论 AI 生成代码平台的安全问题。
答案是:先梳理所有涉及模型和生成代码的数据流。AI 驱动平台的威胁建模与传统的 OWASP ASVS 非常相似,但需要额外关注两条边界——进入模型的 prompt,以及模型输出的代码。
识别资产——模型权重、API keys、用户 prompt、生成的代码片段和日志。
识别入口——HTTP endpoints、WebSocket streams、CLI tools 和 CI pipelines。
识别攻击者——恶意用户、被攻陷的 CI runners、内部开发人员和第三方服务。
识别威胁——prompt injection、凭证泄露、代码执行、数据外泄和许可证违规。
画出关系图后,根据影响程度和发生概率对每项威胁进行排序。对大多数开发者而言,风险最高的通常是 prompt injection 和不安全的代码执行。应当优先针对这两项风险采取缓解措施。
assets:
- model_weights: "private S3 bucket"
- api_keys: "Vault secret"
- user_prompt: "HTTP POST /generate"
- generated_code: "temp file in /tmp/snippets"
entry_points:
- "/generate": "FastAPI endpoint"
- "ci_job": "GitHub Actions step"
attackers:
- external_user
- compromised_ci
threats:
- prompt_injection:
impact: high
likelihood: medium
- unsafe_execution:
impact: high
likelihood: high
将其导入安全待办事项列表,并把每一项都作为一个 ticket 处理。
简短的答案是:验证、清理和隔离。prompt injection 是指用户把恶意指令偷偷混入 prompt,让 LLM 将其理解为需要编写的代码。不安全执行则是指这些代码未经检查,就直接在你的服务器上运行。
绝对不要把用户的原始输入直接交给模型。应当移除可能被解释为指令的内容。对于大多数代码生成场景,一套简单的白名单机制就能发挥作用:
import re
SAFE_TOKENS = re.compile(r'^[\w\s\(\)\{\}\[\];,.\'\"-]+$')
def clean_prompt(user_prompt: str) -> str:
if not SAFE_TOKENS.match(user_prompt):
raise ValueError("Prompt contains unsafe characters")
# Remove common instruction keywords
for bad in ["run", "execute", "import os", "system", "subprocess"]:
user_prompt = user_prompt.replace(bad, "")
return user_prompt.strip()
将模型输出视为不可信内容。在编译或执行之前,先用静态分析工具检查它。对于 Python,bandit 或安装了安全插件的 flake8 可以发现许多危险模式。
bandit -r /tmp/snippets/generated.py
如果扫描器发现任何问题,就拒绝该代码片段并记录这起事件。
即使通过了静态检查,你仍然需要一道运行时屏障。对于 FastAPI 服务来说,Docker 是一种成本低廉且经过大量实战检验的沙箱方案。
FROM python:3.11-slim
RUN pip install fastapi uvicorn
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
entrypoint.sh 会在容器中启动代码片段,并执行以下操作:
#!/bin/sh
set -e
# Drop privileges
useradd -m sandbox
gosu sandbox python /tmp/snippets/generated.py
容器只能使用有限的 CPU 和内存,并且无法访问网络。如果代码片段试图打开 socket,就会立即失败。
一种常见的错误,是把 OpenAI 或 Claude API key 存在源代码或环境文件中,随后又把这些文件提交到了 Git。我曾经把一个包含有效 key 的代码仓库推送出去,结果眼看着一夜之间产生了 2,000 美元的用量。
在 FastAPI 中,可以像这样从 Vault 加载凭证:
import hvac
import os
def get_openai_key() -> str:
client = hvac.Client(url=os.getenv("VAULT_ADDR"))
client.token = os.getenv("VAULT_TOKEN")
secret = client.secrets.kv.v2.read_secret_version(path='ai/openai')
return secret['data']['data']['api_key']
这样一来,key 只会存在于内存中,永远不会写入磁盘。
前面已经介绍了 Docker 沙箱。下一层防护是自动化审查流水线:每一段生成的代码在进入生产环境之前,都必须经过这条流水线。你可以把它理解为针对 AI 输出设置的一道 CI 流程。
name: Review AI Generated Code
on:
workflow_dispatch:
inputs:
prompt:
description: 'User prompt'
required: true
jobs:
generate-and-review:
runs-on: ubuntu-latest
steps:
- name: Get API key from Vault
id: vault
uses: hashicorp/vault-action@v2
with:
url: ${{ secrets.VAULT_ADDR }}
token: ${{ secrets.VAULT_TOKEN }}
secrets: |
secret/data/ai/openai key | OPENAI_KEY
- name: Generate code
id: gen
run: |
python scripts/generate.py "${{ github.event.inputs.prompt }}" > generated.py
- name: Static analysis
run: |
pip install bandit
bandit -r generated.py || exit 1
- name: Run in sandbox
run: |
docker build -t snippet-sandbox .
docker run --rm -v $(pwd)/generated.py:/tmp/snippet.py snippet-sandbox
任何一个步骤失败,workflow 都会中止,同时你会收到一条 Slack 通知。整个闭环不到一分钟,对大多数 SaaS 产品而言,这样的延迟是可以接受的。
如果想深入了解,可以参阅我之前关于修复生产环境中 AI 生成代码质量问题的文章。
当你要求模型编写代码时,输出内容可能是各种公开代码片段的混合体,其中一些采用 GPL、MIT,甚至专有许可证。忽视这个问题,可能会让你陷入棘手的法律纠纷。
记录 prompt 和模型版本——建立可审计的追踪记录。
运行许可证检测器——scancode-toolkit 等工具可以扫描生成的文件,识别已知的许可证声明。
添加来源标头——在每个代码片段前添加注释,记录代码的生成来源。
# Generated by MyAI Platform v1.2.0
# Prompt: "Create a FastAPI endpoint that validates a JWT"
# Model: Claude-2.1
维护一份禁止使用的许可证清单——如果你的产品无法发布 GPL 代码,就拒绝任何包含 GPL 文本的代码片段。
如果需要进一步了解,可以阅读我的 AI 生成代码检测实用指南。
你不能依赖一次性的审查,因为威胁会不断演变。最可靠的方法,是把每一段生成的代码都视为能够产生遥测数据的一等资产。
使用 FastAPI middleware 可以轻松实现这一点:
from fastapi import FastAPI, Request
import hashlib, json, logging
app = FastAPI()
logger = logging.getLogger("ai_security")
@app.middleware("http")
async def log_requests(request: Request, call_next):
body = await request.body()
prompt = json.loads(body).get("prompt", "")
resp = await call_next(request)
log_entry = {
"user": request.headers.get("X-User-ID"),
"prompt_hash": hashlib.sha256(prompt.encode()).hexdigest(),
"status": resp.status_code,
}
logger.info(json.dumps(log_entry))
return resp
将这些日志推送至 SIEM,例如 Elastic 或 Splunk,并针对以下情况创建告警:
重复出现 prompt injection 失败——10 分钟内超过 5 次。
沙箱崩溃——任何非零退出状态。
凭证调用量激增——超过正常调用量的 2 倍。
告警触发后,自动隔离违规用户,并轮换受影响的 API key。自动化可以把平均响应时间从数小时缩短到几分钟。
需要执行特权系统调用,例如 os.system、subprocess.Popen。
在没有明确验证的情况下访问敏感数据存储。
代码来自一个无法审计其偏见或后门的模型。
遇到这些情况时,只应把模型输出当作灵感参考。关键路径应由你自己编写,或者在合并之前交给资深工程师进行人工审查。
我曾帮助多个团队搭建上面介绍的完整流水线,从 Vault 集成到 Docker 沙箱编排。如果你被某个具体故障卡住,或者只是希望有人帮忙做一次合理性检查,可以通过雇佣页面联系我。我会以交流讨论的方式沟通,专注于交付可用的解决方案,而不是向你推销服务。
prompt injection 是指用户在发送给 LLM 的文本中嵌入恶意指令,诱使模型生成危险代码或泄露秘密。清理 prompt 并限制允许使用的 token,可以降低这种风险。
不可以。模型过滤器很有用,但并非万无一失。应当把所有输出都视为不可信内容,并使用你自己的静态分析工具和沙箱进行检查。
对于大多数创业公司来说,每周轮换有些矫枉过正。只要怀疑发生了泄露,就应该轮换 key;对于 CI jobs,则应尽可能使用短期 token。
Docker 是最简单、可移植性最强的方式。其他选择还包括 Firecracker microVMs、gVisor,以及 pypy-sandbox 之类针对特定语言的沙箱。具体选择取决于你的延迟预算和合规要求。
梳理数据流——对每一个 prompt 和代码片段进行威胁建模。
清理并验证——将 prompt 和输出都视为不可信内容。
实施运行时沙箱——使用禁止联网且资源受限的 Docker containers。
保护凭证——将模型 keys 存储在 secret manager 中,绝不能写入源代码。
自动化审查——使用静态分析和 CI pipelines,在大多数问题发布之前将其发现。
追踪许可证——对每个生成的文件运行许可证检测器。
持续监控——记录 prompt、哈希值和沙箱运行结果,并配置告警。
明确拒绝条件——涉及特权操作或敏感数据的代码应当接受人工审查。
保护 AI 生成代码平台并不是执行一次检查清单就能完成的任务,而是一项需要长期坚持的工程纪律。落实上述步骤、快速迭代,并保持紧密的反馈闭环。你的用户、数据和内心的安宁都会因此受益。
如需采取进一步措施,可以考虑屏蔽此人和/或举报滥用行为。