技术指南

API Key 与 .env 安全:从注入到撤销的可复查边界

从创建、注入、提交、AI 上下文到轮换撤销,建立可复查的 API Key 与 .env 安全边界。

API Key 安全不是把一个字符串藏进 `.env` 就结束,而是一条从创建、注入、读取、日志、提交到吊销的生命周期。先把密钥当作已经会离开当前终端的敏感凭据,再分别限制代码仓库、AI 上下文、远程主机和部署平台的可见范围;一旦公开,先让旧凭据失效,再处理留下的文件和历史。

先画出密钥经过的路径

阶段要回答的问题最小证据
创建谁创建、用于哪个项目和环境、能否限制额度/权限?凭据类型、项目、到期和负责人
注入谁把值放进进程,是否会出现在命令行、镜像层或前端包?环境变量/Secret 引用与进程边界
使用哪个后端请求需要它,日志和错误是否会回显?脱敏后的状态码、请求 ID 和日志字段
轮换旧值失效后怎样验证新值,失败如何回退?旧/新版本时间窗与验收结果
撤销删除后仍有哪里可以继续使用?仓库历史、CI、缓存、备份和部署清单

按环境和权限拆分

本地实验、共享开发机、预览、生产和 CI 不应共用一把万能 Key。优先使用项目级或环境级凭据、最小 scopes、调用额度和到期时间;没有这些能力时,也要用不同凭据缩小一次泄露的爆炸半径。个人 Key 不应作为团队部署凭据,前端代码更不能承载真正的私密凭据。

  • 记录凭据所属项目、用途、创建者和下一次轮换日期。
  • 能只读就不要授予写入,能限制单一服务就不要使用组织级 Token。
  • 预览环境使用单独值,避免测试请求消耗生产额度或读取生产数据。
  • 把恢复入口放进密码管理器或平台 Secret,而不是 issue、README 或 shell 历史。

本地项目只提交模板

`.gitignore` 只会阻止尚未被 Git 跟踪的文件进入后续提交;它不是保险箱,也不会从已有历史中删除 `.env`。仓库保留空值模板和变量说明,真实值只在本地进程或受控 Secret 注入。

按项目实际需要补充,不要用通配符把必须审阅的配置也隐藏
.env
.env.*
!.env.example
*.local
*.log
占位符不是可用凭据;服务端读取,浏览器端不要打包
# .env.example:只保留变量名和说明
OPENAI_API_KEY=
OPENAI_PROJECT_ID=

# 应用代码只读取运行时环境
const apiKey = process.env.OPENAI_API_KEY;
if (!apiKey) throw new Error("OPENAI_API_KEY is not configured");

提交前审阅暂存区

把命令输出留在本机;分享前脱敏路径、Cookie、Header 与响应
git status --short
git diff --cached --name-only
git diff --cached
git ls-files | grep -E '(^|/)\.env(\.|$)'
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!.git' \"OPENAI_API_KEY|GITHUB_TOKEN|DATABASE_URL|sk-\" .

发现 `.env` 已被跟踪时,先停止继续传播并轮换其中的值,再用 `git rm --cached` 修正索引。这个命令只改变当前索引,不会清除过去的提交;是否需要历史重写取决于仓库是否推送、谁持有克隆和平台的清理流程。

历史泄露先撤销再清理

  1. 在签发平台吊销或轮换旧 Key,并记录旧值不再被接受的时间。
  2. 确定泄露位置:公开仓库、私有仓库、fork、Actions 日志、构建产物、截图、聊天和备份。
  3. 通知拥有旧克隆或镜像的协作者;否则历史重写后仍可能再次推回旧对象。
  4. 按 GitHub 的敏感数据清理流程重写历史并强制更新受影响分支,再重新扫描。
  5. 替换所有部署、预览、VPS、Compose、systemd 和本地缓存中的旧值。

给 AI 工具最小上下文

Codex、Cursor 和其他 AI 工具可以读文件、运行命令和回显输出。让工具看 `.env.example`、脱敏日志和错误码,不要让它读取真实 `.env`、SSH 私钥、生产日志、完整 Header 或所有环境变量。命令请求也要避免 `printenv`、上传日志和把 Secret 拼进 URL。

  • 在仓库和工具的忽略/上下文排除规则中加入 `.env`、密钥目录、备份和日志。
  • 要求生成配置时使用变量名与占位符,并在提交前亲自审阅 diff。
  • 将 API 响应、错误堆栈和请求头中的 Token 按字段脱敏,而不是只删 `sk-` 前缀。
  • 把需要真实凭据的测试放在本地受控终端,不把值复制进对话、issue 或截图。

远程 VPS 按项目隔离

远程开发机增加了磁盘、备份、多人账号和 SSH 权限的风险。每个项目使用独立目录和环境文件,文件至少由服务用户可读;不要把所有凭据塞进共享的 `~/.bashrc`,也不要因为 `chmod 600` 就忽略主机更新、SSH 访问和备份暴露。

先替换用户和路径并检查现状;不要递归修改整个 home
install -d -m 0750 /etc/my-app
install -o app -g app -m 0600 /dev/null /etc/my-app/app.env
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519

systemd 的 `EnvironmentFile`、Compose 的 `env_file` 和平台 Secret 都只是注入方式,仍要核对服务用户、备份、容器日志和镜像层。不要把真实值写入 unit、Dockerfile、Compose 文件、构建参数或镜像。

CI/CD 只引用平台 Secret

GitHub Actions、Vercel、Cloudflare Pages 和 Netlify 等平台都有环境变量或 Secret 设置。把 Secret 名称放进 workflow,把值留在平台控制面;区分 development、preview 和 production,并确认失败日志、构建产物和部署预览不会回显值。

  • 为每个环境建立最小权限凭据和独立轮换窗口。
  • 禁止在 workflow YAML、Dockerfile、脚本参数和 URL 查询字符串中写真实值。
  • 验证构建后的浏览器资源和 source map 不包含服务器密钥。
  • 轮换后用一条不泄露值的健康请求验证新凭据,随后确认旧值返回预期拒绝。

用一次性仓库验证边界

本批在临时目录创建 Git 仓库和占位变量,不连接任何 API,不使用真实凭据。测试先确认 `.env` 被 ignore、`.env.example` 可提交、`git ls-files` 不包含 `.env`,再把一个明确标注为假的占位符提交到临时历史,证明 `git log -S` 仍能找到历史内容;随后删除目录,证明清理动作不依赖全局 Git 配置。

仅使用 placeholder-only;不要把真实值替换进演练
tmp=$(mktemp -d)
trap 'rm -r -- "$tmp"' EXIT
cd "$tmp"
git init -q
printf 'OPENAI_API_KEY=placeholder-only\n' > .env
printf '.env\n.env.*\n!.env.example\n' > .gitignore
printf 'OPENAI_API_KEY=\n' > .env.example
git add .gitignore .env.example
git check-ignore -v .env
test -z "$(git ls-files -- .env)"
git diff --cached --check

第二次检查用 `chmod 600` 收紧临时环境文件,并用 `rg` 扫描仓库中允许的变量名。演练只证明本机索引、权限和清理路径;它不能证明第三方平台已经撤销某个真实 Key,也不能替代平台 Secret Scanning、日志审计或轮换演练。

泄露后的验收记录

问题必须留下的答案
旧值何时失效?平台、UTC 时间、撤销/轮换操作和返回结果
哪里出现过?仓库提交/fork、日志、截图、CI、镜像、备份和主机
谁仍可能持有?协作者、部署账号、缓存、构建代理和旧克隆
怎样确认恢复?新值最小请求成功、旧值明确拒绝、日志不回显
怎样避免复发?忽略规则、扫描、权限、轮换日期和责任人

返回知识库