• 为什么 GitHub 提交没你想得那么私密

为什么 GitHub 提交没你想得那么私密

2025-08-16 12:32:35 栏目:宝塔面板 0 阅读

译者 | 涂承烨

审校 | 重楼

开发者中普遍存在一种误解,认为一旦删除了提交(commit),它就永远消失了。你可以强制推送(force-push)来重写历史,或者删除包含敏感信息的分支(branch),并以为它已被安全擦除。但 GitHub Git 本身并不样工作。

事实上,GitHub 能够以不显而易见的方式保留并暴露“已删除”的提交。在某些条件下,你认为已被移除的提交仍然可以被公开访问。这造成了一种隐私假象—开发者感觉安全了,但实际上,敏感信息的痕迹可能仍然可以访问。

在本文中,我将逐步阐述这种情况是如何发生的,演示已删除或私有的提交在哪些情况下仍然可以被访问,并解释这对注重安全的团队、开源维护者以及错误应用 GitHub hygiene的开发者意味着什么。

Git 如何处理“已删除”的提交

Git 是一个分布式版本控制系统,用于跟踪文件的版本。这意味着开发者可以独立拥有自己的分支版本。

其核心在于,Git 是一个内容可寻址的数据库。提交(commits)、树(trees)和 blob 对象(blobs)根据它们的 SHA-1 SHA-256 哈希值存储。Git 并非真正“删除”内容—它只是取消对它们的引用。

让我们看看这在实践中是什么样子:

首先,初始化一个新的 Git 仓库并添加一个 README.md 文件。

README.md 添加一些更改并再次提交。

此时,我们有两个提交。你可以用 git log 查看它们:

HEAD 是一个位于仓库内 .git/HEAD 文件中的指针。这个文件通常包含对当前分支的引用(例如,ref: refs/heads/main),或者如果你处于分离的 HEADdetached HEAD)状态,则包含一个特定的提交哈希值。

让我们使用 git reset HEAD^ --hard 重置(reset)到第一个提交。

我们可以看到 HEAD 已切换到第一个提交:

第二个提交消失了。这类似于你进行强制推送(git push -f)时发生的情况。它可能看起来代码已被擦除—但实际上,你仍然可以使用 git reflog 恢复它。

在这里,我们可以看到第二个提交的 SHA-1 哈希值是 2b9714e

让我们通过重置HEAD将它恢复。

让我们看看提交哈希值。Git 同时支持哈希值的完整和缩短版本:

完整哈希值 (Full hash)

缩短版本 (Short version)

fe8b8e6d36d640a29dc893ecc81bc1a2eeead1ed

f38b8e6

2b9714ec5b229700eed2ce2dc673b8d8b52a1f

2b9714e

Git 中的每个提交都有一个唯一的哈希值作为其“ID”。该哈希值是根据整个提交内容计算出来的,包括:

  • 文件和目录结构(“树”对象)
  • 提交信息
  • 元数据,如作者、提交者和时间戳
  • 父提交的哈希值

Git 默认使用 SHA-1(或可选地使用实验性功能 SHA-256)。你通常不需要完整的哈希值—Git 允许你使用缩短版本(通常 4-7 个字符就足够了)。在我的例子中,是 f38b 2b97

这就是 Git 本地工作的方式。

但是 GitHub 呢?这就是事情变得有趣的地方。

GitHub 呢?

GitHub 作为一个构建在 Git 之上的分布式平台,不仅继承了 Git 的去中心化机制,还引入了其自身的复杂性和风险层。

让我们重新审视之前的实验。

我创建了一个公共仓库并添加了两个提交:

然后我运行了:

我们在 GitHub 上看到了什么?

第二个提交消失了—从历史记录中擦除了。

当然,我可以使用本地 Git 工具恢复它(如前所示),但我们还能在 GitHub 本身上访问它吗?

是的!

你可以直接在浏览器中使用以下方式访问该提交:

对于我的例子:

https://github.com/C4tWithShell/demo/commit/cbc61bd83a87561c101a325b03ec9873a7c0cc62

GitHub 警告:

“此提交不属于此仓库的任何分支,可能属于仓库外部的某个分支(fork)。”

但整个提交内容仍然可用。

公共仓库(Public repositories

由于 GitHub 是一个分布式平台,我们可以将这个想法扩展到连接的仓库—上游(upstreams)仓库及其分支(forks)。

我能访问已删除分支(fork)中的提交吗?

我分叉(fork)了我的演示仓库,在上面工作,并错误地添加了一个新的 .md 文件。

然后我意识到了错误,并通过 git push -f 删除了它。

我不再看到那个提交了,但它真的消失了吗?

多亏了 SHA-1 哈希值,我们可以仅用 4-7 个十六进制字符来定位提交。只有 65,536 (16) 种可能的组合,对于现代机器来说,暴力破解简短的 SHA 前缀是微不足道的,并且完全可以自动化。

我仍然可以在我的分支(fork)中找到那个提交。即使该分支后来被删除,我也可以从原始仓库访问它。

如果上游仓库被删除了呢?

好的,让我们反过来想。

假设我向原始仓库提交了一个秘密(secret),然后在任何人分叉(fork)它之前立即删除了它。我安全了吗?

这次,为了确保,我们甚至删除我的上游仓库。

删除我的演示仓库后,我看到不再有“forked”的链接,并且我看不到 SECRET.md 文件了。

这次,我创建了那个分支的一个分支(fork),并使用简短的 SHA 挖掘提交历史。这意味着我仍然可以恢复 SECRET.md 提交!

因为只要还有一个分支(fork)存在,该提交就存在。

这怎么可能?

这是可能的,原因在于 GitHub 中的仓库网络—仓库与其分支(forks)之间的关系网。你可以在以下位置探索它:

它显示:

  • fork了该仓库;
  • 提交如何在不同分支(forks)间产生分歧;
  • 存在于分支(forks)中但不存在于主仓库中的提交。

因此,当有人分叉(fork)一个仓库时,GitHub 会跟踪父子关系。即使分支(forks)被删除或设为私有,只要:

  • 该分支曾经是公开的;
  • 在分支被删除/设为私有之前提交已被推送。

那么,它们可能仍然在网络图(network graph)中被跟踪。GitHub 存储的提交哈希值,如果你知道 SHA,仍然可以访问,这是一个已知的元数据泄露途径。

它会影响私有仓库吗?

让我们用一个私有仓库试试。

我创建了一个私有仓库并fork了它。该分支保持私有状态,无法将其设为公开。我在分支中添加了一个额外的文件。

即便如此,我仍然可以从原始仓库使用其简短的 SHA 访问这个提交。

至少在这种情况下,可见性是受控的——分支无法公开,访问仅限于协作者(collaborators)。

但真正的问题在这里...

我见过一些公司开源其内部仓库。在这样做之前,他们通常会彻底清理主仓库。

但是分支(forks)呢?

当一个私有仓库变为公开时:

  • 原始仓库的分支网络在发布的那一刻被冻结。
  • 在私有分支(forks)中发布之前所做的所有提交都可能变得公开可见。
  • GitHub 不会警告你这一点。

因此,即使你的主仓库是干净的,你也可能正在暴露先前私有分支中的秘密。

让我们测试一下。我更改了我的原始仓库的可见性。它只有一个干净的提交和一个文件。

我能访问我私有分支(fork)中的那个提交吗?

成功!我们可以看到在原始仓库发布之前在私有分支(fork)中做出的所有提交。为了验证,让我们向我们的私有分支添加一个新的提交:

现在让我们尝试从公共仓库访问它:

为什么会这样?因为一旦仓库变为公开,GitHub 就会分离仓库网络。之后添加到私有分支(fork)的提交就变得隔离了。

这种行为是双向的:

  • 发布后私有分支(fork)中的提交从公共仓库是不可见的。
  • 该时间点之后公共仓库中的提交从私有分支(fork)是不可见的。

这是一个 Bug 吗?

不,这是 GitHub 的一种设计行为,他们甚至在文档中提到过。不幸的是,没有多少人深入研究它。

阅读下面—GitHub 关于分支(fork)的可见性说明:

当你将仓库的可见性从私有更改为公共时,其每个现有的私有分支(fork)都将变为私有仓库,并失去对上游网络的访问权限。公共仓库的分支始终是公共的。

可以采取什么措施来解决它?

  • 始终将 GitHub 视为公开的(Treat GitHub as Public-Always假设你推送的任何内容最终都可能被暴露。密钥(Secrets)不属于 Git。使用秘密管理器—VaultAWS Secrets ManagerDopplerGitHub Secrets 等。
  • 在发布前妥善清理(Clean properly before publishing使用如下工具:

a.TruffleHog

b.deepsecret

c.semgrep Secrets

d.BFG Repo-Cleaner

e.git filter-repo

我推荐:

  1. 结合使用 deepsecrets 或 semgrep-secrets 与 truffleHog 或 gitleaks。它们基于上下文检测秘密,而不仅仅是熵(entropy)或正则表达式。例如,passwd: hello 可能会被标准工具遗漏,但不会被 deepsecrets 遗漏。但你也应该预料到会有误报,因为它可能被作为示例提及。
  2. 在 CI 流水线中自动化扫描。
  • 联系 GitHub 支持进行移除(Contact GitHub Support for Removals如果你不小心推送了敏感数据,联系支持部门,GitHub 可以从其后端清除对象,但这并非即时生效或有保证的。
  • 如果秘密暴露了,就轮换它们(Rotate secrets if they get exposed

作为最重要的一步,轮换秘密而不是试图删除它们。一旦秘密暴露,就假设它已泄露并进行更改。不要心存侥幸!

译者介绍

涂承烨,51CTO社区编辑,具有15年以上的开发、项目管理、咨询设计等经验,获得系统架构设计师、信息系统项目管理师、信息系统监理师、PMPCSPM-2等认证。

原文标题:Why GitHub Commits Arent as Private as You Think,作者:Vladimir Shelkovnikov

本文地址:https://www.yitenyun.com/323.html

搜索文章

Tags

数据库 API FastAPI Calcite 电商系统 MySQL Web 应用 异步数据库 数据同步 ACK 双主架构 循环复制 Deepseek 宝塔面板 Linux宝塔 Docker 生命周期 JumpServer JumpServer安装 堡垒机安装 Linux安装JumpServer 序列 核心机制 esxi esxi6 root密码不对 无法登录 web无法登录 SSL 堡垒机 跳板机 HTTPS Windows Windows server net3.5 .NET 安装出错 宝塔面板打不开 宝塔面板无法访问 查看硬件 Linux查看硬件 Linux查看CPU Linux查看内存 连接控制 机制 HTTPS加密 Windows宝塔 Mysql重置密码 无法访问宝塔面板 HexHub 运维 Oracle 处理机制 Serverless 无服务器 语言 Spring Redis 异步化 ES 协同 技术 group by 索引 InnoDB 数据库锁 监控 开源 PostgreSQL 存储引擎 分页查询 高可用 SQL 动态查询 响应模型 缓存方案 缓存架构 缓存穿透 R edis 线程 数据 主库 自定义序列化 服务器 管理口 scp Linux的scp怎么用 scp上传 scp下载 scp命令 日志文件 MIXED 3 查询 SVM Embedding 存储 OB 单机版 Linux 安全 工具 电商 系统 SQLark SQLite-Web SQLite 数据库管理工具 架构 Rsync Postgres OTel Iceberg RocketMQ 长轮询 配置 Recursive 云原生 流量 共享锁 修改DNS Centos7如何修改DNS • 索引 • 数据库 防火墙 黑客 向量数据库 大模型 聚簇 非聚簇 sftp 服务器 参数 PG DBA 缓存 业务 AI 助手 Ftp ​Redis 机器学习 推荐模型 场景 GreatSQL 连接数 优化 万能公式 Netstat Linux 服务器 端口 数据备份 MySQL 9.3 RDB AOF openHalo 数据分类 加密 同城 双活 信息化 智能运维 INSERT COMPACT 窗口 函数 人工智能 推荐系统 核心架构 订阅机制 事务 Java 开发 MVCC 磁盘架构 线上 库存 预扣 redo log 重做日志 mini-redis INCR指令 网络架构 网络配置 Doris SeaTunnel prometheus Alert Redisson 锁芯 向量库 Milvus Undo Log 高效统计 今天这篇文章就跟大家 Python 引擎 性能 网络故障 B+Tree ID 字段 模型 IT运维 Canal Web PostGIS Redis 8.0 不宕机 数据脱敏 加密算法 R2DBC Hash 字段 分布式 集中式 崖山 新版本 自动重启 Pottery OAuth2 Token Entity JOIN ZODB 微软 SQL Server AI功能 容器化 虚拟服务器 虚拟机 内存 MongoDB 数据结构 DBMS 管理系统 容器 数据类型 读写 sqlmock 启动故障 悲观锁 乐观锁 分库 分表 LRU SpringAI 数据页 StarRocks 数据仓库 数据集成工具 分页 单点故障 QPS 高并发 传统数据库 向量化 工具链 Testcloud 云端自动化 Redka filelock 排行榜 排序 发件箱模式 事务隔离 部署 聚簇索引 非聚簇索引 分页方案 排版 大表 业务场景 意向锁 记录锁 速度 服务器中毒 Web 接口 1 SSH 池化技术 连接池 Caffeine CP 仪表盘 分布式架构 分布式锁​ dbt 数据转换工具 日志 原子性 EasyExcel MySQL8 AIOPS MCP 开放协议 优化器 网络 InfluxDB Order IT 双引擎 RAG HelixDB 频繁 Codis Go 数据库迁移 字典 单线程 Ansible 对象 Crash 代码 LLM 事务同步 订单 线程安全 Pump List 类型 UUIDv7 主键 服务器性能 锁机制 Weaviate MGR 分布式集群 并发控制 恢复机制 ReadView 国产数据库 Next-Key 国产 用户 RR 互联网 算法 数据字典 兼容性 GitHub Git 语句 播客 拦截器 动态代理 TIME_WAIT 负载均衡 矢量存储 数据库类型 AI代理 失效 多线程 行业 趋势 技巧 分布式锁 Zookeeper 解锁 调优 闪回 产业链 快照读 当前读 视图 关系数据库 编程 主从复制 代理 count(*) count(主键) 行数 UUID ID 神经系统 慢SQL优化 表空间 千万级 Valkey Valkey8.0 查询规划 恢复数据 CAS