本記事は Ubuntu 24.04 を前提としています。
きっかけ
git remote -v を眺めていたら、Personal Access Token (PAT) が remote URL に丸ごと埋め込まれている状態になっていました。
origin https://[email protected]/USER/REPO.git (fetch)
origin https://[email protected]/USER/REPO.git (push)
.git/config はリポジトリに含まれないため「push しても外には出ない」ものの、ローカルで露出する経路は意外と多くあります。
- シェル履歴 (
~/.bash_history~/.zsh_history) - エディタや CI のログ、画面共有・スクリーンショット
- うっかり配布した dotfiles リポジトリ
- マルウェアやリモートアクセスで
.git/configが読まれる
しかも classic PAT はスコープが粗く、repo 権限があれば private リポジトリ全体の閲覧・書き換え、強制 push による履歴破壊、GitHub Actions secrets の窃取まで可能になります。攻撃者は奪った瞬間には何もせず静かに watch することもできるため、「使われた形跡がない」では安心できません。
これを機に、PAT を URL に埋めず GitHub CLI (gh) の credential helper に任せる構成に切り替えました。
ゴール構成
- remote URL は素の
https://github.com/USER/REPO.git - 認証は
ghが credential helper として処理 (トークンは OS のセキュアストア管理) - devcontainer を作り直しても再ログイン不要
手順
1. 露出したトークンを revoke (最優先)
remote URL を書き換える前に、まず GitHub 側でトークンを無効化します。
- classic PAT: https://github.com/settings/tokens
- fine-grained PAT: https://github.com/settings/personal-access-tokens
該当トークンを Delete した瞬間に無効になります。順序として これを先にやる ことが大切です。後の手順を待っている間に古いトークンが他経路で使われるリスクを断ちます。
2. gh をインストール
devcontainer 環境なら ghcr.io/devcontainers/features/github-cli feature で gh がすでに入っています。
ホスト (Ubuntu 24.04) に入れる場合は、以下の記事を参考にしてください。
3. gh auth login で認証
gh auth login
対話で以下のように選びます。
? Where do you use GitHub? GitHub.com
? What is your preferred protocol for Git operations? HTTPS
? Authenticate Git with your GitHub credentials? Yes
? How would you like to authenticate GitHub CLI? Login with a web browser
Authenticate Git with your GitHub credentials? → Yes が肝で、これが git config --global credential.helper を gh 経由に書き換えてくれます。以後 git push のたびに gh が裏でトークンを取り出して認証してくれます。
devcontainer のようなヘッドレス環境ではブラウザは自動で開きません。表示される 8 桁のワンタイムコードをコピーし、ホスト側のブラウザで https://github.com/login/device に貼って承認します。
✓ Authentication complete.
✓ Logged in as USER
4. remote URL からトークンを除去
git remote set-url origin https://github.com/USER/REPO.git
git remote -v
トークン部分が消えていれば OK です。git fetch origin がパスワードを聞かれずに通れば認証は機能しています。
5. シェル履歴の残骸を確認
revoke 済みでも、ローカル履歴に残ったトークン文字列は気持ちが悪いものです。
grep -rn 'ghp_' ~/.bash_history ~/.zsh_history 2>/dev/null
ヒットしたら該当行を削除します (history -d <行番号> でメモリ上を消した後、ファイルも編集しておきます)。
devcontainer での認証共有
gh の認証情報は ~/.config/gh/hosts.yml (と OS のセキュアストア) に保存されます。devcontainer をリビルドすると ~/.config/gh が消えて再ログインが必要になるため、ホストの設定を bind mount で共有します。
.devcontainer/devcontainer.json:
"mounts": [
"source=${localEnv:HOME}/.claude,target=/home/vscode/.claude,type=bind,consistency=cached",
"source=${localEnv:HOME}/.claude.json,target=/home/vscode/.claude.json,type=bind,consistency=cached",
"source=${localEnv:HOME}/.config/gh,target=/home/vscode/.config/gh,type=bind,consistency=cached"
]
前提として ホスト側に ~/.config/gh ディレクトリが存在している必要があります (なければ bind mount に失敗します)。ホストにも gh を入れて gh auth login 済みにしておくのが一番シンプルです。事情があってホスト側で gh を使わない場合でも、空ディレクトリだけ作っておけばマウント自体は通ります。
mkdir -p ~/.config/gh # ホスト側で実行
設定変更後は VSCode の Dev Containers: Rebuild Container でリビルドすると反映されます。
補足: SSH 鍵にしないのか
SSH 鍵認証もアリですが、
- 鍵ファイル自体の管理 (
~/.sshのパーミッション、複数マシン間の同期) が発生します - GitHub Actions など他用途で
ghをどのみち使うことが多いです
そのため、個人開発の HTTPS なら gh auth login ひとつに寄せた方がメンテが楽だと感じています。複数アカウントの切替も gh auth switch で済むのが良いところです。
まとめ
- PAT を URL に埋めるのは「公開リポジトリに含まれない」だけでローカル経路の露出リスクは大きいです。露出に気付いた時点で revoke が最優先となります。
gh auth loginで credential helper を任せれば URL からトークンを排除でき、トークンは OS のセキュアストアに隔離されます。- devcontainer ではホストの
~/.config/ghを bind mount しておくと、リビルドのたびに再ログインしなくて済みます。