権限を抑制した Fine-grained PAT (Personal Access Token) をつかって HTTPS 経由で github に接続する環境で、平文の PAT をローカルに置かずに、age をつかって暗号化したファイルを参照させる構成案:
概要
~/.gitconfig に下記を書く:
[credential "https://github.com/myorg/"]
helper = ""
helper = "!f() { echo username=mybot; echo password=$(age -d -i ~/.ssh/id_ed25519 ~/.age/myorg.pat.age); }; f"
git で HTTPS リモート url = https://github.com/myorg/myrepot.git を使うとき、 store を credential helper にすると、~/.git-credentials などローカルのファイルに平文の PAT が置く必要があった。
代わりに helper に ! で始まるシェルコマンドを指定することで、git pull・push するときに都度、password (PAT) を復号化して参照してくれる。
age なら既存の SSH 鍵を流用して暗号化できるので、(既に SSH 鍵がある環境なら)GPG などの他の暗号化管理の仕組みを導入する必要がない。
暗号化した PAT は git 管理することもできるし、claude code さんがついついファイルの中身を Read してしまい、平文の PAT がログが流れるような事態も避けられて安心。
手順
Step 0: age をインストール
https://github.com/FiloSottile/age/#installation を参照
# Ubuntu の場合
sudo apt install age
# macOS の場合
brew install age
Step 1: PAT を取得
GitHub の Personal Access Tokens (Fine-grained) を発行する。
git pull・push するには、Contents: Read and write のPermissionsが必要。
Step 2: PAT を age で暗号化
ローカルユーザの SSH 公開鍵を -R (recipient) に指定して、PAT を暗号化したファイル myorg.pat.age を作成する
mkdir -p ~/.age
age -R ~/.ssh/id_ed25519.pub -o ~/.age/myorg.pat.age <<EOT
github_pat_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
EOT
chmod 600 ~/.age/myorg.pat.age
~/.ssh/id_ed25519.pub (公開鍵) で暗号化して、
~/.ssh/id_ed25519 (秘密鍵) で復号化する。
Step 3: .gitconfig に credential helper を追加
[credential "https://github.com/myorg/"]
helper = ""
helper = "!f() { echo username=mybot; echo password=$(age -d -i ~/.ssh/id_ed25519 ~/.age/myorg.pat.age); }; f"
-
myorg⇒ 実際の GitHub の Organization 名 -
mybot⇒ PAT を持つ GitHub アカウント名(bot アカウント名) -
~/.age/myorg.pat.age⇒ Fine-grained PAT 暗号化ファイル
ユーザアカウント全体の PAT でなくて、Organization スコープの PAT なら、もしもの漏洩時の影響を当該 Organization だけに限定できる。仕組み的には、リポジトリを限定した PAT で、リポジトリごとに PAT を切り替える運用も可能。(後述)
Step 4: 動作確認
どの credential が使われるかは、リポジトリ URL ごとに git credential fill で確認できる。
echo "url=https://github.com/myorg/some-repo.git" | git credential fill | sed 's#^\(password=[a-z_]*\).*#\1********#'
期待する出力:
protocol=https
host=github.com
username=mybot
password=github_pat_********
確認ポイント:
-
username=mybotに Step 1 を発行した github ユーザが出てくる -
password=github_pat_...に Step 1 で発行した PAT が出てくる - 末尾に
~/.git-credentials由来の余計な行が混じっていない
通ったら実 push:
cd ~/some-repo # myorg 配下の HTTPS clone 済 repo
git fetch
git push
.gitconfig を逐行で解説
[credential "https://github.com/myorg/"]
-
[credential "<URL>"]で このセクションを「URL がこれにマッチするときだけ」発動にできる - 末尾の
/が必要。末尾の/なしだとmyorg-extraのような他の Organization にも適用されてしまう -
https://github.com/(ORG 指定なし) と書けば全 GitHub に適用、https://github.com/myorg/<repo>まで書けば 1 リポジトリだけに適用
helper = ""
- 継承された helper を一度クリアするためのおまじない
- システム/グローバル設定で
[credential] helper = manager-core(GCM) やosxkeychainが設定されていると、自分の helper の前にそちらが先に動いてしまう - 空文字列を指定するとその時点までの helper チェーンがリセットされ、以降のエントリだけが効く
helper = "!f() { echo username=mybot; echo password=$(age -d -i ~/.ssh/id_ed25519 ~/.age/myorg.pat.age); }; f"
- 先頭
!で「シェルコマンドとして実行」モード - git は
sh -c "<helper> get"のように、末尾にget/store/eraseを渡して呼ぶ。この引数を無視するため、シェル関数f()でラップ →; fで呼び出す。git が渡すgetはf $1になって捨てられる
動作中の流れ
$ git push
│
└─ git の credential subsystem
├─ URL = https://github.com/myorg/myrepo にマッチする helper を探す
├─ helper = "" でチェーンリセット
├─ 自分の helper 関数 f を起動
│ └─ age -d -i ~/.ssh/id_ed25519 ~/.age/myorg.pat.age
│ └─ stdout: github_pat_xxx...
├─ echo username=mybot; echo password=github_pat_xxx... を git に返す
└─ git が HTTPS Basic 認証で github.com に push
PAT 平文は age の stdout に一瞬乗るだけで、ファイルにも環境変数にも残らない。
メリット
- ✅ PAT が平文でディスクに残らない。
~/.age/myorg.pat.ageは age 暗号化済。 - ✅ 追加デーモンなし。helper は git push のたびに起動・終了する
- ✅ SSH 鍵を流用。鍵管理が一元化
- ✅ rotate がファイル差し替えだけ (新 PAT を
age -R ... -o ~/.age/myorg.pat.ageで再暗号化)
デメリット・注意点
- ❌ push のたびに age プロセスが起動する。レイテンシ +20-50ms 程度。普段の操作なら気にならないが、CI で大量に push するなら短期 cache helper との併用を検討
- ❌ passphrase 付き SSH 鍵だと age が対話プロンプトを出す。ssh-agent からは取れないので、passphrase 付きにしたい場合は age の identity を別ファイルに分けるか agent 化する工夫が要る
- ❌ age 復号鍵 (
~/.ssh/id_ed25519) を奪われたら PAT も復号される。SSH 鍵の保護が PAT の保護を兼ねるので、SSH 鍵自体の管理 (chmod 600, パスフレーズ等) は重要
(参考)リポジトリ単位の限定 PAT で一部だけ上書きする
[credential "<URL>"] のマッチングは URL prefix の長さで決まる。これを使うと「普段は Organization PAT、特定の機密リポジトリだけは、限定 PAT を使う」という二層構成が組める。
GitHub の Fine-grained PAT は対象リポジトリを 1 個から選べるので、「最小権限の原則を .gitconfig のレベルで宣言する」運用が可能。
# 普段使い: Organization 全体に効く
[credential "https://github.com/myorg/"]
helper = ""
helper = "!f() { echo username=mybot; echo password=$(age -d -i ~/.ssh/id_ed25519 ~/.age/myorg.pat.age); }; f"
# 特定リポジトリだけ別系統 (より狭いスコープの PAT)
[credential "https://github.com/myorg/sensitive-repo"]
helper = ""
helper = "!f() { echo username=mybot; echo password=$(age -d -i ~/.ssh/id_ed25519 ~/.age/sensitive-repo.pat.age); }; f"
sensitive-repo への push のときは より長い prefix がマッチする方が優先されるので、リポジトリ限定 PAT が使われる。それ以外のリポジトリは Organization PAT に fallback する。