GitHub が全リポジトリに永続的な所有者を割り当てた方法
GitHub は内部組織内の非所有リポジトリによるセキュリティリスクを解消するため、全リポジトリの所有者を特定し、新規作成時の必須化を含む新しい管理モデルを導入した。
キーポイント
所有権ギャップとセキュリティリスク
サービスに紐付かない非アーカイブリポジトリが多数存在し、秘密情報漏洩時の対応において所有者不明によるリスクや遅延が発生していた。
既存モデルの限界
従来のサービスカタログベースの管理は「サービス→リポジトリ」へのマッピングには強かったが、「リポジトリ→所有者」への逆引きが困難で、非サービス系リポジトリの所有者特定に手作業を要していた。
大規模な整理とモデル変更
1 年半かけて全アクティブリポジトリの所有権を検証し、8,000 件以上の不要なリポジトリをアーカイブするとともに、新規作成時に所有者必須化を徹底した。
再設計された所有権モデル
サービス依存型からリポジトリ固有の永続的所有者(Durable Owner)を定義するモデルへ転換し、インシデント対応やコンプライアンスの自動化基盤を強化した。
GitHub カスタムプロパティの採用
所有権情報をリポジトリ内のファイルや中央管理リポジトリではなく、ネイティブな GitHub カスタムプロパティとして実装し、組織全体での照会とポリシー適用を可能にしました。
柔軟性と堅牢性の両立
入力フォーマットはユーザーの負担を減らすため寛容(例:@プレフィックスの許容)としつつ、GitHub App によるバックエンド検証で無効なチームや離職者などを確実に排除しました。
自動化されたロールアウトと教訓
サービスカタログとの同期を自動実行し、CronJob を用いて警告Issueの作成からアーカイブまでのフローを実装しましたが、週末の実行はグローバルな組織では常に誰かがオンラインであるという前提を見落としていました。
重要な引用
That gap became a recurring problem during our secret scanning remediation effort: while we could technically rotate a secret, doing so without knowing the repository owner was risky and often disruptive
Having this rich metadata enables service-centric workflows, such as incident response, on-call routing, vulnerability management, and compliance scoping.
That left a significant ownership gap that included team repositories, documentation repositories, internal tools, one-off project repositories, personal experiment repositories
Fundamentally, we needed repository ownership to be a first-class property.
We wanted to make it frictionless to add ownership and lean on robust validation to catch invalid entries like nonexistent teams, former employees, and services that had been decommissioned.
We chose archiving because it's reversible and non-destructive: the repository becomes read-only and GitHub Actions stops running, but nothing is deleted.
影響分析・編集コメントを表示
影響分析
この記事は、大規模なオープンソースプラットフォームやエンタープライズ環境における「コード資産のガバナンス」の重要性を浮き彫りにしています。セキュリティ対応の効率化という観点から、リポジトリと責任者の明確な紐付けが不可欠であることが示されており、DevSecOps の実践において、単なるツール導入だけでなく、組織的なプロセス設計(所有権モデル)が重要であることを示唆しています。
編集コメント
GitHub の内部事例ですが、大規模なコード資産を扱う組織にとって「誰が責任を持つのか」の明確化は、セキュリティインシデント発生時の決定的な差を生む重要な教訓です。
GitHub の主要な内部組織には、現在 14,000 を超えるリポジトリが存在します。2025 年初頭時点では、非アーカイブ化されたリポジトリが 11,000 を超えていましたが、その大半は明確な所有者を持っていませんでした。本番環境のサービスに紐付くリポジトリについては、従来から堅牢な永続的な所有権管理を行ってきましたが、関連するサービスがないリポジトリについては、誰が所有者であるかを reliably 判断する方法がありませんでした。
このギャップは、シークレットスキャン(secret scanning)の修正対応において recurring な問題となりました。技術的にはシークレットを回転させることは可能ですが、リポジトリの所有者が不明な状態でそれを行うのはリスクが高く、しばしば業務に支障をきたすものであり、また修正作業を適切にルーティングする明確な方法も存在しませんでした。1 年半の期間を通じて、すべてのアクティブなリポジトリについて所有権を検証し、使用されなくなった約 8,000 のリポジトリをアーカイブ化するとともに、リポジトリ作成時に所有者の指定が必須となるように仕組みを変更しました。
当初の所有権モデル
長年にわたり、GitHub は内部のサービスカタログ(Service Catalog)を通じて、デプロイされたサービスの所有権を追跡してきました。各サービスエントリには、どのリポジトリに所属しているかといったメタデータが記録されており、これにより「サービスからリポジトリへのマッピング」「所有チーム」「エグゼクティブスポンサー」「サポート情報」を把握することが可能でした。
以下は、Repo Ownership アプリケーションにおけるサービス所有権エントリの例です:
- チーム:github/repo-ownership-dev
リポジトリ:https://github.com/github/repo-ownership
名称:repo-ownership
種類:moda(サービス型)
正式名称:Repo Ownership
説明:組織全体にわたるリポジトリの所有権を強制するサービス
メンテナー:mrecachinas
エグゼクティブ・スポンサー:stephanmie
...
この豊富なメタデータ(metadata)により、インシデント対応、オンコールルーティング、脆弱性管理、コンプライアンスのスコープ設定など、サービス中心のワークフローが可能になります。
残念ながら、その関係性は多対一(many-to-one)でした。つまり、1 つのサービスは単一のリポジトリにしか紐付けられませんが、1 つのリポジトリには複数のサービスを割り当てることができました。これは、サービスから出発すれば対応するリポジトリとその所有者を見つけることができる一方、リポジトリから出発して所有者を探す場合は逆引きを行う必要があり、しかもそれは最初からサービスとマッピングされているリポジトリに限られることを意味しました。
これにより、チーム用リポジトリ、ドキュメント用リポジトリ、社内ツール、単発プロジェクトのリポジトリ、個人的な実験用リポジトリ、そしてデプロイされたサービスをバックアップしていないあらゆるリポジトリを含む、大きな所有権のギャップが残されました。これらの「所有者不明」リポジトリのいずれかの所有者に連絡する必要があるたびに、コミット履歴の確認、README の読込、Slack での周囲への問い合わせ、あるいはリポジトリ名に基づいた推測など、手作業が必要でした。
一度限りの取り組みであれば、そのような曖昧さは煩わしいものの管理可能ですが、組織全体に広がる再発するセキュリティワークフローにとっては、実際のリスクとなります。シークレットスキャンニングのクリーンアップ中に、アラートについて適切な判断を下す前に、正しい所有者を見つけることに時間をかけすぎてしまいました。
新しい所有権モデルの設計
根本的に、リポジトリの所有権は第一級のプロパティである必要がありました。各リポジトリ内に専用のファイルに所有権を保存するか、集中管理されたリポジトリで維持することを検討しましたが、最終的には GitHub カスタムプロパティ (custom properties) を選択しました。このアプローチにより、ネイティブで構造化され、組織全体で照会可能な所有権管理の方法が提供されました。また、所有権の種類に応じて、エンタープライズおよび組織のポリシーやルールセットを selectively 適用することも可能になりました。
私たちは2つのカスタムプロパティを作成しました: ownership-type と ownership-name です。
ownership-type は3つの値を受け付けました: "Service Catalog" (サービスカタログ)、"Hubber Handle" (「Hubber」とは GitHub の従業員を指す用語です)、および "Team" (チーム) です。これらは、GitHub におけるリポジトリの所有権の現実的な範囲を網羅しています。リポジトリは、オンコールチームと定義されたライフサイクルを持つサービスに所属するか、共有ドキュメントリポジトリや内部ツールのようなチームに所属するか、あるいは個人プロジェクトや実験のような個人の所有となります。
ownership-name は軽微な検証機能を持つテキストフィールドでした。GitHub App はすべての値を検証しました:Hubber のハンドルは、当社の GitHub オrganization 内の実際のメンバーシップに対してチェックされ、チームが組織内に存在し、少なくとも 2 人のメンバーがいることが確認されました。また、Service Catalog エントリは、Service Catalog 自体と照合して確認されました。フォーマットについては意図的に寛容な方針を採用しました。誰かが my-team の代わりに @my-team と入力した場合でも、私たちはそれを受け入れました。所有権の追加を摩擦なく行えるようにし、存在しないチームや退職した従業員、廃止されたサービスといった無効なエントリは堅牢な検証機能で捕捉できるようにすることを望んでいました。
初日カバレッジ
誰にも何かを依頼する前に、Service Catalog からリポジトリのカスタムプロパティへの定期的な同期を構築しました。既知のサービスをバックアップしているすべてのリポジトリについて、ownership-type は「Service Catalog」に設定され、ownership-name も自動的に埋められました。これにより、約 1,500 のサービスバックアップ型リポジトリが処理され、チーム用リポジトリ、ドキュメント用リポジトリ、単発プロジェクト、個人用リポジトリが残りました。
展開プロセス
これを展開するために、Kubernetes CronJob に支えられた GitHub App を構築しました。強制ロジックには Service Catalog、GitHub API、およびいくつかの内部システムへのアクセスが必要だったため、単純な GitHub Actions ワークフローでは不十分でした。
以下の図は、初期所有権スキャンから警告Issueの作成、自動クローズ、または 30 日後のアーカイブに至るリポジトリ所有権強制フローを示しています。

私たちは、誰も注意を払っていないだろうと考えて、CronJob(定期実行ジョブ)の初回実行を土曜日の朝にスケジュールしました…しかし、それは大きな誤算でした!組織内のリポジトリで問題が発生し始め、Slack で人々が飛びつき、「自分のリポジトリにあるこの新しい問題について、アーカイブされる」と尋ねるようになりました。世界中に分散した企業では、常に誰かがオンラインになっています。
30 日間の猶予期間が過ぎた後、まだ所有権が設定されていないリポジトリはすべてアーカイブしました。アーカイブを選んだのは、それが取り消し可能で破壊的ではないからです:リポジトリは読み取り専用になり、GitHub Actions(GitHub の自動化プラットフォーム)の実行も停止しますが、何も削除されません。誰かが再び必要とした場合、アーカイブ解除して所有権を設定し、継続するための簡単な方法を提供しました。これにより、あらゆる例外ケースについて議論するのではなく、安全に広くアーカイブを適用することが可能になりました。
初期の猶予期間が過ぎ、アーカイブの大部分が完了した後、強制ループの期間を 30 日から 1 時間に短縮しました。作成時の所有権要件を何らかの方法で回避してしまった新しいリポジトリは、ほぼ即座にフラグ付けされます。
鋭いエッジケース
これは主にシームレスに展開されましたが、2 つの小さな内部インシデントにより、いくつかの興味深いエッジケースが明らかになりました。
最初のインシデントは、所有権が適用されていないリポジトリをアーカイブしたことが原因でした。Datadog は監視ワークフローの一環として、そのリポジトリにイシューを作成するように設定されていました。リポジトリがアーカイブされ Datadog がイシューを作成できなくなった際、内部の監視サービスがこれを検知して自動的に所有チームにページ通知し、彼らが私たちにエスカレーションしました。
このインシデントは、通知方法における隙間を露呈させました。所有権に関するイシューはリポジトリに届いていましたが、誰にも直接通知が行われていなかったのです。これに対処するため、リポジトリ管理者に対して@メンションを行い、所有権に関するイシューのフォールバックとして書き込み権限を持つすべてのユーザーを割り当てるようにしました。これにより、イシューが埋もれたり見落とされたりすることがなくなり、実際に所有権を設定できる人々が即座にそれらを確認できるようになりました。
2 つ目のインシデントはデータ信頼性の問題でした。Service Catalog のアウトエージに対しては堅牢でしたが、古くなったデータや破損したデータを返す可能性については考慮していませんでした。もし不良データによってアプリが、実際には失われていないのにバッチ処理されたリポジトリ群が Service Catalog エントリを失ったと誤認し、その結果として正当な所有者を持つリポジトリを一括でアーカイブしてしまう事態になりかねませんでした。
正当なリポジトリのアーカイブ化リスクを軽減するため、下限閾値を設定しました。各実行サイクルでは、アクションを実行する前に、アプリが実行しようとしているアーカイブの数と開こうとしているイシューの数をカウントします。この数が保守的な閾値を超えた場合、実行を完全に中止し、失敗した実行のリスクを負う代わりに Datadog モニターをトリガーします。Service Catalog に到達できない場合は、ジョブは Service Catalog の検証をスキップし、独自に検証可能な項目のみを確認します。
数値による結果
最終的に、アクティブなリポジトリが約 3,000 件、アーカイブ化されたリポジトリが 11,000 件となりました(開始時のアーカイブ化数は約 3,000 件でした)。最初の土曜朝の実行から定常状態に至るまでの全工程は 45 日未満で完了しました。すべてのアクティブなリポジトリには検証済みの所有者が割り当てられており、そうでないものはアーカイブされます。
これらの新たにアーカイブされたリポジトリの多くは、何年もコミットが行われていませんでした。放棄された実験プロジェクト、終了したハッカソンでの成果物、そして 2008 年の個人によるプロトタイプさえ含まれています。これらをアーカイブ化することで、最終的に攻撃対象領域を縮小し、アクティブなリポジトリの在庫が現実を反映するようになりました。
所有権の定着
100% カバレッジに到達しても、それが維持されなければ意味がありません。そのため、リポジトリ作成ページ(以下参照)、内部ツール、自動化プロセスを含むすべてのリポジトリ作成ワークフローにおいて、所有権プロパティを強制し、新規リポジトリには必須としました。

また、執行ループも強化しました。所有権を失ったリポジトリは、従来の 30 日間の猶予期間ではなく、1 時間以内にフラグが立てられるようになりました。
各所有権タイプには固有の耐久性特性があり、私たちはそれらを前提に設計を行いました。Service Catalog のエントリはサービスのライフサイクルに従います:あるサービスが廃止されると、その関連するリポジトリも通常アーカイブされ、これが意図された動作です。チームについては、少なくとも 1 人のメンバーがいることが検証されており、チームの存在と所属メンバーは概ね安定している傾向があります。個人(Individual Hubber)の所有権は、誰かが会社を去った場合にのみ無効となりますが、通常これはその個人の関連リポジトリもアーカイブされるべきことを意味します。単一の人物を超えて存続する必要があるほど重要なリポジトリであれば、所有権は個人ではなくチームまたはサービスであるべきです。
これがあなたにとって何を意味するか
GitHub のカスタムプロパティ(custom properties)を用いて、今日から同様の所有権モデルを実装することができます。以下が私たちが推奨するアプローチです:
所有権の分類体系を定義してください。貴組織にとってどのタイプの所有者が適切かを決定してください。サービス、チーム、個人という区分は私たちには機能しましたが、貴組織のカテゴリ構成は異なる可能性があります。
組織レベルでカスタムプロパティを作成します。所有権タイプのプロパティは、許可された値を持つ単一選択として設定し、所有権名のプロパティはテキストフィールドとして設定してください。カスタムプロパティは API を通じて照会可能であり、組織全体で表示されます。
サービスカタログや資産インベントリがある場合は、同期してください。すでに追跡しているリポジトリの所有権情報を埋めることは、人々にギャップを埋めるよう求める前に、意味のあるカバレッジを迅速に得る最速の方法です。
リポジトリ作成時に所有権の強制を実装します。プロパティを必須項目として設定し、今後のインベントリの清潔さを保ちます。
既存のリポジトリに対する猶予期間ワークフローを構築します。合理的な期限(30 日を使用しました)を設定したイシューを開き、未請求のリポジトリはアーカイブしてください。アーカイブは元に戻せるかつ破壊的ではないため、安全なデフォルトとなります。
最初の強制実施を土曜日に行わないでください!
大規模な自動化を信頼する前にガードレールを構築してください。低水位閾値と @メンションによるフォールバック機能は、元の設計には含まれていませんでした。これらは実際のインシデントから生まれたものです。リポジトリのアーカイブやイシューの大規模作成を行うシステムを構築する場合は、データソースが時々誤っている可能性があり、通知が失われることもあると想定してください。その前提で最初から設計してください。
カスタムプロパティの詳細については、カスタムプロパティドキュメントをご覧ください。
「GitHub がすべてのリポジトリに永続的な所有者を与えた」という記事は、The GitHub Blog に最初に掲載されました。
原文を表示
GitHub has over 14,000 repositories across our primary internal GitHub organization. As of early 2025, there were over 11,000 non-archived repositories, the vast majority of which with no clear owner. For repositories attached to production services, we have historically had robust durable ownership, but for repositories with no associated service, there was no reliable way to tell who the owner is.
That gap became a recurring problem during our secret scanning remediation effort: while we could technically rotate a secret, doing so without knowing the repository owner was risky and often disruptive, and we had no clear way to route remediation work. Over the course of a month and a half, we validated ownership for every active repository, archived about 8,000 repositories that were no longer in use, and changed repository creation so that ownership was required from the start.
Our original ownership model
For years, GitHub has been tracking ownership for deployed services through our internal Service Catalog. Each service entry recorded metadata like which repository it lived in, which gave us a mapping from service to repository; the owning team; executive sponsor; and support information.
Here’s an example of the Repo Ownership app’s service ownership entry:
- 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
...
Having this rich metadata enables service-centric workflows, such as incident response, on-call routing, vulnerability management, and compliance scoping.
Unfortunately, that relationship was many-to-one (i.e., a service could only be attached to a single repository, but a single repository could have multiple services). That meant if you started from a service, you could find the repository and its owners. But if you started from a repository and needed to find an owner, you had to reverse the lookup, and that only worked for repositories that mapped to a service in the first place.
That left a significant ownership gap that included team repositories, documentation repositories, internal tools, one-off project repositories, personal experiment repositories, and anything else that didn’t back a deployed service. Every time we needed to contact the owner of one of these “unowned” repositories, it required manual work: check the commit history, read the README, ask around in Slack, or make a guess based on the repository name.
For a one-off effort, that kind of ambiguity is annoying but manageable. For recurring security workflows that fan out across the entire organization, it presents a real risk. During our secret scanning cleanup, we spent too much time trying to find the right owners before we could make informed decisions about alerts.
Designing the new ownership model
Fundamentally, we needed repository ownership to be a first-class property. We considered storing ownership in a dedicated file within each repository or maintaining it in a centralized repository, but ultimately chose GitHub custom properties. This approach provided a native, structured, and organization-wide queryable way to manage ownership. It also enabled us to enforce enterprise and organization policies and rulesets selectively according to ownership type.
We created two custom properties: ownership-type and ownership-name.
ownership-type accepted three values: “Service Catalog,” “Hubber Handle” (a “Hubber” is what we call a GitHub employee), and “Team.” These covered the realistic range of repository ownership at GitHub. A repository either belongs to a service (with an on-call team and a defined lifecycle), a team (like a shared documentation repository or internal tool), or an individual (like a personal project or experiment).
ownership-name was a text field with light validation. Our GitHub App validated every value: Hubber handles were checked against actual membership in our GitHub organization, teams were verified to exist in the organization and have at least two members, and Service Catalog entries were confirmed against our Service Catalog itself. We were intentionally permissive on formatting. If someone typed @my-team instead of my-team, we accepted it. We wanted to make it frictionless to add ownership and lean on robust validation to catch invalid entries like nonexistent teams, former employees, and services that had been decommissioned.
Day-one coverage
Before we asked anyone to do anything, we built a periodic sync from Service Catalog to repository custom properties. Every repository that backed a known service had its ownership-type set to “Service Catalog” and its ownership-name populated automatically. That took care of about 1,500 service-backed repositories, leaving team repos, docs repos, one-off projects, and personal repos remaining.
The rollout
To roll this out, we built a GitHub App backed by a Kubernetes CronJob. The enforcement logic needed access to Service Catalog, the GitHub API, and a few internal systems, so a simple GitHub Actions workflow wasn’t sufficient.
The diagram below shows the repository ownership enforcement flow, from initial ownership scan through warning issue creation, automatic closure, or archival after 30 days.

We scheduled the first run of the CronJob for a Saturday morning thinking nobody would be paying attention… Big mistake! Issues started appearing in repositories across the organization, and people began jumping on Slack, asking about this new issue in their repository saying it would be archived. At a globally distributed company, someone is always online.
After the 30-day grace period, we archived any repository that still didn’t have ownership set. We chose archiving because it’s reversible and non-destructive: the repository becomes read-only and GitHub Actions stops running, but nothing is deleted. If someone needs it again, we provided an easy way for them to unarchive it, set ownership, and continue. That enabled us to safely apply archival broadly instead of debating every edge case.
Once the initial grace period passed and the bulk of archiving was done, we tightened the enforcement loop from 30 days to one hour. A new repository that somehow bypassed the creation-time ownership requirement would get flagged almost immediately.
The sharp edges
This mostly rolled out seamlessly, with two minor internal incidents exposing some interesting edge cases.
The first incident was caused by archiving a repository where ownership had not been applied. Datadog had been configured to open issues in that repository as part of a monitoring workflow. When the repository was archived and Datadog couldn’t create the issue, our internal monitoring service noticed and automatically paged the owning team, and they escalated to us.
That incident exposed a gap in how we notified. The ownership issues were landing in repositories, but nobody was getting notified directly. We fixed this by @-mentioning repository administrators and assigning all users with write access as a fallback on ownership issues. That way the issues couldn’t be buried or overlooked, and the people who could actually set ownership saw them immediately.
The second incident was a data reliability problem. While we were robust against a Service Catalog outage, we didn’t consider that it might return stale data or corrupted data. If bad data caused the app to think a batch of repositories had lost their Service Catalog entries when they hadn’t, we’d be mass-archiving repositories with perfectly valid owners.
To mitigate the risk of archiving legitimate repositories, we added a low water mark. During each run, prior to performing any actions, the app would tally how many archives it was about to perform and issues it was about to open. If the number exceeded a conservative threshold, it would bail out entirely and trigger a Datadog monitor rather than risk a bad run. If Service Catalog was unreachable, the job would skip Service Catalog validation and only check what it could verify independently.
Results, by the numbers
We finished with approximately 3,000 active repositories and 11,000 archived (up from about 3,000 archived at the start). The entire effort took under 45 days from the first (Saturday morning) run to steady state. Every active repository now has a validated owner, or it gets archived.
Many of those newly archived repositories hadn’t seen a commit in years: abandoned experiments, completed hackathon projects, and even one-person prototypes from 2008! Archiving them ultimately reduced our surface area and made the active repository inventory reflect reality.
Making ownership stick
Getting to 100% coverage is only useful if it stays at 100%, so we enforced ownership properties across every repository creation workflow, including the repository creation page (shown below), internal tooling and automation, making them mandatory for all new repositories.

We also tightened the enforcement loop: repositories that lose their ownership are now flagged within one hour rather than the original 30-day grace period.
Each ownership type has its own durability characteristics, and we designed around them. Service Catalog entries follow service lifecycle: when a service is deprecated, its repositories typically get archived too, and that’s the intended behavior. Teams are validated for having at least one member, and team existence and membership tend to be reasonably stable. Individual Hubber handles only become invalid when someone leaves the company, which usually means their personal repositories should be archived regardless. For any repository critical enough to outlast a single person, ownership should be a team or a service, not an individual.
What this means for you
You can implement a similar ownership model today using GitHub custom properties. Here’s the approach we’d recommend:
Define your ownership taxonomy. Decide which types of owners make sense for your organization. Services, teams, and individuals worked for us, but your categories might look different.
Create custom properties at the organization level. Set up an ownership-type property as a single-select with your allowed values, and an ownership-name property as text. Custom properties are queryable through the API and visible across the organization.
If you have a service catalog or asset inventory, sync it. Populating ownership for repositories you already track is the fastest way to get meaningful coverage before you start asking people to fill in gaps.
Enforce ownership at repository creation time. Make the properties required so the inventory stays clean going forward.
Build a grace-period workflow for existing repositories. Open issues with a reasonable deadline (we used 30 days), then archive repositories that go unclaimed. Because archiving is reversible and non-destructive, it’s a safe default.
Don’t run your first enforcement pass on a Saturday!
Build guardrails before you trust automation at scale. The low water mark and the @-mention fallbacks weren’t in the original design. They came from real incidents. If you’re building a system that archives repositories or opens issues at scale, assume your data sources will occasionally be wrong, and your notifications will sometimes get lost. Design for that from the start.
For more on custom properties, see the custom properties documentation.
The post How GitHub gave every repository a durable owner appeared first on The GitHub Blog.
関連記事
今日のまとめ
AI日報で今日の重要ニュースをまとめ読み