パッケージを公開するリポジトリにセキュリティ管理について扱う
- OIDC
- Trusted Publisher
- Enviroment:
npm
を指定する - Require two-factor authentication and disallow tokens
で、2FA or OIDCのどちらかでのみpublish可能にできる。
GitHubのアカウントが乗っ取られた場合、npmにpublishされてしまう。
- できるだけ外部依存を減らす
actions/*
と ビルトインコマンドのみにする
- 依存は必ずSHA PINする Require actions to be pinned to a full-length commit SHAを有効化
- CODEOWNERでworkflowの変更にオーナーをつける
- RulesetでRequire review from Code Ownersを有効化
- Release Workflowの実行にはEnviroment Protection RulesでOwnerの承認を必須にする
- Enviroment:
npm
はrelease/*
ブランチのみで実行できるようにする
- npmアカウントの乗っ取り
- MFAがセキュリティキーのみなので難しい
- npm tokenの漏洩
- tokenのみではpublishできない
- OIDCのtokenの漏洩
- これは防げないが永続的にするにはworkflow自体を乗っ取る必要がある
- GitHubアカウントの乗っ取り
- MFAがセキュリティキーのみなので難しい
- GitHub Actionsの脆弱性 = GITHUB_TOKENの漏洩
- workflowファイルは外部依存を減らす =
gh
コマンドぐらいにしたい github/workflows/create-release-pr.yml
.github/workflows/release.yml
- workflowファイルは外部依存を減らす =
- Workflowファイルの改ざん
- 攻撃パターン
- GITHUB_TOKENの漏洩
- -> RulesetでのProteection BranchでCODEOWNERSの承認を必須にすることで防げる
- 管理者アカウントの侵害
- MFAのチェックなしにリポジトリの設定を変更できないようにしたい
- GITHUB_TOKENの漏洩
- 攻撃パターン
- リポジトリOwnerアカウントの乗っ取り
- MFAがセキュリティキーのみなので難しい
- リポジトリOwnerのバスファクタ
- -> npmの権限移譲、GitHubの権限移譲と基本的には変わらないのでバスファクタ係数自体は変化がない
- GitHub Actionsの実行権限の乗っ取り
- -> Enviroment Protection Rulesで実行にはOwnerの承認を必須にする
- GitHubのTokenの漏洩
- これによって崩壊しちゃってるので、なんとかしたい
- GitHub TokenでRulesetなどを変更できてしまう
- 1passwordのgh pluginを使うが、token自体は存在している
- https://developer.1password.com/docs/cli/shell-plugins/github/
- GitHubリポジトリのProtection RulesでMFAのチェックを必須にする方法がわからない
- GitHub Tokenが漏洩した場合の対策が難しい
gh
コマンドを使う場合、短期的にTokenが存在してしまうgh
コマンドのtokenはclassicで権限が広すぎる - admin権限を含んでいる- 短期的にローテーションされていないため、長期的な問題がある
- 1passwordの10分セッションで、10分ごとにローテーションしたい
gh
コマンドのScopeをread-onlyにする- Short-lived tokenを使う
gh
コマンドのデフォルトの権限はとても広い。
- repo - リポジトリへのフルアクセス
- read:org - 組織情報の読み取り
- gist - Gistの作成・管理
- https://github.com/cli/cli/blob/42a8e0265c9c53f9e8fadf272bdfa5beee01442d/internal/authflow/flow.go#L43
そのため、gh
コマンドの権限を最小限にするのが、攻撃のリスクを減らす上で重要。
普段のgh
コマンドの利用では、read-onlyで十分なことが多いので、gh
コマンドのデフォルトの権限をread-onlyにできると良い。
GitHubのPersonal Access Token (PAT)には、2種類ある。
- classic personal access tokens: ユーザーに紐づく権限
- fine-grained personal access tokens: リソースに紐づく権限
GitHubのClassic personal access tokensは read-only tokenのような細かい設定はできない。一方で、fine-grained personal access tokensは、read-only tokenは作成できるが、OrganizationごとにTokenを作る必要がある。
Open Sourceとかは基本Public Repositoryなので、fine-grained personal access tokensで"Public repositories"だけを選択して、何も権限を付与しないことで、read-only tokenを作成できる。
- Repository access: Public repositories
- Permissions
- なし
一方で、Private Repositoryを扱う場合は、Organizationごとにfine-grained personal access tokensで次のようなread権限を持ったTokenを作成する必要がある。
- Repository access: All repositories
- Permissions
- Contents: Read-only
- Pull requests: Read-only
- Issues: Read-only
- Metadata: Read-only
- Actions: Read-only
これで、PublicリポジトリとPrivateリポジトリ用の2種類のTokenを用意する。
1PasswordのGitHub Pluginではディレクトリごとに、GitHub Tokenを切り替えることができる。
- Public Repository用のTokenを"Use as global default on my system"として設定する
$ op plugin init gh
# PUblic Repository用のTokenを選択
# Use as global default on my system? を選択
- Private Repository用のTokenをPrivate Repositoryのディレクトリで設定する
$ cd private-org
$ op plugin init gh
# Private Repository用のTokenを選択
# "Use automatically when in this directory or subdirectories"を選択
これで、Public RepositoryではPublic Repository用のTokenが、Private RepositoryではPrivate Repository用のTokenが使われるようになる。
どちらのGitHub Tokenもread-onlyなので、Tokenが漏洩しても攻撃のリスクはかなり減る(基本的にリポジトリには、機密情報をそのまま置くことはないので、Privateのコードが漏れるぐらいで済む)
いくつかWrite権限が欲しい操作もある。
- Pull Requestの作成 →
gh pr create --web
で代用 - Pull Request Bodyの更新 → 更新内容をクリップボードにコピペして、Webで更新
- リポジトリの設定更新 → これだけはWrite権限が必要
3のリポジトリの設定更新は、リポジトリ作成時とかメンテナンス時とか特定のタイミングのみでやる操作なので、別途、Write権限を持ったTokenを1Passwordに保存しておいて、必要なときにだけ使うようにする。
function gh-write-task() {
op signin
# GitHubのWrite権限を持ったTokenを使う処理
GITHUB_TOKEN="op://Private/GitHubWriteToken/token" op run -- gh repo edit azu/example --enable-discussions
# 終わったら1passwordからサインアウト
op signout
}
command gh auth login --git-protocol ssh --skip-ssh-key --clipboard --web
を使うことで、GitHub CLIのOAuthのtokenを使う方法も考えたけど、OAuth AppのtokenはrevokeするのがWeb画面のみとなっている(gh auth logout
しても、古いtokenはrevokeされない形になっていた)。
- 10個のToken発行で古いものが消えている、publicに公開すると取り消されるみたいな仕様がある
- Token expiration and revocation - GitHub Enterprise Server 3.16 Docs
GitHub Appのtokenを使う方法はもあるけど、秘密鍵の管理が必要になるのと、複数人で扱うとGitHub Appとしての管理の仕組みが必要になる。
Security Log的には、GitHub App + userとなるので追跡はできそう。
@ghtest-testtestbot @azu
ghtest-testtestbot[bot] – repo.change_merge_setting
Unblocked a merge setting on the azu/example-example repository
1 minute ago
@timestamp 2025-09-21 09:25:47 +0900
_document_id xxx
action repo.change_merge_setting
actor ghtest-testtestbot[bot]
actor_id 233508683
created_at 2025-09-21 09:25:47 +0900
hashed_token xxx=
operation_type modify
programmatic_access_type GitHub App server-to-server token
public_repo true
repo azu/example-example
repo_id 123432456
request_access_security_header nil
request_id 077E:1111:7022A1:9B0DE5:68CF460B
user azu
user_agent GitHub CLI 2.79.0
user_id 19714
visibility public
GitHub Appのinstallation_idが、Organizationごとに異なるIDとなる。 OrganizationごとにGitHub Appをインストールする必要がある。
Organizationごと異なるなら、共通のGitHub Appを使う必要性があまりないような気がする。 共通のGitHub Appにすると、生成されたPrivate Keyの権限が強くなるだけだから、OrganizationごとにGitHub Appを作成した方が良い気もする。
Tokenの有効期限を短くすることでTokenが漏洩した場合のリスクの長期化が減る。
GitHub Personal Access Token (PAT)やOAuthのTokenには、有効期限がとても短いようなToken(10分とか)を扱う実用的な方法が用意されていない。
- Support for short-lived oauth tokens · Issue #5924 · cli/cli
- be able to revoke a token · Issue #2193 · cli/cli
- CLIからOAuth Appをrevokeできたら代用できたが、これも対応されていない。
GitHub AppのTokenを使うことで、Tokenのローテーションと短い時間のTokenを扱うことはできる。
ただし、GitHub Appを管理する必要がある問題と少しハック的な方法になるので、今のところやってない。寿命の短いTokenを作れるという点はGitHub Appの方が良いが、GitHub Appの秘密鍵を持つリスクがあると感じている。