GitHub 如何为每个仓库赋予持久的所有者
摘要
GitHub Engineering 描述了如何通过自定义属性实现持久所有权、归档数千个未使用的仓库以及在创建时强制执行所有权,从而解决了无主仓库的问题。
暂无内容
查看缓存全文
缓存时间: 2026/07/09 22:38
# 如何让GitHub的每个仓库都有一个持久的所有者
来源:https://github.blog/security/application-security/how-github-gave-every-repository-a-durable-owner/
GitHub 在我们主要的内部 GitHub 组织中拥有超过 14,000 个仓库。截至 2025 年初,其中有超过 11,000 个非归档仓库,其中绝大部分没有明确的所有者。对于与生产服务相关联的仓库,我们历史上一直拥有可靠的持久所有权,但对于没有关联服务的仓库,我们无法可靠地判断所有者是谁。
这个缺口在我们的**密钥扫描修复工作**中反复出现:虽然我们技术上可以轮换密钥,但在不知道仓库所有者的情况下这样做是有风险的,而且常常会造成干扰,我们也没有明确的方法来分配修复工作。在一个半月的时间里,我们验证了每个活跃仓库的所有权,归档了大约 8,000 个不再使用的仓库,并改变了仓库创建流程,使得所有权从一开始就成为必需项。
## 我们最初的所有权模型
多年来,GitHub 一直通过我们的内部服务目录来跟踪已部署服务的所有权。每个服务条目都记录了元数据,比如它所在的仓库,这给了我们从服务到仓库的映射;所属团队;执行发起人;以及支持信息。
以下是仓库所有权应用的服务所有权条目示例:
``
- team: github/repo-ownership-dev
repo: https://github.com/github/repo-ownership
name: repo-ownership
kind: moda
long_name: Repo Ownership
description: Service enforcing repo ownership across the org
maintainer: mrecachinas
exec_sponsor: stephanmiehe
...
``
拥有这些丰富的元数据使得以服务为中心的工作流成为可能,例如事件响应、值班路由、漏洞管理和合规范围界定。
遗憾的是,这种关系是多对一的(即一个服务只能关联一个仓库,但一个仓库可以关联多个服务)。这意味着如果你从服务入手,你可以找到仓库及其所有者。但如果你从仓库入手需要找到所有者,你就必须反向查找,而这只对原本就映射到服务的仓库有效。
这就留下了巨大的所有权缺口,包括团队仓库、文档仓库、内部工具、一次性项目仓库、个人实验仓库,以及任何其他不支撑已部署服务的仓库。每次我们需要联系这些“无主”仓库的所有者时,都需要手动操作:查看提交历史、阅读README、在Slack上询问、或者根据仓库名称猜测。
对于一次性的工作,这种模糊性虽然烦人但还可管理。但对于需要在整个组织中铺开的定期安全工作流,这构成了真正的风险。在密钥扫描清理过程中,我们花了太多时间去寻找正确的所有者,然后才能对告警做出明智的决策。
## 设计新的所有权模型
从根本上说,我们需要让仓库所有权成为第一类属性。我们考虑过在每个仓库中存储一个专用文件,或者维护在一个集中式仓库中,但最终选择了GitHub自定义属性。这种方法提供了一种原生的、结构化的、可在整个组织中查询的方式来管理所有权。它还使我们能够根据所有权类型选择性地执行企业级和组织级策略及规则集。
我们创建了两个自定义属性:`ownership-type` 和 `ownership-name`。
- `ownership-type` 接受三个值:“Service Catalog”、“Hubber Handle”(“Hubber”是我们对GitHub员工的称呼)和“Team”。这些涵盖了GitHub仓库所有权的实际范围。一个仓库要么属于一个服务(有值班团队和定义的生命周期),要么属于一个团队(例如共享的文档仓库或内部工具),要么属于个人(例如个人项目或实验)。
- `ownership-name` 是一个带有轻量验证的文本字段。我们的GitHub应用会对每个值进行验证:Hubber句柄会与GitHub组织中的实际成员身份进行核对,团队会验证为存在于组织中且至少有两名成员,服务目录条目会与我们的服务目录本身进行确认。我们在格式上故意保持宽松。如果有人输入了`@my-team`而不是`my-team`,我们也会接受。我们希望让添加所有权变得无摩擦,并依靠稳健的验证来捕获无效条目,例如不存在的团队、已离职员工以及已停用的服务。
## 首日覆盖
在要求任何人做任何事情之前,我们构建了一个从服务目录到仓库自定义属性的周期性同步。每个已知服务背后的仓库都已将其`ownership-type`设置为“Service Catalog”,并且`ownership-name`自动填充。这解决了大约1,500个由服务支撑的仓库,剩下的是团队仓库、文档仓库、一次性项目和个人仓库。
## 推广部署
为了推广这个方案,我们构建了一个基于Kubernetes CronJob的GitHub应用。强制逻辑需要访问服务目录、GitHub API以及一些内部系统,因此简单的GitHub Actions工作流是不够的。
下图展示了仓库所有权强制流程,从初始的所有权扫描到警告issue的创建、自动关闭或30天后归档。
仓库所有权强制流程图:扫描仓库的所有权,缺少所有权则打开一个30天归档警告issue,未解决的issue导致归档,已解决的issue自动关闭。我们在周六早上安排了CronJob的第一次运行,以为没人会注意到……大错特错!整个组织的仓库中开始出现issue,人们开始在Slack上询问关于他们仓库中那个说会被归档的新issue。在一家全球分布的公司,总有人在线。
在30天宽限期过后,我们归档了所有仍未设置所有权的仓库。我们选择归档是因为它是可逆的且非破坏性:仓库变为只读,GitHub Actions停止运行,但没有任何内容被删除。如果有人再次需要它,我们提供了一种简单的方法让它们取消归档、设置所有权并继续使用。这使我们能够安全地大规模应用归档,而不是对每个边缘案例进行争论。
一旦初始宽限期过去并且大部分归档工作完成,我们将强制循环从30天缩短到1小时。一个以某种方式绕过了创建时所有权要求的新仓库将几乎立即被标记。
## 棘手的边缘情况
这次推广基本平稳,只有两次小的内部事件暴露了一些有趣的边缘情况。
第一次事件是由归档一个未设置所有权的仓库引起的。Datadog被配置为在该仓库中打开issue作为监控工作流的一部分。当仓库被归档且Datadog无法创建issue时,我们的内部监控服务注意到并自动通知了所属团队,他们又升级给了我们。
那次事件暴露了我们在**通知方式**上的缺口。所有权issue落到了仓库里,但没有人直接收到通知。我们通过@提及仓库管理员,并在所有权issue上以具有写入权限的所有用户作为后备,解决了这个问题。这样issue就不会被埋没或忽略,而实际可以设置所有权的人也会立即看到它们。
第二次事件是一个数据可靠性问题。虽然我们能够应对服务目录的故障,但我们没有考虑到它可能会返回过时数据或损坏数据。如果错误数据导致应用认为一批仓库丢失了服务目录条目,而实际上并没有丢失,那么我们就会大规模归档那些拥有完全有效所有者的仓库。
为了降低归档合法仓库的风险,我们增加了一个低水位标记。在每次运行期间,在执行任何操作之前,应用会统计它即将执行多少归档和打开多少issue。如果数量超过了保守的阈值,它会完全退出并触发一个Datadog监控器,而不是冒险进行错误的运行。如果服务目录不可访问,作业会跳过服务目录验证,只检查它可以独立验证的内容。
## 数据结果
我们最终拥有大约3,000个活跃仓库和11,000个归档仓库(从开始时的大约3,000个归档仓库增加到这个数量)。整个工作从第一次(周六早上)运行到稳定状态用了不到45天。现在每个活跃仓库都有一个经过验证的所有者,否则就会被归档。
许多新归档的仓库已经多年没有提交:废弃的实验、完成的黑客马拉松项目,甚至还有2008年的单人原型!归档它们最终减少了我们的攻击面,并使活跃仓库清单反映了真实情况。
## 让所有权持续有效
达到100%的覆盖率只有在保持100%的情况下才有用,因此我们在每个仓库创建工作流(包括仓库创建页面(如下所示)、内部工具和自动化)中强制要求所有权属性,使其对所有新仓库成为必填项。
GitHub仓库创建页面显示必填的所有权自定义属性字段。我们还收紧了强制循环:失去所有权的仓库现在会在1小时内被标记,而不是最初的30天宽限期。
每种所有权类型都有其自身的持久性特征,我们针对这些特征进行了设计。服务目录条目遵循服务生命周期:当一个服务被废弃时,它的仓库通常也会被归档,这正是预期的行为。团队会被验证为至少有两名成员,团队的存在和成员通常相当稳定。只有当有人离开公司时,个人的Hubber句柄才会失效,这通常意味着他们个人的仓库无论如何都应该被归档。对于任何足够重要以至于需要超出个人生命周期而长期存在的仓库,其所有者应该是团队或服务,而不是个人。
## 这对你意味着什么
你现在就可以使用GitHub自定义属性实现类似的所有权模型。以下是我们在方法上给出的建议:
- **定义你的所有权分类法。** 决定哪些所有者类型对你的组织有意义。服务、团队和个人对我们有效,但你的分类可能不同。
- **在组织级别创建自定义属性。** 设置一个值为单选的`ownership-type`属性,允许的值是你定义的类别;再设置一个文本类型的`ownership-name`属性。自定义属性可通过API查询,并在整个组织中可见。
- **如果你有服务目录或资产清单,同步它们。** 为你已经跟踪的仓库填充所有权,是在你要求人们填补缺口之前获得有意义覆盖率的最快方式。
- **在仓库创建时强制所有权。** 使这些属性必填,以便仓库清单在未来保持干净。
- **为现有仓库构建一个宽限期工作流。** 打开带有合理截止日期的issue(我们用了30天),然后归档无人认领的仓库。由于归档是可逆的且非破坏性的,这是一个安全的默认操作。
- **不要在周六运行你的第一次强制扫描!**
- **在信任自动化大规模运行之前,先构建防护措施。** 低水位标记和@提及后备并不在最初的设计中。它们来自真实的事件。如果你正在构建一个大规模归档仓库或打开issue的系统,请假设你的数据源有时会出错,你的通知有时会丢失。从一开始就为此进行设计。
有关自定义属性的更多信息,请参见自定义属性文档 (https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization)。
## 作者
Michael Recachinas
Michael Recachinas 是 GitHub 的一名高级安全工程师,他领导大规模安全计划,专注于漏洞管理、安全开发生命周期工具和以开发者为先的安全自动化。他的职业生涯一直致力于构建大规模运行的系统,帮助团队让安全选择成为容易的选择。
相似文章
@dabit3:一个非常有趣的故事,展示了 @github 目前的状态无法有效保护开源维护者免受 AI 滥用的影响。
一个故事描述了在发布 900 美元赏金后,AI 机器人如何用垃圾评论和未经测试的 PR 淹没了一个 GitHub 仓库,迫使维护者实施如贡献者白名单和声誉机器人等变通方法,凸显了 GitHub 缺乏反机器人机制。
@github: 我们听到了你们的声音。我们也同意。鉴于实体媒体的最新发展,GitHub 自豪地宣布,你可以……
GitHub 宣布,用户现在可以订购包含其公共仓库的 CD-ROM,提供一种物理的、复古的方式来拥有自己的代码。
GitHub对AI Agent的计划(90分钟阅读)
本文探讨了AI编码代理的爆炸式增长(2026年增长1400%)如何使GitHub的基础设施承压,导致显著的服务可用性问题,并讨论了GitHub为使其平台适应这一新时代而制定的计划。
我们应得的代码锻造平台
本文讨论了对GitHub可靠性日益增长的不满,并提出基于AT协议的去中心化Git锻造平台Tangled,作为一个结合了中心化便利性与用户数据所有权的有前途的替代方案。
@danshipper: GitHub 坐拥前排席位,目睹代码编写方式的变革——如今每个人,连同他们的智能体大军,都能提交代码。三月份……
GitHub COO Kyle Daigle 讨论了 AI 智能体创建的拉取请求激增(3 月份达到 1700 万),以及预计今年将有 140 亿次提交。他强调开源维护者的控制权、基于使用量的定价模式取代按席位许可,以及随着 AI 工具使非开发者也能构建应用,开发者与非开发者之间的界限正在模糊。