Claude CodeがUser-Agentに自分のメールアドレスを入れるのを、どう止めるか?

コードを読まないAIエンジニア
サムネイル

Claude Codeが外部のAPIを叩くとき、User-Agentヘッダーにサインイン中のアカウントのメールアドレスを入れて送っていた件が、2026年11月5日にGitHubのissueで報告されました。止め方は、組み込みの対策がどこまで見ているかを知ることと、hooksで自分のアドレスを含むツール呼び出しを止めることの2つです。11月7日時点で根本の修正はまだリリースされていないので、手元に止める仕組みを1つ足しておく価値があります。

Claude CodeはなぜUser-Agentにメールアドレスを入れるのか?

User-Agentは、HTTPリクエストで「誰が、どのツールで叩いているか」を名乗るヘッダーです。サイトによっては、ここに連絡先を書くよう求めています。

Wikipediaを運営するWikimediaのUser-Agentポリシーは、curl のような汎用の名前を使わず、ツール名と連絡先を入れるよう求めています。例として CoolBot/0.0 (https://example.org/coolbot/; coolbot@example.org) generic-library/0.0 が載っていて、連絡先のないスクリプトは予告なくブロックされることがある、と書かれています。米国の企業開示データベースのSEC EDGARも、Sample Company Name AdminContact@<sample company domain>.com の形で会社名とメールを名乗るよう求めています。

エージェントから見ると、「連絡先を入れないと弾かれる」という決まりが目の前にあり、手元には使えそうなメールアドレスがある状態です。issueに寄せられた報告では、Wikimediaのほか、ジオコーディングのAPIや論文検索のAPIを叩くcurlに、アカウントのアドレスが入っていました。gitの設定(user.email)から読んで送った、という報告もあります。

では、なぜcontextにメールがあったのか。issue内で引用された開発側の説明では、Claudeが @author タグなどに名前を作り話で入れる問題への対策として、サインイン中のユーザーのメールをcontextに渡していたそうです。決まりに忠実に従おうとして、手近なアドレスを埋めてしまったわけです。

自分のメールアドレスは、もう送られていたのか?

Claude Codeは会話の記録を、~/.claude/projects/ 配下にjsonlファイルで残しています。ここを自分のアドレスで検索すれば、過去のツール呼び出しにアドレスが入っていたかが分かります。

# you@example.com を自分のアドレスに置き換えて、自分のターミナルで打つ
grep -rliF 'you@example.com' ~/.claude/projects --include='*.jsonl'

Claude Codeに頼まず、自分のターミナルで打つのがポイントです。頼むと検索語としてアドレスがコマンドに入り、会話の記録にもう一度残ります(このあと紹介するhookにも止められます)。

ファイルが見つかったら、中の User-Agent、curl -A、-H の前後を見ます。git logの出力など、送信と関係のない記録も混じるからです。issueには、自分の記録を走査して、8月9日から11月1日までの8セッションで53回、User-Agentにメールが入っていたという報告もありました。

cc-plugin-privacyが見る呼び出し、見ない呼び出し

Anthropicは2.1.317で、Claude Codeに同梱する組み込みのmod(プラグイン)として、cc-plugin-privacyを入れました。CHANGELOGには「アカウントのメールアドレスをネットワークに送るWebやシェルのツール呼び出しの前に、確認するチェックを追加した」とあります。

ところがREADMEを読むと、止めるのは「アドレス」と「User-Agentの指定」が1つの呼び出しにそろったときだけです。README自身が「配慮であって境界ではない」と書いています。

呼び出しの形
cc-plugin-privacy
Bash、PowerShell、Monitorで、アドレスとUser-Agentの指定が同じ呼び出しにある
拒否する
アドレスはあるが、User-Agentの語がない(URL、body、curl -u、From: ヘッダー)
見ない
実行時に作る値($(git config user.email))、変数、ファイル経由
見ない
アドレスを書き込んだスクリプトを実行する
見ない
WebFetch、WebSearch、MCPのツール
見ない

挙動の説明も、一次情報の間でそろっていません。READMEは「確認を出さずに拒否する」、CHANGELOGは「送る前に確認する」と書いています。issueには、READMEの想定なら拒否されるはずの echo 'User-Agent: test <アドレス>' > /dev/null が通った、という報告がmacOSとWindowsの両方から出ています。

あなたの手元のClaude Codeは、どの版でしょうか? claude --version で2.1.317以降かを見て、/plugins の一覧にbuiltinとして出ているかを確かめます。開発側の説明では、stableチャンネルは2.1.311のままで、mod自体が入っていません。とはいえ、一覧に出ていれば安全とも言えないのが現状です。

hooksで、メールアドレスを含むツール呼び出しを止める

hooksは、Claude Codeがツールを使う前後に、自分のスクリプトを差し込める仕組みです。ツール実行の直前に呼ばれるPreToolUseでは、ツール名と引数(tool_input)がJSONで標準入力に届きます。スクリプトが終了コード2で終わるとその呼び出しは止まり、stderrに書いた理由がClaudeに返ります。

ここがhooksのいいところで、見る場所を自分で決められます。Bashのコマンドに加えて、Writeで書き込む中身、Editで差し込む文字列、WebFetchのpromptまで見られます。

止めるスクリプトの全文

OWN に自分のアドレス、ALLOW に外に出してよい専用の連絡先を入れます(下はダミーです)。jq を使うので、入っていなければ先に入れておきます。

#!/bin/bash
# ~/.claude/hooks/block-own-email.sh(v2: git の user.email を読むコマンドも止める)
OWN='you@example.com'
ALLOW='contact@example.org'
input=$(cat)
text=$(jq -r '.tool_input | [.command, .content, .new_string, .url, .prompt] | map(select(. != null)) | join("\n")' <<<"$input")
text_lc=$(tr 'A-Z' 'a-z' <<<"$text")
if grep -qiF "$OWN" <<<"$text_lc" && ! grep -qiF "$ALLOW" <<<"$text_lc"; then
  echo "自分のメールアドレスを外に出す可能性があります。専用の連絡先に置き換えてください" >&2
  exit 2
fi
if grep -qE 'user\.email' <<<"$text_lc"; then
  echo "git の user.email を読もうとしています。外部に送る連絡先には専用のアドレスを使ってください" >&2
  exit 2
fi
exit 0

中身は単純で、tool_input から command、content、new_string、url、prompt を集めて小文字にそろえ、自分のアドレスがあって専用の連絡先がなければ止めます。加えて、user.email を読もうとするコマンドも止めます。stderrのメッセージはClaudeに返るので、専用の連絡先で書き直す手がかりになります。

settings.jsonへの登録

公式リファレンスの書式では、次のように書きます。全プロジェクトに効かせたいので、置き場所はユーザーの ~/.claude/settings.json です。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash|PowerShell|Write|Edit|WebFetch",
        "hooks": [
          {
            "type": "command",
            "command": "/Users/yourname/.claude/hooks/block-own-email.sh",
            "onFailure": "block"
          }
        ]
      }
    ]
  }
}

command はスクリプトの絶対パスにして、chmod +x で実行権を付けておきます。onFailure: "block" は、hook自体が失敗したとき(jq が無い、など)に呼び出しを止める側に倒す設定で、2.1.295以降で使えます。既定の continue のままだと、hookが落ちたときは素通りです。登録できたかは /hooks で一覧できます。

なお、このスクリプトはbash前提です。WindowsでPowerShellのツールまで見るなら、PowerShell版が要ります。

8通りの入力で確かめた結果

使い捨てのコンテナ(node:22-bookworm、jq 1.6)で、公式リファレンスのPreToolUseの入力JSONを模した8通りを、スクリプトに標準入力で渡して終了コードを見ました。v1は、user.email の判定を足す前の版です。

入力
v1
v2
1. Bashの curl -A "site-build/1.0 (you@example.com)"
止まる
止まる
2. User-Agentヘッダーに大文字まじりの You@Example.COM
止まる
止まる
3. 専用の連絡先 contact@example.org を入れたcurl
通る
通る
4. アドレスなしのcurl
通る
通る
5. Writeでスクリプトに "User-Agent": "bot/1.0 (you@example.com)" を書く
止まる
止まる
6. WebFetchのpromptにアドレスが入る
止まる
止まる
7. U=you; D=example.com と分けて $U@$D で組み立てる
通る
通る
8. $(git config user.email) で読んで入れる
通る
止まる

8通りのうち、v1で止まったのは5通り、v2では6通りです。専用の連絡先に置き換えた3は通るので、連絡先が必須のAPIを使う作業は続けられます。

個人的に一番効くと思うのは5です。スクリプトにアドレスを書いてから python3 fetch.py で実行する経路は、cc-plugin-privacyが見ていません。hookなら、ファイルに書き込む時点で止められます。

hooksでも止まらない書き方

7のように、変数で分割して組み立てられると、文字列の一致では止まりません。hookはよくある経路をふさぐ網であって、すべてを防ぐ壁ではないです。

スクリプトを読むと、ほかにも穴が見えます。

  • 1つの呼び出しに、本アドレスと専用の連絡先が両方入っていると通る
  • base64やURLエンコードしたアドレスは見ない
  • MCPのツールは matcher に入れていないので見ない。入れる場合も、スクリプトが読むフィールド名を合わせて直す必要がある
  • git config user.email で設定を確かめるだけのコマンドも止まる

「いっそcurlを丸ごと禁止すれば?」と思いますよね。permissionsのdenyルールで書けますが、公式のpermissionsのドキュメントによると、denyルールはフルパスで呼んだ同じプログラムや sh -c の中には当たりません。そもそもdenyルールが見るのはコマンドの形で、メールアドレスという中身では止められないんです。

普段の作業で、アドレスが一番通りやすい出口はどこでしょうか? MCP経由でHTTPを叩くツールを毎日使っているなら、そこを先に塞ぐのがよさそうです。

連絡先が必須のAPIには、専用のメールアドレスを渡す

止めるだけだと、連絡先を求めるAPIを使う作業が進みません。外に出してよい連絡先を先に決めて、Claude Codeに渡しておきます。

Wikimediaは、連絡先にメールのほか、WebサイトのURLやwikiのユーザー名((<project>; User:<name>) の形)を認めています。メールを出したくなければ、ツールの説明ページのURLで足ります。SEC EDGARは会社名とメールの形を求めるので、そのAPI用に転送アドレスを1つ作って渡すのが無難です。

決めた連絡先は、hookの ALLOW に入れ、CLAUDE.mdにも書いておきます。

## 外部APIの連絡先
- User-Agentなどで連絡先を求められたら、contact@example.org を使う
- アカウントのメールアドレスやgitのuser.emailは、外部に送らない

ただ、CLAUDE.mdの一文に強制力はありません。公式のドキュメントも、CLAUDE.mdは「Claudeが試すことを方向づけるが、境界を強制しない」と書いています。CLAUDE.mdで入れにくくして、入ったらhookで止める。私はこの2段で持っておくのが現実的だと考えています。

contextからメールアドレスが消えても、hooksを残す理由

開発側は、モデルに渡すcontextからアカウントのメールアドレスを外す、次のリリースは来週前半の見込み、と説明しています。11月7日時点のCHANGELOGの先頭は2.1.319で、該当する記載はまだありません。

消えたあとも、gitの user.email やコミット履歴のように、手元で読めるアドレスは残ります。hookはcontextの中身に関係なく出口で見るので、修正版が出ても外す理由はありません。

今日やることは4つです。

  1. 自分のターミナルで、~/.claude/projects/ を自分のアドレスで検索する
  2. claude --version と /plugins で、cc-plugin-privacyが入っているかを見る
  3. hookのスクリプトの OWN を自分のアドレスにして、~/.claude/settings.json に登録する
  4. 連絡先が必須のAPI用に専用の連絡先を決め、ALLOW とCLAUDE.mdに書く

モデルの気の利かせ方は、版が変わるたびに変わります。止める場所を自分の手元に1つ持っておけば、次のアップデートで挙動が変わっても、送る前に気づけます。