Claude Media
Claude Codeのsandboxから繋がらない — psqlとsshの原因と回避

Claude Codeのsandboxから繋がらない — psqlとsshの原因と回避

sandbox内のpsqlやsshがNetwork is unreachableで失敗するのは、プロキシ変数を読まないため。localhost直結のOS差と、excludedCommandsとaskルールで承認制にする設定例をまとめます。

sandbox内でpsqlやsshがNetwork is unreachableで失敗するのは、sandbox内のコマンドに外へ出る直接の経路がなく、プロキシ環境変数を読まないツールは接続口そのものを持てないからです。ホストをallowedDomainsに足しても直りません。直す手段は、そのコマンドだけをsandboxの外で動かすexcludedCommandsで、askルールと組み合わせれば実行のたびに承認を挟めます。

本記事は、症状のメッセージからどの経路で止まっているかを判別し、localhost直結・DBクライアント・SSHそれぞれの回避を選ぶ流れでまとめます。

症状のメッセージで、止まっている場所を見分ける

同じ「繋がらない」でも、原因は大きく2系統です。プロキシを通るツールがホストの許可で止まっているのか、プロキシを通らないツールが経路ごと持てずに止まっているのか。メッセージの文面で切り分けられます。

症状止まっている場所向かう節
Operation not permitted(macOS)止まっている場所プロキシ非対応ツールの直接接続向かう節DBクライアント・SSHの節
Network is unreachable(Linux・WSL2)止まっている場所同上向かう節DBクライアント・SSHの節
Could not resolve host(macOS)/ Temporary failure in name resolution(Linux・WSL2)止まっている場所同上。名前解決の段階で落ちる向かう節DBクライアント・SSHの節
プロンプトが出る、または403が返る止まっている場所プロキシ対応ツールで、ホストが未許可向かう節allowedDomainsに足す
localhostへの接続だけ失敗止まっている場所直結の可否がOSで違う向かう節localhostの節
resolved to a loopback addressを含む403止まっている場所許可済みの名前が自分のマシンのアドレスを指している向かう節開発用ホスト名の節

この表で見るべきは「許可済みのホストでも失敗するか」です。許可済みなのに失敗するなら、allowedDomainsをいじっても進みません。

なぜ許可済みホストでも繋がらないのか

sandbox内のコマンドには、ネットワークへの直接の経路がありません。Linux・WSL2ではコマンドが別のネットワーク名前空間に入り、手元のネットワークと繋がっていません。macOSではSeatbeltが、sandboxプロキシへの接続以外を既定で遮断します。

外へ出る唯一の道は、Claude Codeが手元で動かすプロキシです。コマンドにはHTTP_PROXY・HTTPS_PROXY・ALL_PROXYなどの環境変数でその場所が伝えられ、プロキシがホスト名ごとに許可・拒否を判定します。

ここで、ツールの作りで運命が分かれます。

くらべる

プロキシ変数を読むツールと読まないツール

接続できる

読むツール

curl、npm、HTTPS経由のgitなど。ホストが許可されれば繋がります。ポートなしのallowedDomainsエントリは、そのホストの全ポートを許可します。

接続できない

読まないツール

素のssh、多くのDBドライバーなど。許可済みのホストでも繋がりません。UDP、QUIC上のHTTP/3、pingのようなICMPもsandboxを出られません。

手元のツールがどちらかを確かめたいときは、sandbox内のシェルでプロキシ変数が見えているかを確認し、ツールのドキュメントでプロキシ設定の項目があるかを見ます。

env | grep -i _proxy

変数が見えていてもツール側が読まなければ意味がありません。DBドライバーの多くは独自のソケットで直接つなぎにいくため、この変数を見ない側に入ります。

localhostのdev serverやDBに繋がらないとき

ホスト側で動いているdev serverや、コンテナ内のDBに、sandbox内のコマンドから直接繋ぐ場合はOSで話が変わります。

  • macOS: sandbox.network.allowLocalBindingをtrueにします。sandbox内のコマンドがポートで待ち受けでき、localhostの任意のポートに接続できるようになります
  • Linux・WSL2: sandbox内のlocalhostは、そのコマンド専用の独立した領域です。コマンド自身が起動したサーバーには届きますが、localhostや127.0.0.1への直接接続はホスト上のサーバーに届きません。allowLocalBindingは効きません

macOSの設定は次のとおりです。

{
  "sandbox": {
    "network": {
      "allowLocalBinding": true
    }
  }
}

trueにすると、localhostで待ち受けている認証なしのサービス(デバッガーなど)を、sandbox内のコマンドが外側から操る足場にできてしまいます。また、非ループバックアドレスで待ち受けたコマンドは、他のマシンからの接続も受けます。キーの扱いとOS差の詳細はsandboxのallowUnixSocketsとallowLocalBindingはmacOS専用にあります。

Linux・WSL2でホストのサーバーに繋ぐテストを動かすなら、そのコマンドをsandboxの外に出すしかありません。allowedDomainsにlocalhostを入れても、直接接続には影響しません。このエントリはプロキシ経由の接続に効くだけで、しかもプロキシを使うコマンドにマシンのlocalhost全ポートを見せることになります。Claude CodeはsandboxのコマンドにNO_PROXYを設定し、localhostへは直接つなぐようにしています。

psqlやsshなど、プロキシ非対応ツールを通す

DBクライアントやSSHは、ホストをallowedDomainsに入れても通りません。通す方法は「そのコマンドをsandboxの外で動かす」だけで、excludedCommandsに書きます。ただし外に出したコマンドはファイルシステムもネットワークも制限なしで動くため、承認の入口を作っておくのが現実的です。

記載の例は、1本のスクリプトを除外しつつaskルールで実行ごとに承認を挟む形です。これをpsql向けに読み替えると、次のようになります(公式の例に沿った書き方です)。

{
  "sandbox": {
    "excludedCommands": ["psql *"]
  },
  "permissions": {
    "ask": ["Bash(psql *)"]
  }
}

askルールを足す理由は、権限ルールの評価順にあります。評価はdeny、ask、allowの順で、最初に当たったものが結果を決めます。askが先に当たるので、他のallowルールが広くてもpsqlの実行は毎回プロンプトになります。

同じ形はスクリプト単位にも使えます。DBへ繋ぐ処理を1本のスクリプトに閉じ込めて、そのスクリプトだけを除外する形です。

{
  "sandbox": {
    "excludedCommands": ["python scripts/load_orders.py *"]
  },
  "permissions": {
    "ask": ["Bash(python scripts/load_orders.py *)"]
  }
}

除外した範囲はそのまま権限の穴になります。ここは気をつけたい点が3つあります。

落とし穴

除外したスクリプトはClaudeが書き換えられる

作業ディレクトリ内のスクリプトを除外すると、Claudeがその中身を編集してから、sandboxの外で実行できます。承認プロンプトが出たときに、コマンド名だけでなくスクリプトの中身も確かめる運用が前提です。パターンは狭く保つほど、外で動かせる範囲も狭くなります。

  • パターンは *で終える: ワイルドカードのないパターンは完全一致です。psqlだけでは、引数なしのpsqlにしか当たりません
  • 呼び出し全体が一致する必要がある: npm ci && psql ...のように複数のコマンドを繋ぐと、npm ciも除外に当たらない限りsandbox内のままです
  • 一部の形は除外されても内側に残る: cdを含む呼び出し、コマンド置換$(...)、ファイルへのリダイレクトなどです。cd app && psql ...は、psql *を書いても除外されません

除外パターンの書き方と効かないケースの全体像は、sandbox.excludedCommandsでdockerなど非対応コマンドを対象外にすると効かない3つの原因で扱っています。

SSHとgit over SSHの違い

素のssh、scp、rsyncover SSHは、DBクライアントと同じ理由で失敗します。gitのSSHリモートだけは事情が違います。Claude Codeがgitの接続をsandboxプロキシ経由でトンネルするためです。

環境git fetch / pull / push(SSHリモート)対処
Linux・WSL2git fetch / pull / push(SSHリモート)ホストを許可すれば動く対処ポート22の許可、社内プロキシのポート22許可、鍵ファイルを読めること
macOSgit fetch / pull / push(SSHリモート)ホストを許可しても失敗対処リモートをHTTPSに替える、またはgit fetch *などをexcludedCommandsへ

macOSでgit over SSHが通らないのは、トンネルがプロキシに認証できないためです。HTTPSに替える場合は、パーソナルアクセストークンなどの認証情報が要ります。

git remote set-url origin https://git.example.com/example-org/example-repo.git

SSHのままにするなら、gitのネットワークコマンドだけを除外します。

{
  "sandbox": {
    "excludedCommands": ["git fetch *", "git pull *", "git push *"]
  }
}

除外されたgitコマンドはallowedDomainsの外のホストにも届く点が代償です。cdやgit -C、コマンド置換を足した呼び出しは内側に残ります。

Linux・WSL2で動かないときの確認先は3つです。ホストがポート22で許可されているか(ポートなしのallowedDomainsエントリで足ります)、社内プロキシがポート22を通すか、そして鍵がファイルとして読めるかです。sandboxはssh-agentのソケットを遮断することがあり、denyReadやcredentialsで~/.sshを隠していると鍵ファイルも読めません。

Claude自身が「sandbox外で再試行」を提案する場合

除外を書く前でも、Claudeはsandboxで失敗したコマンドをdangerouslyDisableSandboxパラメーターで再試行しようとすることがあります。承認の扱いは権限モードで分かれます。

  • bypassPermissionsモード: プロンプトなしで実行されます
  • ManualモードとacceptEditsモード: 「Bash command (unsandboxed)」というタイトルのプロンプトが出ます
  • Autoモード: 別の分類器モデルが元のコマンドを評価します
  • dontAskモード: 再試行は拒否されます

Bash(curl *)のようなallowルールが当たるコマンドは、再試行も無プロンプトで外に出ます。再試行を毎回止めたいなら、Bash(dangerouslyDisableSandbox:true)のaskルールを足します。このルールはAutoモードやbypassPermissionsモードでもプロンプトを出し、一致するallowルールより優先されます。再試行そのものをやめたい場合はallowUnsandboxedCommandsをfalseにします。この場合、Claudeが外で動かせるのはexcludedCommandsに一致するコマンドだけになります。

つまり、DBやSSHの実行経路を設計するときは2つの口が開いています。excludedCommandsで決めた通り道と、Claudeが失敗から提案する再試行です。前者をaskで承認制にし、後者をallowUnsandboxedCommands: falseで閉じる構成にすれば、「外に出る道は書いたものだけ」にできます。管理者がこのロックをかけた場合、リポジトリの.claude/settings.jsonにあるexcludedCommandsは無視される点にも注意が要ります。

許可したはずの開発用ホスト名が403になるとき

myapp.testのように127.0.0.1を指す開発用の名前をallowedDomainsに入れても、プロキシは名前の解決先がループバックだと接続を拒否します。コマンドには、Connection to myapp.test blocked: resolved to a loopback addressのような本文の403が返ります。同じ検査は169.254.169.254(クラウドのメタデータエンドポイント)のようなリンクローカルアドレスや、自マシンに割り当てられたアドレスにも掛かります。localhostと*.localhostだけは、ループバックへの解決が許されています。

回避は、名前が指すIPアドレスをポート付きで並べて許可する形です。

{
  "sandbox": {
    "network": {
      "allowedDomains": ["myapp.test:3000", "127.0.0.1:3000"]
    }
  }
}

ポートを書かないIPアドレスのエントリは、そのアドレスで待ち受ける全サービスに届いてしまいます。ポートは必ず絞ります。なお、この検査はv2.1.284より前には効いておらず、プロキシは許可済みの名前の解決先へそのまま接続していました。古いバージョンで通っていた構成が更新後に403になる場合は、この変更が原因です。社内の上流プロキシ経由の接続は、名前解決を上流側が行うため検査の対象外です。

経路の選び方

最後に、どの症状でどの手を打つかを並べます。

状況取る手入口の締め方
プロキシ対応ツールで403・プロンプト取る手allowedDomainsに追加入口の締め方strictAllowlistでプロンプトではなく拒否に
macOSでlocalhost直結取る手allowLocalBinding: true入口の締め方認証なしのローカルサービスの有無を先に確認
Linux・WSL2でホストのサーバーに直結取る手該当コマンドをexcludedCommandsへ入口の締め方askルールで実行ごとに承認
DBクライアント・素のssh・UDP取る手該当コマンドをexcludedCommandsへ入口の締め方askルール+スクリプト単位の除外
開発用ホスト名が403取る手名前とIPをポート付きで許可入口の締め方ポートを必ず指定

DBクライアントのような「外に出すしかない」ツールが複数あるなら、1本ずつ除外するより、DB操作をスクリプトに寄せて、そのスクリプトだけを承認制の除外にするほうが穴は小さくなります。除外するものが減るほど、承認プロンプトで中身を確認する回数も減らせます。

社内の検査プロキシを通す必要がある環境では、httpProxyPortとsocksProxyPortによる構成も選択肢です。httpProxyPortとsocksProxyPortで社内検査プロキシを通す設定に手順があります。

この記事を共有:XはてブLinkedIn