How to Fix a Leaked API Key: A Developer’s Guide to Git Security
TL;DR · AI 摘要
泄露的API密钥可通过撤销、清理Git历史、使用.env文件三步彻底消除,需立即执行且覆盖所有代码库。
核心要点
- 立即撤销泄露的API密钥并生成新密钥
- 使用.env文件存储敏感信息并加入.gitignore
- 强制清理Git历史中所有密钥记录
结构提纲
按章节快速跳转。
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- 处理泄露的API密钥
- 应急响应
- 撤销密钥
- 检测异常
- 代码清理
- 历史清理
- git filter-branch
- 强制推送
- 预防措施
- .env文件
- Git钩子
- 秘密扫描
金句 / Highlights
值得收藏与分享的关键句。
If an API key has been committed to Git, assume it has been copied and compromised
Use .env files for local development and add them to .gitignore
Git keeps previous versions of files in its history
如何修复泄露的API密钥:开发者的Git安全指南
2026年8月25日
/
#Git
Eva J Patel
想象一下:你加班到很晚,代码终于运行成功,准备推送到GitHub。
你执行:
git add .
git commit -m "Fix API integration"
git push几分钟后,你注意到一些异常。API使用量突然激增,可能出现了异常请求、新增的云资源,甚至账单金额远超预期。
然后你发现了问题:
const apiKey = "sk_live_123456789";你的API密钥正存储在Git仓库中。
这种情况令人焦虑,但可以解决。
最重要的原则是:
如果API密钥已被提交到Git,即使立即删除,也要假设它已被复制并泄露。
从文件最新版本中删除密钥并不能保证旧密钥的安全。Git会保留文件的历史版本,暴露的凭证可能被自动化扫描工具发现。
在本指南中,你将学习以下内容:
- 什么是API密钥?
- 紧急响应:首先应该做什么
- 步骤1:撤销或轮换泄露的密钥
- 步骤2:调查可疑活动
- 步骤3:从当前代码中移除密钥
- 步骤4:使用.env文件进行本地开发
- 步骤5:创建安全的.env.example文件
- 步骤6:确定密钥是否仍存在于Git历史中
- 何时需要重写Git历史?
- 步骤7:从Git历史中移除密钥
- 步骤8:验证密钥是否已彻底清除
- 步骤9:谨慎推送清理后的历史记录
- 步骤10:在所有位置替换凭证
- 步骤11:限制新密钥的权限
- 前端应用如何处理?
- 环境变量与密钥管理器的对比
- 在工作流程中添加密钥扫描
- 使用Git钩子作为额外的安全网
- 提交前检查已暂存的差异
- 开发者常见错误
- 完整的API密钥事件检查清单
- 安全的项目结构
我们将贯穿全文使用这个基本流程:
使无效 → 调查 → 移除 → 替换 → 预防在进入最重要的部分——密钥泄露后应该立即采取的行动之前,让我们先了解API密钥到底是什么。
什么是API密钥?
API密钥是允许应用程序与另一个服务通信的凭证。
例如,一个应用可能使用API密钥来访问:
- 天气服务
- 支付提供商
- 地图服务
- 人工智能API
- 云平台
- 数据库
- 邮件服务提供商
- 私有公司API
密钥可能看起来像这样:
const apiKey = "your-real-api-key";或者出现在配置文件中:
{
"apiKey": "your-real-api-key",
"databasePassword": "your-real-password"
}API密钥通常被称为"秘密",因为拥有它可能允许他人发起请求、访问数据、创建资源或在你的账户上生成费用。
并非所有API密钥的敏感性相同。某些服务会提供专门给浏览器使用的可见密钥,这些密钥仍应设置适当的限制、配额和权限。
总体原则是:
如果凭证可以访问私有数据、创建资源、修改记录或生成费用,就不应该直接存储在源代码中。
不要从这里开始。
你的首要任务是让泄露的凭证失效。
请按照以下顺序操作:
1. 使泄露的凭证失效
2. 调查可疑活动
3. 从代码中移除密钥
4. 用新凭证替换
5. 如有必要清理Git历史记录
6. 验证清理结果
7. 增加防止未来泄露的防护措施把API密钥想象成掉落在繁忙街道上的房屋钥匙。
如果有人已经捡起了实体钥匙,删除钥匙的照片是没有意义的。
应该先更换锁具。
第一步:撤销或轮换泄露的密钥
前往签发该凭证的服务提供商的仪表板。
根据提供商的不同,你可能会看到以下选项:
- 撤销
- 删除
- 禁用
- 轮换
- 重新生成
- 创建新密钥
如果提供商支持密钥轮换,建议在禁用旧密钥前先创建替换凭证(如果可能)。这可以在更新配置时减少应用停机时间。
关键的是原始凭证必须完全失效。
不要重复使用泄露的密钥。不要重命名它。不要对它进行编码。不要把它移动到其他文件并认为这样就安全了。不要假设没人看到它。
应将其视为已被泄露。
第二步:调查可疑活动
在禁用凭证后,检查提供商的使用仪表板和日志。
留意以下异常情况:
- 请求量突然激增
- 来自陌生地区的请求
- 非预期的数据库查询
- 新创建的云资源
- 权限变更
- 非法下载
- 异常支付活动
- 新部署
- 应用处于非活跃时段的请求
如果凭证具有广泛权限,需假设其权限范围内的任何内容都可能已被访问或修改。
例如,如果云凭证可以创建虚拟机,检查是否有异常虚拟机被创建。
如果凭证可以访问数据库,需审查:
- 认证日志
- 读取操作
- 写入操作
- 被删除的记录
- 导出的数据
- 新创建的账户
- 权限变更
如果凭证可能产生基于使用量的费用,还需检查账单信息。
将你的发现记录下来。简单的事件时间线可以帮助:
10:15 - 提交API密钥
10:23 - 将仓库公开推送
10:41 - 检测到异常使用
10:45 - 密钥被撤销
11:00 - 审查日志
11:30 - 部署新密钥
12:00 - 清理Git历史记录如果需要向团队或服务提供商报告事件时,这将特别有用。
第三步:从当前代码中移除密钥
在原始凭证被禁用后,从工作文件中移除它。
以下方式不安全:
相反,应从环境变量中加载凭证:
const apiKey = process.env.API_KEY;
if (!apiKey) {
throw new Error("API_KEY is not configured");
}在Python中:
import os
api_key = os.environ.get("API_KEY")
if not api_key:
raise RuntimeError("API_KEY is not configured")核心思想很简单:
源代码 → 环境变量 → 密钥值而不是:
源代码 → 硬编码的密钥环境变量不是管理密钥的唯一方式,但它们是本地开发和许多部署环境中的常见实用方案。
API_KEY=your-local-development-key
DATABASE_URL=your-local-database-urlNode.js 项目可以通过 dotenv 等包加载这些值。
安装方法:
npm install dotenv然后:
import "dotenv/config";
const apiKey = process.env.API_KEY;关键点在于 .env 文件通常不应提交到 Git 仓库中。
将其添加到 .gitignore:
# 环境文件
.env
.env.*
!.env.example
# 凭据文件
*.pem
*.key
credentials.json
service-account.json
# 本地开发文件
.DS_Store但需要注意一个重要细节:.gitignore 不会删除 Git 已经追踪的文件。
如果 .env 已经被提交过,将其添加到 .gitignore 不会从 Git 中删除它。
你可以停止追踪该文件但保留本地副本:
git rm --cached .env然后提交 .gitignore 的更改:
git add .gitignore
git commit -m "Ignore local environment files"但请记住:这只会从未来的提交中移除文件,不会从之前的提交中删除敏感信息。
这就是 Git 历史记录的作用。
第5步:创建安全的 .env.example
其他开发者仍然需要知道应用程序需要哪些环境变量。
不要提交 .env 文件,而是创建 .env.example:
API_KEY=
DATABASE_URL=
PORT=3000
LOG_LEVEL=info该文件包含变量名而非真实凭据,因此可以提交到仓库。
你也可以添加注释:
# 必需的 API 凭据
API_KEY=
# PostgreSQL 连接字符串
DATABASE_URL=
# 可选的应用端口
PORT=3000新开发者可以复制该文件:
cp .env.example .env并填写自己的值。
示例中应使用明显虚假的占位符:
API_KEY=replace-me-with-your-own-key避免在 .env.example 中使用看起来像真实生产凭据的内容。
第6步:判断敏感信息是否仍存在于 Git 历史记录中
这是修复泄露凭据过程中最重要的部分之一。
假设你的 Git 历史记录如下:
提交A:将 API 密钥添加到 config.js
提交B:更新 API 集成
提交C:删除 API 密钥即使提交C不再包含密钥,提交A仍然包含。
Git 会记住文件的早期版本。
你可以通过以下方式检查文件历史:
git log --all -- config.js要查看旧提交中的文件内容:
git show COMMIT_ID:config.js你也可以搜索 Git 历史记录中已知的泄露值:
git log --all -S"your-leaked-key" --oneline如果你知道该密钥已被提交,应假设它可能存在于仓库历史记录中,直到你确认不存在为止。
何时需要重写 Git 历史记录?
并非所有意外泄露的密钥都需要重写历史记录。考虑以下情况:
密钥从未被提交过
如果密钥仅存在于工作目录且从未被提交,通常不需要重写历史记录。
删除它,将适当文件添加到 .gitignore 中,然后继续工作。
密钥仅在本地提交过但未推送到远程仓库
如果密钥存在于本地提交但未与远程仓库共享,你可能在推送前清理这些提交。
密钥已推送到远程仓库
将凭据视为已被泄露,立即撤销或轮换它。
然后判断是否需要从仓库历史记录中删除该密钥。
仓库曾为公开仓库
假设有人或某物已经复制了该密钥。
这就是为什么撤销操作应在 Git 清理之前进行。
### 密钥存储在私有仓库中
私有仓库比公开仓库更安全,但它并不是一个保密保险库。
凭证仍可能通过以下方式泄露:
- 被入侵的账户
- 合作伙伴
- 集成系统
- CI 日志
- 分支仓库
- 备份文件
- 截图
- 复制的代码
- Pull Request
因此,最安全的规则仍然是:
> 即使在私有仓库中,也绝不应故意将凭证提交到 Git。
## 步骤 7:从 Git 历史中移除密钥
如果凭证已被提交,可能需要从仓库历史中将其移除。
在重写历史之前,请先创建备份:
git clone --mirror https://github.com/your-username/your-repository.git repository-backup.git
镜像克隆包含分支和标签,这在出现问题时可用于恢复。
### 选项 1:移除整个文件
如果密钥存储在如 `.env` 这样的文件中,可以从整个历史中移除该文件:
git filter-repo --path .env --invert-paths
如果是目录中的文件:
git filter-repo --path config/production.json --invert-paths
这会从重写后的仓库历史中移除该文件。
### 选项 2:替换文件中的密钥
有时需要保留文件但移除历史版本中的密钥。
创建临时替换文件:
然后运行:
git filter-repo --replace-text replacements.txt
可以将值替换为占位符:
your-leaked-key==>YOUR_API_KEY_HERE
请特别小心处理 `replacements.txt`。该文件包含原始密钥,因此不要提交它。
清理完成后删除它:
rm replacements.txt
在 Windows PowerShell 中:
Remove-Item replacements.txt
对于多个密钥:
old-api-key==>REMOVED_API_KEY old-database-password==>REMOVED_DATABASE_PASSWORD old-token==>REMOVED_TOKEN
请先在备份克隆上测试清理效果。
## 步骤 8:验证密钥是否已移除
不要假设清理操作已成功,仅仅因为命令执行完成。
再次搜索已知泄露的值:
你也可以检查相关文件和提交记录:
以及:
如果仓库使用分支和标签,请确保你没有只检查当前签出的分支。
你还应检查密钥可能出现在的其他位置,包括 Pull Request、CI/CD 日志、构建产物、发布文件、Docker 镜像、包发布、文档、问题评论和截图。
请记住:
> 重写仓库历史不会删除其他地方已经存在的副本。
这也是为什么原始凭证必须被撤销的另一个原因。
## 步骤 9:谨慎推送清理后的历史
在验证清理操作后,可能需要推送重写后的历史:
git push --force --all origin git push --force --tags origin
### 重要警告
强制推送重写后的历史具有破坏性。它会改变提交哈希,可能影响已有仓库克隆的协作者。
在共享项目上执行此操作前:
- 通知协作者
- 确保所有人理解历史正在被重写
- 协调清理工作
- 如果有组织的事件响应流程,请遵循该流程
重写后,协作者可能需要重新克隆仓库:
git clone https://github.com/your-username/your-repository.git
他们不应该盲目地将旧仓库的历史记录合并回清理后的仓库。
## 第 10 步:在所有位置替换凭证
现在创建或使用替代凭证。更新应用程序运行的每个环境。常见位置包括:
- 本地开发
- 测试
- 预发布环境
- 生产环境
- Docker 容器
- Kubernetes 密钥
- CI/CD 系统
- 托管平台
- 计划任务
- 无服务器函数
常见的错误是只更新了生产环境却忘记了部署管道。
例如,你的本地应用可能因为 `.env` 包含新密钥而正常工作,但 CI/CD 系统可能仍然使用旧密钥。
制定检查清单:
- 本地开发
- 自动化测试
- 预发布环境
- 生产环境
- CI/CD 变量
- Docker 配置
- 云部署设置
- 计划脚本
- 无服务器函数
更新凭证后,要在每个重要环境中测试应用程序。
## 第 11 步:限制替代密钥的权限
替换泄露的凭证只是解决方案的一部分。
新凭证应仅具有其实际需要的权限。
有用的限制包括:
- 只读权限
- 特定 API 范围
- 允许的 IP 地址
- 允许的域名
- 环境特定访问
- 请求配额
- 速率限制
- 过期日期
例如,天气应用可能只需要读取天气数据的权限。
它不应该具有管理用户、修改账单或删除无关资源的权限。
这就是最小权限原则:
> 为每个凭证提供完成其工作所需的最小访问权限。
为不同环境使用不同凭证也是一个好主意:
local-development-key testing-key staging-key production-key
这样,开发凭证泄露不会自动暴露生产资源。
## 前端应用怎么办?
这正是 API 密钥安全变得令人困惑的地方。
前端代码在用户的设备上运行。
这意味着用户可以检查代码。
const apiKey = "browser-key";
用户可以检查 JavaScript 包、浏览器开发者工具或网络请求,可能看到密钥值。
一些服务有意提供专为公开可见设计的浏览器 API 密钥。
这些密钥仍应通过以下方式限制:
- 网站来源
- API 操作
- 使用配额
- Referer 限制
- 时间限制
但真正的私有凭证绝不能出现在浏览器代码中。
fetch("https://private-api.example.com/data", { headers: { Authorization: "Bearer private-secret-token" } });
应让浏览器调用你自己的后端:
fetch("/api/data");
然后后端与私有服务通信:
const response = await fetch( "https://private-api.example.com/data", { headers: { Authorization: Bearer ${process.env.PRIVATE_API_TOKEN} } } );
后端可以只返回浏览器被允许接收的信息。
关键区别在于:
公共/浏览器凭证 ↓ 可以可见,但应受限制
私有凭证 ↓ 必须保留在可信后端或密钥管理系统中
## 环境变量与密钥管理系统的区别
环境变量很有用,但它们不是万能的密钥管理解决方案。
对于小型应用或本地开发环境,可以使用类似以下内容:
API_KEY=your-secret
可能完全合理。
对于更大的生产系统,你可能需要一个专用的密钥管理器。
密钥管理系统可以提供以下功能:
- 集中式凭证存储
- 访问控制
- 审计
- 凭证轮换
- 版本控制
- 环境隔离
- 与部署系统的集成
关键理念是源代码不应负责存储生产环境的密钥。
正确的流程应为:
应用 ↓ 密钥管理系统 ↓ 凭证
而非:
应用 ↓ 硬编码的生产凭证
选择哪种方案取决于项目规模和需求。
## 在工作流中添加密钥扫描
人类是出色的程序员,但偶尔也是糟糕的搜索引擎。
自动化密钥扫描可以在凭证进入仓库前就捕获它们。
流行工具包括:
- Gitleaks
- TruffleHog
- detect-secrets
- pre-commit钩子
- Git托管平台密钥扫描
- CI安全扫描器
例如,你可以在本地运行Gitleaks:
gitleaks detect --source . --verbose
你也可以将密钥扫描集成到CI中。
一个基本的GitHub Actions工作流可能如下:
name: Secret Scan
on: push: pull_request:
jobs: scan: runs-on: ubuntu-latest
steps:
- name: 检出仓库
uses: actions/checkout@v4 with: fetch-depth: 0
- name: 扫描密钥
uses: gitleaks/gitleaks-action@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
查阅所选工具的文档,并根据项目安全规范固定版本。
密钥扫描器可能会产生误报,因此可能需要为安全的测试值配置例外规则。
但要注意使用白名单。过于宽松的例外规则可能会隐藏真实的凭证。
## 使用Git钩子作为额外的安全网
你也可以在文件提交前进行扫描。
例如,一个简单的pre-commit脚本可以搜索可疑词汇:
#!/usr/bin/env bash
if grep -RniE "api[_-]?key|password|secret|token|private[_-]?key" . \ --exclude-dir=.git \ --exclude=".env.example"; then
echo "检测到可能的密钥。提交已取消。" exit 1 fi
这不是完整的安全扫描器,但可以捕获明显错误。
为了更强的保护,可以通过pre-commit框架使用专用的密钥扫描工具。
目标不是让提交变得痛苦,而是让意外发布凭证变得更困难。
## 提交前检查已暂存的差异
你可以培养的一个最简单的安全习惯是检查即将提交的内容。
首先:
git status
然后只暂存你打算提交的文件:
git add src/api.js README.md
现在检查已暂存的更改:
git diff --cached
注意查找:
- API密钥
- 密码
- 令牌
- 私有URL
- 内部主机名
- 客户数据
- 调试输出
- 个人信息
- 私有证书
在确认已暂存的差异无误后提交:
git commit -m "从环境加载API密钥"
要特别小心使用:
git add .
它可能会暂存你从未打算发布的文件,包括.env文件、数据库导出文件、生成文件或本地配置文件。
## 开发者常见的错误
### 错误1:"我已经删除了,所以没问题"
从当前文件版本中删除一个密钥,并不会从Git历史记录中删除它。
正确做法:在适当的时候撤销凭证并清理仓库历史记录。
### 错误 2:「仓库是私有的」
私有仓库并不是保险箱。
凭证仍可能通过被入侵的账户、集成、CI 日志、分支、备份或复制的代码泄露。
正确做法:即使是对私有仓库,也不要提交任何凭证。
### 错误 3:「我只需要对它进行编码」
以下方式无法保护凭证的秘密性:
const key = atob("c29tZS1rZXk=");
或:
const key = "some-" + "secret-" + "value";
对凭证进行编码、拆分、重命名或隐藏并不能真正保护它。
如果应用程序能够重新构造凭证,分析该应用程序的人也可能做到同样的事情。
### 错误 4:日志中记录了凭证
不要这样做:
console.log(process.env.API_KEY);
日志可能被终端、CI 系统、托管服务提供商、监控平台或云服务存储。
console.log( "API key configured:", Boolean(process.env.API_KEY) );
如果在调试过程中必须检查某个值,请避免打印完整的凭证。
function maskSecret(value) { if (!value) return "not configured"; if (value.length <= 8) return "****";
return ${value.slice(0, 4)}...${value.slice(-4)}; }
console.log(maskSecret(process.env.API_KEY));
即使是对已掩码的凭证,也应谨慎处理。
### 错误 5:在所有地方使用相同的凭证
如果本地开发、测试、预发布和生产环境都使用相同的凭证,一次泄露可能影响所有环境。
正确做法:使用具有独立权限的独立凭证。
### 错误 6:仅清理当前分支
泄露的凭证可能仍然存在于:
- 旧分支
- 标签
- 其他引用
正确做法:在调查和清理泄露的凭证时,应考虑整个仓库。
### 错误 7:忘记构建产物
凭证还可能出现在:
- 编译后的 JavaScript 包
- Docker 镜像
- 可下载的发布版本
- 已发布的包
- 生成的文档
正确做法:撤销凭证并识别可能需要删除或替换的受影响产物。
## 完整的 API 密钥事件检查清单
如果你发现暴露了 API 密钥,请使用以下检查清单:
- 撤销或轮换泄露的密钥
- 创建替代凭证
- 限制替代凭证的权限
- 查看提供商日志
- 查看账单和使用情况
- 检查未授权资源
- 从当前文件中删除密钥
- 将密钥文件添加到 .gitignore
- 创建或更新 .env.example
- 搜索 Git 历史记录
- 检查分支和标签
- 如果需要,从 Git 历史记录中删除密钥
- 验证旧密钥已被清除
- 如果适当,强制推送清理后的历史记录
- 检查拉取请求和分支
- 检查 CI 和部署日志
- 更新本地配置
- 更新预发布环境配置
- 更新生产环境配置
- 更新 CI/CD 凭证
- 运行密钥扫描器
- 记录事件
- 添加预防性安全检查
具体步骤取决于你的提供商和项目,但顺序很重要:先使凭证失效,再进行清理。
## 安全的项目结构
一个简单的 Node.js 项目可能如下所示:
my-project/ ├── src/ │ └── api.js ├── .env ├── .env.example ├── .gitignore ├── package.json └── README.md
本地的 `.env` 文件包含实际的开发值:
API_KEY=your-local-key
`.env.example` 文件不包含任何真实凭证:
import "dotenv/config";
const apiKey = process.env.API_KEY;
if (!apiKey) { throw new Error("Missing API_KEY environment variable"); }
export async function getData() { const response = await fetch( "https://api.example.com/data", { headers: { Authorization: Bearer ${apiKey} } } );
if (!response.ok) { throw new Error( API request failed: ${response.status} ); }
return response.json(); }
`.gitignore` 会将本地环境文件排除在后续提交之外:
.env .env.* !.env.example
node_modules/
最后,你的 README 可以解释设置步骤而不会暴露凭证:
步骤 1:在 bash 中复制示例环境文件 `cp .env.example .env`
步骤 2:将你的 API 密钥添加到 `.env` 文件中。
最后但同样重要的是,启动你的应用程序!
npm start
## 最后思考
泄露 API 密钥并不意味着你是个糟糕的开发者,这仅仅说明你的开发流程需要更好的防护措施。
重要的是要知道如何快速应对,以及如何防止再次发生同样的错误。
记住应急处理公式:
- 使泄露的凭证失效,确保其无法再次使用。
- 检查日志、使用情况和账单记录,确认是否被滥用。
- 从当前代码中移除密钥,必要时从 Git 历史记录中删除。
- 用仅具有所需权限的新凭证替换它。
- 通过环境变量、密钥管理器、密钥扫描和谨慎的 Git 操作防止未来泄露。
Git 非常擅长记住项目的历史记录,这在你意外删除重要功能时很有用。但如果历史记录中包含密码,这种特性就变得非常危险。
因此,在适当的时候保持代码公开,而将敏感信息存储在其他安全的位置。
编码愉快!
我是一名自学成才的开发者,热衷于 Python、人工智能/机器学习,以及构建解决现实问题的项目。我喜欢通过动手项目学习,探索新技术,并分享我的学习成果。目前正在探索人工智能工程、全栈开发和开源项目。
如果这篇文章对你有帮助,请分享它。
免费学习编程。freeCodeCamp 的开源课程已帮助超过 40,000 人成为开发者。立即开始
ADVERTISEMENT