Git 中的机密信息:为什么删除文件并不能解决问题

Git 中的机密信息:为什么删除文件并不能解决问题

一个关于配置文件的常规问题变成了当天最严重的发现

本周有人问了我一个无聊的问题:“那个生产环境配置文件在哪里?”二十分钟后,我看着一个位于 Git 仓库中的活动云访问密钥,团队里的每个人以及任何曾经克隆过该仓库的人都能读取它。

今天是 2026 年 9 月 29 日,这种情况之所以不断发生,是因为一个非常具有现实意义的原因。团队正在快速行动,将 AI 功能拼凑到现有产品上,而每一次新的集成都会带来一个新的密钥:一个模型 API 密钥,一个云服务帐户,一个存储凭据。这些密钥必须存放在某个地方,而阻力最小的路径就是应用程序的配置文件。如果该文件已经被 Git 追踪,那么在有人提交的那一刻,这个机密信息就被公开了。

这篇文章的重点不是“不要提交机密信息”。每个人都知道这一点。重点是当你发现一个机密信息时该怎么做,因为出于直觉的明显做法往往是错误的。

一切始于一个无聊的问题

我当时正在查看一个 .NET 应用程序。有问题的配置文件是 appsettings.Production.json,这是 .NET 应用程序保存其生产环境设置的标准位置。问题仅仅是哪个副本是权威的,因为该文件同时存在于三个不同的地方:

  • 在源代码树中,也就是在仓库中
  • 在运行中的容器内部,位于部署路径下
  • 在部署时被构建流水线重写,它会注入数据库连接字符串

第三个很重要。因为流水线覆盖了 部分 部署时的值,这很容易让人以为它会覆盖 全部 的值。并非如此。任何流水线没有注入的值都将完全按照提交时的状态被使用。

首先要检查的是文件是否被追踪

这是一个单行检查,值得对您不确定的任何配置文件运行一下:

git ls-files --error-unmatch path/to/appsettings.Production.json

如果该命令成功,则文件已被追踪,这意味着它存在于仓库及其历史记录中。在我的例子中,它成功了,并且 .gitignore 根本没有针对配置文件的任何规则。

里面大约有十几个机密信息:一个数据库连接字符串、几个密码、Web 推送通知密钥、一个 AI 模型 API 密钥和一个云访问密钥对。

为什么“我们只要把它删掉”行不通

这是让人惊讶的地方。从文件中删除机密信息并提交该更改,并不会删除该机密信息。

Git 存储的是历史记录,而不仅仅是当前状态。旧的提交依然存在。任何人都可以运行:

git log --oneline -- path/to/appsettings.Production.json
git show <old-commit>:path/to/appsettings.Production.json

并读取原始值。仓库的每一个克隆都携带着完整的历史记录。所以,去年克隆了该项目的开发人员,今天他们的笔记本电脑上仍然有这个机密信息,即使您“删除”了它。

您可以使用专门构建的工具重写历史记录,但这需要强制推送,它会破坏每一个现有的克隆,最关键的是,它对人们已经拥有的副本毫无作用。重写历史记录是清理,它不是补救。

真正能撤销暴露的唯一行动是更改机密信息本身。

弄清楚到底谁拥有它

在决定这有多紧急之前,诚实地清点一下持有者。在一个典型的小型团队中,这个名单比看起来要长:

  • 每一个有仓库访问权限的现任开发人员
  • 每一个曾经克隆过该仓库的前任开发人员或承包商,您已经无法控制他们的笔记本电脑了
  • 构建代理工作目录及其缓存
  • 服务器和虚拟机的备份及快照
  • 仓库的任何镜像

最后一组很容易被遗忘。备份被设计为具有持久性,这对于泄露的凭据来说恰恰是错误的属性。

查明该密钥实际上能做什么

严重性不在于泄漏的事实,而在于特权。一个存储桶的只读密钥与管理密钥是一个截然不同的问题。

这里的用户帐户被命名得好像它只是一个仅用于存储的帐户。但它不是。检查它的实际权限反映了一个不同的情况:

aws iam list-attached-user-policies --user-name app-s3
aws iam list-user-policies        --user-name app-s3
aws iam list-groups-for-user      --user-name app-s3

它拥有跨越所有存储桶的完全存储访问权限,属于一个被授予完全控制运行生产环境的计算服务权限的组,并具有以公司域发送电子邮件的权限。一个暗示范围狭窄的名字,背后却隐藏着广泛的权力。

然后,决定您的时间表的这个问题:

aws iam get-access-key-last-used --access-key-id AKIAEXAMPLEKEYID

它在当天早上被使用过。所以这个密钥并不是一个被遗忘的残留物。生产环境中有些东西依赖于它,这意味着立即删除它会导致服务中断。

轮换,而不仅仅是撤销

最后一个细节改变了计划。一个活跃的、正在使用的凭据需要轮换,而不是突然撤销。以下是在不破坏生产环境的情况下堵住漏洞的步骤序列。

步骤 1:确认密钥处于活动状态并找出其爆炸半径

运行上面的权限和最后使用检查。根据密钥能做什么来决定紧急程度,而不是根据它的名字。

步骤 2:在第一个密钥旁边创建第二个密钥

大多数云提供商允许每个用户拥有两个活动的访问密钥,正是为了您可以安全地进行轮换。

aws iam create-access-key --user-name app-s3

先不要碰旧密钥。

步骤 3:将新密钥放在非仓库的位置

把它移到您的流水线已经使用的任何机密信息存储中。大多数团队都已经有一个了,只是没有一致地使用它。在构建中引用它,以便在部署时注入该值,永远不要将其写入源代码。

步骤 4:部署并验证

重新部署,然后确认使用了该密钥的功能仍然有效。在我的例子中,这意味着检查出站电子邮件和文件存储。在撤销之前验证,绝不要在之后验证。

步骤 5:停用旧密钥

先停用而不是删除,因为如果您漏掉了一个消费者,停用是可以瞬间恢复的。

aws iam update-access-key --user-name app-s3 --access-key-id AKIAEXAMPLEKEYID --status Inactive

观察几天看看有没有错误,然后再删除它。

步骤 6:直到现在,才清理仓库

git rm --cached path/to/appsettings.Production.json
echo "appsettings.Production.json" >> .gitignore

故意将这一步放在最后。它能防止下一次泄漏。它不能修复这一次的泄漏。

步骤 7:顺便收紧权限

如果一个以存储命名的帐户拥有您的生产计算控制权,也要解决这个问题。轮换一个权限过大的密钥,只会给您一个权限过大的新密钥。

结论

当您在 Git 中发现一个机密信息时,直觉是删掉它让自己感觉好点。这种直觉会产生一个看起来很干净的仓库和一个没有改变的风险。历史是永久的,克隆无处不在,而备份就是为了永远保存东西而建立的。真正有帮助的唯一办法是替换凭据,并以一种在处理过程中不会让生产环境宕机的顺序进行。检查它是否活跃,衡量它能做什么,卷入一个新密钥,验证,然后关闭旧密钥。清理仓库是最后一步,而不是第一步。

优点

  • 检测检查是单个命令,适用于任何仓库
  • 轮换是永久性的修复,不同于文件删除或历史重写
  • 使用第二个密钥进行轮换意味着实时系统无需停机
  • 在轮换期间审计权限通常会暴露出权限过大的帐户
  • 将机密信息移入现有的机密信息存储通常不需要新工具
  • 在最终删除之前,整个过程是可逆的

缺点

  • 轮换需要找到密钥的每个消费者,这很少有文档记录
  • 您无法撤销最初的暴露,只能限制它的价值
  • 重写历史记录会破坏现有的克隆,对团队具有破坏性
  • 已经复制到笔记本电脑和备份中的机密信息超出了您的控制范围
  • 清理工作与功能开发争夺时间,很容易被推迟
  • 一些旧系统使得凭据轮换变得非常麻烦

注意事项

本文具有教育意义,描述的是一种通用方法,而不是针对您的环境的处方。这里的每一个命令、文件路径、帐户名和密钥标识符都是占位符,必须替换为您自己系统中的值。如果遗漏了消费者,轮换凭据可能会中断实时服务,因此首先要在非生产环境中验证每个步骤,在停用密钥之前确认有哪些东西依赖于它,并在依赖这里写的任何内容之前检查您的云提供商和工具的最新文档。

常见问题

  • 从 Git 仓库中删除一个机密信息能将其移除吗? — 不能。该值保留在提交历史记录和每个现有的克隆中,所以任何有该仓库的人仍然可以读取它。
  • 重写 Git 历史记录足以修复泄露的机密信息吗? — 不能。它清理了仓库,但对人们已经克隆的副本毫无作用,因此凭据仍然必须被轮换。
  • 我如何检查一个配置文件是否被 Git 追踪? — 运行 git ls-files --error-unmatch <path>。如果命令成功,则说明文件已被追踪,其内容存在于历史记录中。
  • 我应该立即删除泄露的密钥吗? — 只有在它未使用的情况下才删除。如果生产环境中有东西依赖它,先创建一个替换密钥,部署,验证,然后再停用旧的。
  • 我怎么知道泄露的云密钥是否仍在使用中? — 大多数提供商公开了访问密钥的最后使用时间戳,这可以告诉您是否还有消费者依赖它。
  • 那么应用程序的机密信息应该存放在哪里? — 存放在机密信息存储区或您的构建系统的凭据存储区中,在部署时注入,这样该值就不会出现在源代码控制中。
  • 为什么部署流水线没有保护机密信息? — 流水线通常只注入一些设置,例如数据库连接字符串,而让其他所有值完全保留提交时的样子。
  • 我如何阻止这种情况再次发生? — 将配置文件添加到 .gitignore,在您的仓库上启用自动机密扫描,并审查权限,以使泄漏密钥的价值尽可能小。

标签

#Security #DevOps #Git #SecretsManagement #CloudSecurity #IAM #KeyRotation #DevSecOps #ConfigManagement #InfrastructureAsCode

Free field guide

Incident Response: First Hour

A calm, evidence-preserving checklist for establishing control, bounding impact, communicating clearly, and containing an incident safely.