---
title: "用 git filter-repo 从 Git 历史记录中删除密钥"
description: "误将密钥或密码提交到 Git？先撤销，再用 git-filter-repo 从 Git 历史记录中删除，清理 GitHub，并防止再次泄露。"
url: https://example-petstore.com/zh/guides/remove-secrets-from-git
language: zh-Hans
---

# 从 Git 历史记录中删除机密信息

密钥、令牌或密码被提交到了某个 commit 中。在新的 commit 中删除该文件并不能解决问题：旧的 commit 仍然包含它，存在于每个克隆和每个 fork 中。本指南介绍正确的顺序：先撤销，再重写历史记录，然后清理托管平台，并确保不再发生。

## 第一步：撤销机密信息

即使是在私有仓库中，也应将已提交的机密信息视为已泄露：克隆、fork、CI 日志和备份中可能已经存有副本，而自动扫描工具会在几分钟内发现公开仓库中的新 commit。

1. 在签发该密钥、令牌或密码的服务中将其撤销或轮换。
2. 将新的凭据保存在配置或密钥管理工具中，而不是代码中。
3. 检查该服务的日志，查找您无法识别的使用记录。

在常见服务提供商处撤销密钥的位置，请参阅[凭据泄露：现在该怎么办](https://example-petstore.com/zh/guides/leaked-credentials)。重写历史记录是在此之后进行的步骤，绝不能代替撤销。

## 是否重写历史记录

机密信息一旦撤销就不再具有访问权限，GitHub 也指出这可能已经足够。但在以下情况下，仍然值得重写：

- 机密信息无法撤销，或者文件中还包含其他敏感数据，例如个人数据或客户记录；
- 仓库是公开的，或者将要公开；
- 您希望扫描工具不再报告旧的机密信息。

重写会改变之后每个 commit 的 ID。协作者需要对自己的工作执行 rebase，未关闭的 pull request 可能会丢失评审意见，commit 签名也会被移除。请与所有相关人员约定时间，并先合并或关闭所有打开的 pull request。

## 找出所有出现位置

```
# 哪些 commit 添加或删除了该字符串？
git log --all --oneline -S 'the-secret-value'

# 它出现在哪些文件中？
git grep 'the-secret-value' $(git rev-list --all)

# 扫描整个历史记录中的已知机密信息格式
gitleaks git -v
```

记下机密信息出现过的每个文件路径，如果文件曾被移动或重命名，也要包括以前的名称。

## 使用 git-filter-repo 重写

`git-filter-repo` 是 GitHub 推荐的工具。请使用 2.47 或更高版本，该版本提供 `--sensitive-data-removal` 选项，并在全新的克隆中操作。

```
# 安装（或使用您的包管理器）
brew install git-filter-repo        # macOS
pip install git-filter-repo         # 任何装有 Python 的环境

git clone https://github.com/YOUR-ORG/YOUR-REPO
cd YOUR-REPO

# 方法 1：从全部历史记录中删除整个文件
git-filter-repo --sensitive-data-removal --invert-paths --path config/secrets.yml

# 方法 2：在所有出现的位置替换机密信息
git-filter-repo --sensitive-data-removal --replace-text ../replacements.txt
```

替换文件每行列出一个值。默认情况下，每个匹配项都会变成 `***REMOVED***`；使用 `==>` 可以自行指定替换内容，使用 `regex:` 则按模式匹配：

```
# ../replacements.txt（放在仓库之外）
sk_live_51Hx0000000000000000000000
AKIA0000000000000000==>AWS_ACCESS_KEY_ID_REMOVED
regex:password\s*=\s*"[^"]+"==>password = "REMOVED"
```

使用 `git log --all -S 'the-secret-value'` 检查结果：应当没有任何输出。然后覆盖远程仓库：

```
git push --force --mirror origin
```

如果分支保护会阻止强制推送，需要在此期间暂时关闭。推送之后，重写将无法撤消。

## 清理 GitHub

强制推送之后，旧的 commit 仍可通过 pull request、缓存视图和 fork 访问。

- **Pull request 和缓存视图：**联系 GitHub 支持，并提供受影响的 pull request 数量（`grep -c '^refs/pull/.*/head$' .git/filter-repo/changed-refs`）以及 git-filter-repo 输出的“First Changed Commit(s)”。只有在轮换凭据无法消除风险时，GitHub 才会提供帮助。
- **Fork：**fork 中的 commit 会保留在那里。请联系所有者删除或清理该 fork；GitHub 不会提供他们的联系方式。
- **同事的克隆：**每个人都必须将自己的分支 rebase 到新的历史记录上，而不是 merge。只要一次 merge，就会把旧的 commit 带回来。

在 GitLab、Bitbucket 或自托管服务器上，步骤大致相同；请查阅相应平台的文档，了解它如何删除旧对象和缓存视图。

## 替代方案：BFG

BFG Repo-Cleaner 是一款较早的、基于 Java 的工具，目前仍被广泛使用。它在镜像克隆上运行，并且默认不改动最新的 commit，因此请先通过一次普通 commit 从当前版本中删除机密信息。

```
git clone --mirror https://github.com/YOUR-ORG/YOUR-REPO.git
java -jar bfg.jar --replace-text replacements.txt YOUR-REPO.git
cd YOUR-REPO.git
git reflog expire --expire=now --all && git gc --prune=now --aggressive
git push
```

## 防止下一次泄露

- **推送保护：**在 GitHub 上，启用推送保护的机密扫描会在推送到达仓库之前，阻止包含已知机密信息格式的推送。
- **提交前检查：**将 gitleaks 或 git-secrets 作为 pre-commit 钩子运行，并在 CI 中再运行一次，以覆盖跳过钩子的人。
- **用配置，而不是代码：**从环境变量或密钥管理工具中读取机密信息，并在第一次 commit 之前将 `.env` 等文件添加到 `.gitignore`。
- **先看再提交：**逐个暂存文件并检查 `git diff --cached`，而不是使用 `git add .` 或 `git commit -a`。

这个密钥是否也发送到了 api.example-petstore.com 这样的占位地址？那么它也已到达一台不受您控制的服务器：[配置 API 客户端和 SDK](https://example-petstore.com/zh/guides/api-base-url)。

## 相关指南

### [凭据泄露：现在该怎么办](https://example-petstore.com/zh/guides/leaked-credentials)

密钥、令牌或密码被发送到了示例域名。请撤销并替换它们，本指南附有常见服务提供商撤销页面的直接链接。

### [配置 API 客户端和 SDK](https://example-petstore.com/zh/guides/api-base-url)

将基础 URL 移出代码，指向真实服务，并添加一项检查，防止示例地址进入生产环境。

## 来源

- [Removing sensitive data from a repository](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository) GitHub Docs
- [About push protection](https://docs.github.com/en/code-security/secret-scanning/introduction/about-push-protection) GitHub Docs
- [git-filter-repo](https://github.com/newren/git-filter-repo) GitHub
- [BFG Repo-Cleaner](https://rtyley.github.io/bfg-repo-cleaner/) Roberto Tyley
- [Gitleaks](https://github.com/gitleaks/gitleaks) GitHub
