Claude Codeでssh configを整える — ProxyJumpと鍵を渡さない運用
~/.ssh/configをホスト別に書き、ProxyJumpで踏み台を通し、秘密鍵はReadの拒否ルールとsandboxのcredentialsで隠す。Claudeにはホスト名だけを渡す設定例をまとめます。
Claude Codeにサーバー作業を任せるとき、鍵の中身を見せる必要はありません。~/.ssh/configにホスト別の設定を書いて、Claudeにはssh app-stgというホスト名だけを渡す。鍵ファイルはReadの拒否ルールで隠す。この分担にすると、踏み台(ProxyJump)を挟んだ接続も、鍵を渡さずに動かせます。
本記事は、configの書き方、踏み台の設定で踏みやすい点、鍵を読ませない拒否設定の順にまとめます。
Claudeに渡すのはホスト名だけにする
SSHの接続先、ユーザー、鍵、踏み台は、すべて~/.ssh/configのHostブロックに寄せられます。ここに寄せておけば、Claudeが打つコマンドはssh app-stg uptimeのように短くなります。コマンドラインに-i ~/.ssh/id_ed25519_appのような鍵のパスを書かせる必要がなくなり、許可ルールも書きやすくなります。
役割は次のように分かれます。
| 置き場所 | 持たせるもの | Claudeに見せるか |
|---|---|---|
~/.ssh/config | 持たせるものホスト名、ユーザー、踏み台、鍵のパス | Claudeに見せるか見せてよい(秘密ではない) |
~/.ssh/keys/ | 持たせるもの秘密鍵そのもの | Claudeに見せるか見せない(Read拒否) |
~/.claude/settings.json(ユーザー設定) | 持たせるもの拒否・確認のルール | Claudeに見せるか常に読まれる側 |
configには鍵の「パス」しか書かれません。パスは秘密ではないので、Claudeが読めても困りません。隠すのは鍵ファイルの中身だけです。
ホスト別のconfigと、ProxyJumpの書き方
踏み台(bastion)を1台挟み、その奥にステージング用のappサーバーがある構成を例にします。ホスト名とIPは例示用の値です。
# ~/.ssh/config
Host bastion
HostName bastion.example.com
User deploy
IdentityFile ~/.ssh/keys/id_ed25519_bastion
IdentitiesOnly yes
Host app-stg
HostName 10.0.1.20
User deploy
ProxyJump bastion
IdentityFile ~/.ssh/keys/id_ed25519_app
IdentitiesOnly yes
Host *
ServerAliveInterval 30ポイントは3つです。
ProxyJump bastionは、bastionへの接続を作り、そこから最終の宛先へTCP転送する指定です。user@host:portの形でも書け、カンマ区切りで複数の踏み台を順に通せます。値をnoneにすると無効になります- 踏み台自身の設定(
UserやIdentityFile)は、宛先ホストのブロックから引き継がれません。踏み台用のHost bastionブロックを別に書く必要があります IdentitiesOnly yesを付けると、ssh-agentが多数の鍵を持っていても、configで指定した鍵だけを提示します
Host *のような広い指定は末尾に置きます。configは各項目で最初に得られた値が使われるため、広い指定を先頭に置くと、後ろの個別指定が効かなくなります。
解決結果はssh -Gで確かめる
書いたconfigが意図どおりに解決されるかは、接続せずにssh -Gで確認できます。-Gは、HostとMatchを評価したあとの設定を表示して終了するオプションです。上のconfigを一時ファイルに置いて、OpenSSH 10.2p1で実行した結果が次です。
ssh -F ./cfg -G app-stg | grep -i -E '^(hostname|user|proxyjump|identityfile) 'user deploy
hostname 10.0.1.20
identityfile ~/.ssh/keys/id_ed25519_app
proxyjump bastion踏み台側も同じ要領で見ます。bastionを指定すると、hostname bastion.example.comと踏み台用の鍵が出て、proxyjumpの行は出ませんでした。踏み台が踏み台を持たないことが、この出力から読み取れます。
この確認はClaudeに任せられます。ssh -Gは設定の解決結果を出すだけで、鍵ファイルの中身は出力に含まれません。「新しいHostブロックを足して、ssh -Gでproxyjumpとidentityfileを確認して」と頼めば、鍵を開かずに検証まで進められます。
鍵の中身をClaudeに読ませない
鍵を隠す層は2つあり、守る範囲が違います。
Readの拒否ルールで、ファイルツールを止める
permissions.denyにReadルールを書くと、Claudeのファイルツールからその鍵が読めなくなります。
{
"permissions": {
"deny": [
"Read(~/.ssh/keys/**)"
]
}
}~/で始まるパスはホームディレクトリからの指定です。この拒否は、次の範囲に及びます。
Readの拒否は、同じパスへのEditとWriteも止めます。新しいファイルをそこに作ることも含みますcat、head、tail、sed、teeなど、Claude Codeが認識するファイル系のBashコマンドと、<などのリダイレクト先にも適用されます- 鍵へのシンボリックリンクも拒否されます。拒否ルールは、要求されたパスと解決先のどちらかが一致すれば効きます。
./project/keyから~/.ssh/id_rsaへのリンクを使った例が、そのまま記載されています
一方で、効かない場面があります。grep -r pattern .のようにファイルを名指ししないコマンドや、PythonやNodeのスクリプトが自分でファイルを開く場合は対象外です。ファイルツールの拒否は、全プロセスを止める壁ではありません。
sandboxのcredentialsで、コマンド側も止める
OSレベルで止めたいなら、sandboxのcredentialsに鍵のディレクトリを入れます。
{
"sandbox": {
"enabled": true,
"credentials": {
"files": [
{ "path": "~/.ssh/keys", "mode": "deny" }
]
}
}
}mode: "deny"のエントリは、sandbox内のコマンドからの読み取りを拒否します。filesystem.denyReadと同じ制限です。スクリプトが鍵を開こうとしても、sandbox内なら失敗します。
Readの拒否ルールは、Claude CodeがsandboxのdenyReadにも加えます。一方、sandboxのdenyReadだけでは、組み込みのReadツールは止まりません。両方を書くのは冗長ではなく、止める対象が違うからです。
鍵を隠したまま、sshは動かす
ここまで隠すと、疑問が出ます。Claudeが鍵を読めないなら、sshは動くのでしょうか。
動きます。sshが鍵を開くのは、Claudeではなくsshプロセスです。Readの拒否はファイルツールと認識されたコマンドを止めるだけなので、ssh app-stgの中での鍵の読み取りは止まりません。
ただし、sandboxを有効にしている場合は話が別です。素のsshはプロキシ変数を読まないため、sandbox内では許可済みのホストでも失敗します。経路の作り方はsandboxから繋がらない原因と回避にまとまっています。gitのSSHだけがmacOSで落ちる件はgitのSSH接続がmacOSのsandboxで失敗する件で扱っています。
sshをexcludedCommandsでsandboxの外に出すなら、代償を理解しておく必要があります。excludedCommandsに入れたコマンドは、ファイルシステムの制限もネットワークプロキシも受けずにsandboxの外で動くため、allowedDomainsの制限も及びません。そこで、askルールで実行のたびに承認を挟む形が安全です。
{
"permissions": {
"ask": ["Bash(ssh *)", "Bash(scp *)"]
},
"sandbox": {
"excludedCommands": ["ssh *", "scp *"]
}
}この組み合わせでは、sshは承認のうえでsandbox外で動き、鍵を読めます。それ以外のsandbox内のコマンドは、credentialsの拒否で鍵に触れません。
拒否ルールの文面にも限界があります。
Bash(ssh *)は、Claudeが書いたsshのコマンド文に一致するルールです。/usr/bin/ssh ...やsh -c 'ssh ...'のような別の形は、同じルールでは止まりません。強制力が要る部分は、sandboxやPreToolUseフックで担保します。
承認を省きたい操作だけを限定する
毎回の承認が重いなら、読み取り専用の確認用コマンドだけをallowに回す手があります。ただし、permissionsのルールはdeny、ask、allowの順に評価され、最初に一致したルールで結果が決まります。具体的なルールでも順序は変わらないため、前の節のaskにBash(ssh *)を置いたままだと、allowに足したルールも毎回確認に回ります。承認を省くなら、askのほうを本番に限ったルールへ狭めます。
{
"permissions": {
"ask": ["Bash(ssh app-prod *)", "Bash(scp *)"],
"allow": ["Bash(ssh app-stg uptime)", "Bash(ssh -G *)"]
},
"sandbox": {
"excludedCommands": ["ssh *", "scp *"]
}
}この構成では、ssh app-prodで始まるコマンドは常に確認を挟み、ssh app-stg uptimeとssh -Gは確認なしで通ります。どちらのルールにも一致しないssh(たとえばssh app-stg rm -rf /tmp/x)は、ルールでは決まらず、そのときの権限モードの扱いになります。*を含まないルールは、そのコマンドと完全に一致したときだけ通ります。ssh app-stg uptimeは通り、ssh app-stg uptime --fooは通りません。許可を広げるときは、ssh app-*のようにホスト側でなく、実行するコマンドの側で絞るほうが、意図しない接続先を許しにくくなります。
踏みやすい点
落とし穴
踏み台のブロックを書かず、鍵が合わない
ProxyJump bastionと書いただけで、宛先のUserやIdentityFileが踏み台にも使われると思いがちです。実際には、宛先の設定は踏み台に適用されません。踏み台専用のHost bastionブロックに、UserとIdentityFileを書きます。
落とし穴
IdentitiesOnlyを外すと、agentの鍵を順に試す
IdentitiesOnlyがno(既定)のままだと、ssh-agentが持つ鍵もすべて提示の候補に入ります。agentに多数の鍵があるときに、意図しない鍵まで提示されるのを避けるためのオプションです。ホストごとに鍵を決めたいときはIdentityFileとIdentitiesOnly yesをセットで書きます。
ForwardAgentは、踏み台にyesで付けないほうが無難です。転送されたagentに接続できる権限を持つ相手は、鍵そのものは取れなくても、agentに載っている鍵での認証操作はできてしまいます。Claudeに鍵を渡さない運用なら、踏み台の先でも鍵を使わせない設計に寄せるほうが一貫します。
パスフレーズ付きの鍵を使う場合、sshがBashツールの中でパスフレーズを要求して画面が崩れる報告があります。ssh-agentに先に鍵を載せておく回避策はsshでpassphraseを聞かれて固まる原因と回避策にあります。
CLAUDE.mdに添える運用メモ
拒否ルールは強制力を持ちますが、Claudeが迷わず動くには、使ってよいホストの一覧も要ります。configを読ませたうえで、プロジェクトのCLAUDE.mdに次のような断片を置いておく方法があります。
## SSH
- 接続先は ~/.ssh/config の Host 名だけを使う(app-stg / app-prod)
- 新しい Host を足したら `ssh -G <host>` で proxyjump と identityfile を確認する
- 鍵のパス指定(-i)や ssh-add は使わない
- 本番(app-prod)への接続は、実行前に目的を1文で説明するCLAUDE.mdは、Claudeの動き方を方向づけるメモです。禁止の強制は拒否ルールとsandboxが担い、メモは「どのホストをどう使うか」を揃える役に回すと、役割が混ざりません。
configが長くなる場合は、Includeで別ファイルに分けられます。複数のパス指定とワイルドカードに対応し、案件ごとにホスト定義を分けておくと、Claudeに見せる範囲も分けやすくなります。
設定をどの階層に置くか
ここまでのpermissionsとsandboxは、個人のユーザー設定に置くのが基本です。理由は、~/で始まるパスがホームからの指定で、どのプロジェクトでも同じ鍵を守れるからです。
/pathのように1つのスラッシュで始めたパスは、設定ファイルの位置を基準にします。ユーザー設定のRead(/secrets/**)は~/.claude/secrets/**を指し、プロジェクトのsecretsではありません。ホーム直下の鍵を守る目的なら~/か//の絶対パスを使います。
複数人のチームでホスト一覧を配る用途なら、Claude DesktopのSSH接続を配る仕組みもあります。sshConfigsでチーム共通のSSH接続を配る手順を参照してください。Claude Code自体をリモートで動かす設定はClaude Code SSH接続ガイドに、設定ファイル全体の位置付けはsettings.jsonの完全ガイドにあります。
まとめ
configには「どこへ、誰として、どの鍵のパスで」を書き、鍵の中身は~/.ssh/keys/に集めてReadの拒否とsandboxのcredentialsで隠す。sshの実行はaskで承認制にし、sandbox内で失敗するならexcludedCommandsで外に出す。この三段で、踏み台を含む接続を保ったまま、Claudeに渡る情報をホスト名とパスだけに絞れます。拒否ルールの文面は別の呼び方で迂回できるため、鍵を守る本体はsandboxのほうに置くのが筋です。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。