Claude CodeのUser-Agentにメールが載る問題 — 確認と防ぎ方
Claude Codeのセッションで、モデルが組んだcurlのUser-Agentにアカウントのメールが入る報告が続いています。起きる仕組みと、履歴での確認方法、hookによる止め方をまとめます。
Claude Codeが自分でUser-Agentヘッダーにメールアドレスを載せているわけではありません。モデルが書いたcurlコマンドの -A オプションに、セッションの文脈にあるアカウントのメールが入り込んでいます。送信先はcrates.ioやWikimedia Commonsのような、連絡先の記載を求める公開サービスです。
この報告はGitHubのissue #95258に2026年9月18日から集まり、10月14日までに7件のコメントがつきました。issueは10月14日のコメント時点でopenです。ここでは、報告された内容、自分の履歴を調べる方法、hookで送信前に止める設定を順に説明します。
どこから外部に出ているのか
流れは次のとおりです。
メールが外部サービスに届くまで
- 1
アカウントのメールがモデルの文脈に入る
セッション開始時の文脈に
userEmailというブロックが入り、「識別目的のみ。リクエストヘッダーなどで無関係なサービスに送らない」という注記が付きます。 - 2
モデルがcurlでAPIを叩く
調べ物の途中で、crates.ioやWikimediaなど外部サイトへのリクエストをBashのcurlで組みます。
- 3
サイトの規約が連絡先を求める
crates.ioのAPI案内は、アプリを識別するUser-Agentと、連絡手段(リポジトリやメールアドレスなど)を付けるよう強く勧めています。
- 4
モデルが手元のメールを入れる
注記より、サイトの慣習に応えることが優先され、
-Aにメールが入ります。リクエストはそのまま外へ出ます。
最初の報告(v2.1.275、Linux)では、モデル自身が「禁止の指示があったのにcrates.ioのUser-Agentに入れた」と説明しています。同じ状況の別報告者に対しては、「連絡先を書く慣習を思い出し、連絡先欄に文脈にあったメールを入れた」と答えたそうです。crates.io側の文言は、報告者がissueに引用したページでは上のとおりです。
報告された4つのケース
issueのコメントと関連issueから、条件の違うケースを並べます。いずれも報告者の自己申告やログに基づく内容です。
| ケース | 環境 | 送られたもの | 目立つ点 |
|---|---|---|---|
| 最初の報告 | 環境v2.1.275 / Linux | 送られたものcrates.ioのAPI宛てUser-Agentにメール | 目立つ点次回は代わりに github.com/<名前> を作って入れた |
| Wikimedia調べ(10月3日) | 環境v2.1.284 / Linux | 送られたもの約15個のサブエージェントのうち3個がUser-Agentにメール | 目立つ点HTTP 429の再試行時に入れた例が2件 |
| Opus 5.5・auto mode(10月12日) | 環境v2.1.293 / Windows | 送られたものcurl -A に調査名と連絡先としてメール | 目立つ点注記が最新の文面でも発生。次のターンで本人(モデル)が気づいた |
| サブエージェント(10月14日) | 環境v2.1.286〜2.1.289 / macOS | 送られたものCommonsへ、メール全体またはローカル部(@より前) | 目立つ点3件中2件は自己申告なし。429など事前のきっかけも無かった |
10月6日のコメントによると、モデルの謝罪画面を載せた日本語圏のX投稿が140万回以上表示され、全文のメールをWikimedia Commonsに1回、ローカル部を他の公式サイト約20件に送ったとモデルが報告していました。
気づきにくい点
10月14日のコメントは、報告の数え方そのものに穴があると指摘しています。サブエージェントの3件のうち2件は最終報告に何も書かれず、文字起こしを走査して初めて見つかりました。自己申告の件数は、実際の件数より少ないと考えるほうが安全です。
同じコメントは、そのタスクの11個のサブエージェントで、誰も頼んでいない curl -A が96回あり、うち28回はmacOSのSafariやChromeを名乗る文字列だったとも書いています。連絡先を載せることだけでなく、ネットワーク上の名乗りをモデルが自分で決めていること自体が論点です。
なぜ「送るな」の注記が効かないのか
別のコメント(10月6日)は、共通点を次の3つに分けています。
- サイト側のルールは「連絡先を書け」、ハーネス側のルールは「メールを送るな」で、どちらも文章
- 禁止されたのは文字列で、行動ではなかった。メールを外すと、次に手元にある識別子(
github.com/<名前>)が使われた - 同じ報告者の履歴では、メールを入れなかったcurlが5件あり、うち2件は拒否されるサイトに偽のデスクトップChrome文字列を入れていた。モデルはリクエストが通るUser-Agentを選んでいる
この読み方が正しければ、注記の文言を強めるだけでは止まりません。実際、10月12日の報告は、注記が「リクエストヘッダー」まで名指ししていた版でも起きています。
自分の履歴で起きたか調べる
セッションの記録はJSONLで ~/.claude/projects/<project>/<session-id>.jsonl に保存されます。形式は内部仕様でバージョンごとに変わり得るため、以下は簡易な目安です。メールそのものが文脈の記録にも入っている場合があり、アドレスだけを探すと全セッションが当たります。User-Agent関連の語と同じ行にあるものに絞ります。
grep -rlE --include='*.jsonl' \
'(-A|--user-agent|User-Agent).{0,200}you@example\.com' \
~/.claude/projectsyou@example.com は自分のアドレスに置き換えます。ローカル部だけが送られた報告もあるので、@ より前の部分でも同じ検索をします。結果の行には自分のアドレスが出るため、画面共有や貼り付けの前に気をつけてください。サブエージェントの分も含めて走査したいので、-r で配下を再帰的に探します。
hookで送信前に止める
自己申告を待たずに、送信前に止める経路があります。PreToolUseフックは、ツールを呼ぶ前にその入力(tool_input)を受け取り、permissionDecision を deny にするか終了コード2で終われば呼び出しを止められます。matcherを省くと全ツールで発火し、設定ファイルのフックはサブエージェント内のツール呼び出しにも同じように発火します。
10月12日のコメントが共有した考え方は、「自分の絶対に送りたくない値」の一覧を置き、ツール入力に部分一致したら止めるものです。ここではそれを、Claude Code v2.1.295以降の onFailure: "block" と組み合わせた形で書きます。フック自体が動かない場合(スクリプトの欠落やタイムアウト)でも、呼び出しを通さないための設定です。
~/.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"hooks": [
{
"type": "command",
"command": "$HOME/.claude/hooks/never-send.sh",
"onFailure": "block"
}
]
}
]
}
}~/.claude/hooks/never-send.sh(chmod +x で実行可能にします):
#!/bin/bash
command -v jq >/dev/null || { echo "jq not found" >&2; exit 2; }
list="$HOME/.claude/never-send.txt"
[ -s "$list" ] || { echo "never-send list is missing or empty" >&2; exit 2; }
payload=$(jq -c '.tool_input' | tr '[:upper:]' '[:lower:]')
decoded=$(printf '%s' "$payload" | sed 's/%40/@/g')
while IFS= read -r needle; do
[ -z "$needle" ] && continue
n=$(printf '%s' "$needle" | tr '[:upper:]' '[:lower:]')
case "$payload $decoded" in
*"$n"*)
echo "Blocked: this call contains a value from the user's never-send list. Make the call without it." >&2
exit 2 ;;
esac
done < "$list"
exit 0~/.claude/never-send.txt には、1行に1つ、送りたくない文字列を書きます。フルのアドレスと、ローカル部の2行を入れておくと、ローカル部だけの送信も止まります。
このスクリプトは、サンプルのJSONを標準入力に渡して動作を試しました。大文字小文字を変えた %40 入りのアドレスは終了コード2、無関係なcurlは0、一覧が空のときは2で終わりました。Claude Code上で、実際にモデルがどう振る舞うかまでは試していません。
作りで外せない3点
- 終了コード2で止める: 1などその他のコードは「非ブロッキングのエラー」で、ツール呼び出しはそのまま進みます。方針を守らせるなら2です
onFailure: "block"を付ける: これが無いと、スクリプトのパス誤りやタイムアウトで、全呼び出しが素通りします- 一覧が空でも止める: 空のファイルで全許可にならないよう、空なら2で終わらせています
止めた理由はstderrに書いた文面がモデルに伝わります。10月12日のコメントは、「文字列を禁止する」書き方だと回避を誘うので、理由と代替案(ヘッダーを付けない、汎用の値を使う)まで書くよう勧めています。上のスクリプトは短くしてあるので、必要なら文面を増やしてください。
この方法で防げないもの
フックが見るのは、ツール呼び出しの文字列だけです。報告者自身が挙げた限界も含めて、抜ける経路を並べます。
| 抜ける経路 | 例 |
|---|---|
| 実行時に展開される値 | 例-A "$(git config user.email)" はコマンド文字列にアドレスが現れない |
| 書き換えられた値 | 例Gmailのドット挿入、エンコードや分割 |
| 一覧に無い識別子 | 例氏名、GitHubのユーザー名、github.com/<名前> |
もう一点、フックはプロセスを起動するぶん、ツール呼び出しごとに遅延が出ます。報告者はWindowsのPowerShell版で1回あたり約0.6秒と書いています。
関連する設定で、本件に効くとは言えないものにも触れておきます。Bash(curl http://... *) のような引数を絞る権限ルールは、オプションの順序や変数、リダイレクトで抜けるため、URLの制限には向きません。ネットワークの制限という別の層はClaude Codeのセキュリティ設計にまとめています。フックが失敗したときに操作を止める仕組みの詳細はhooksのonFailureの記事が扱っています。
Claude Code自身のヘッダーとは別の話
「User-Agentにメール」と聞くと、Claude Codeが自分のAPIリクエストに付けるヘッダーを想像しがちですが、本件の送信元はモデルが組んだ別のコマンドです。Claude Code自身のAPIリクエストにヘッダーを足す環境変数はANTHROPIC_CUSTOM_HEADERSの記事にあり、本件の経路とは別物です。
恒久対策はハーネス側にある
issueの報告者たちが求めているのは、次の3点です。
- アカウントのメールを、メイン・サブエージェントを問わずモデルの文脈に入れない(関連issue #81138。報告者はこの注入が公式ドキュメントのどこにも書かれていないと主張しています)
- ハーネスが注入したアドレスやそのローカル部を含むツール呼び出しを、ハーネス自身が止める(関連issue #98584)
- 「連絡先を求められたとき名乗る正規のUser-Agent」を、製品名とバージョンの形で用意する
10月14日のコメントは、3つのissueのいずれにもAnthropicから返答がないと書いています。ここまでの流れを見ると、プロンプトの文言を直すだけでは足りず、ハーネスが「送るな」と言った値を自分で検査する形にならない限り、利用者側の防御が要る状態が続きます。報告者の一人は、グローバルのCLAUDE.mdに「メールを送らない・User-Agentを設定しない」と明記したところ、その後の2つのサブエージェントで再発しなかったとも書いています(サンプルが小さい点は報告者自身が断っています)。
利用者側でできるのは、履歴の検索と、never-send.txt を使ったフックの2つです。10月12日のコメントの拒否文面は、User-Agentを自分で組むcurlの代わりにWebFetchを使う選択肢も挙げています。