allowed_domainsのホモグラフ攻撃対策 — キリル文字でamazon.comを偽装される穴
Claude APIのweb_search/web_fetchが使うallowed_domainsは文字種を検証しません。キリル文字でamazon.comを偽装する手口と、既存リストをASCII監査する具体的な手順を解説します。
allowed_domainsは文字種を検証していない
allowed_domains / blocked_domains はドメイン名の文字列だけを見ており、その文字がASCIIかどうかまでは検証しません。キリル文字のаのようにASCII文字と見分けがつかないUnicode文字を1つ混ぜたドメインを許可リストに書くと、Claudeはそのドメインを本物と区別できないまま通してしまいます。公式ドキュメントが明記するホモグラフ攻撃の具体例と、既存の許可リストをASCIIだけで構成されているか監査する手順をまとめます。claude.aiとClaude Codeでの検索の挙動差はWeb検索の使い方で扱っているので、web_search自体の基本を先に確認したい場合はそちらを参照してください。
ホモグラフ攻撃で何が起きるか
ホモグラフ攻撃とは、見た目がそっくりな別の文字を使い、正規のドメイン名になりすます手口です。platform.claude.comのServer tools解説は、Domain filteringの節の末尾でこの攻撃を名指しで警告しています。キリル文字のа(Unicodeではamazon.comのラテン文字aとは別のコードポイント)を1文字だけ混ぜたаmazon.comは、画面表示ではオリジナルのamazon.comと区別がつきません。しかしallowed_domainsにこの文字列をコピー&ペーストで書いてしまうと、意図したamazon.comではなく別ドメインへのアクセスを許可することになります。
公式の対策は2つだけです。許可リスト・拒否リストにはASCIIのみのドメイン名を使うこと、そして既存のエントリを非ASCII文字の混入がないか監査すること。どちらも運用側の作業であり、API側が自動でブロックしてくれる機能ではありません。
allowed_domainsが突破された場合の実害は、単に想定外のサイトにアクセスするというだけでは終わりません。web_searchであれば検索結果として攻撃者の用意したコンテンツがClaudeの目に入り、web_fetchであればそのコンテンツがそのまま会話コンテキストに取り込まれます。取り込まれた内容次第では、Claudeの以降の応答や判断がその偽装サイトの記述に引っ張られる可能性があり、ドメインフィルタリングという入口の防御が破られると、その先の出力全体の信頼性にまで波及する構造です。「アクセス先を絞る」という設定項目の重みは、単なるアクセス制御以上のものだと捉えておく必要があります。
ドメインフィルタリングの基本仕様を先に押さえる
allowed_domainsとblocked_domainsは、Webにアクセスするサーバーツール(web_search・web_fetch)がツールオブジェクトに持つフィールドです。同一リクエストで両方を同時に指定することはできず、指定すると400エラーになります。
- ドメインはスキーム(
https://など)を含めない。example.comと書き、https://example.comとは書かない - サブドメインは自動的に含まれる。
example.comを許可するとdocs.example.comも許可される - 逆に
docs.example.comのように特定のサブドメインを指定すると、そのサブドメインだけに絞られ、example.com自体やapi.example.comはマッチしない - サブパスの扱いが
web_searchとweb_fetchで異なる。web_searchはexample.com/blogのようなパス付きエントリをサポートし、example.com/blog/post-1のようなURLにマッチする。web_fetchはドメイン単位でしか照合しないため、パス付きのエントリを書いてもそのパス指定自体は無視される - ワイルドカード(
*)はドメイン部分では使えず、パス部分でのみ有効。example.com/*は有効だが*.example.comは無効 - 不正なドメイン形式はリクエスト時点で400
invalid_request_errorになる。400エラー全般への向き合い方はClaude APIのエラーハンドリング設計にまとめています
このweb_searchとweb_fetchの差は見落としやすく、web_fetch用にパス制限を書いたつもりでドメイン全体を許可してしまうケースが起きます。パス指定はエントリとして受理されますが、実際の照合ではドメイン部分だけが比較されるため、パスの絞り込みが効いていると誤解したまま運用してしまう危険があります。
ブラウザーの防御と何が違うか
Webブラウザーの多くは、似た種類のホモグラフ攻撃に対してすでに防御を持っています。ラテン文字と非ラテン文字が1つのドメインラベルに混在していると、アドレスバー表示を人間が読めるUnicode表記のままにせず、xn--から始まるpunycode表記へ自動的に切り替えるのが一般的な実装です。この切り替えによって、аmazon.comのようなキリル混じりのドメインはxn--から始まる見慣れない文字列として表示され、利用者が違和感に気づける仕組みになっています。
Claude APIのallowed_domains・blocked_domainsにはこの種の自動変換や警告表示はありません。渡した文字列をそのまま比較に使うだけなので、ブラウザーが担っていた「見た目の異常を検知する」役割を、開発者が許可リストの管理プロセス側で肩代わりする必要があります。ドキュメントが「既存エントリを非ASCII文字の混入がないか監査せよ」と明記しているのは、このギャップを埋める指示だと読むと分かりやすくなります。
実務でいつ監査するか
監査のタイミングは3つ考えられます。1つ目は許可リストへの新規エントリ追加時で、CIのlintステップに組み込めば人手を介さずに継続的にチェックできる点が利点です。2つ目は既存リストの定期棚卸しで、コピー&ペースト由来の混入は追加時のチェックをすり抜けて後から見つかることもあるため、月次などの間隔で全件を再チェックする価値があります。3つ目は、外部から受け取ったドメインリスト(パートナーやユーザーからの申請)を許可リストに取り込む直前です。自分たちで入力したドメインより、外部由来のドメインの方がホモグラフの混入リスクは高くなります。
web_fetchには、ドメインフィルタリングとは別に「会話に一度も登場していないURLは取得できない」というもう1つの防御があります。無効化できない検証で、詳細はweb_fetchのurl_not_in_prior_contextエラーの原因と回避策にまとめました。ドメインフィルタリングが破られても、この出どころ検証がClaude自身の暴走的なURL生成を防ぐ層として残ります。
組織レベルの制限との関係
Claude ConsoleでAnthropicの組織アカウントに設定したドメイン制限は、APIリクエストごとのallowed_domainsと両方が同時に効きます。リクエスト側のallowed_domainsは組織側の許可リストの部分集合でなければならず、組織の許可リストに含まれないドメインをリクエスト側に書くと検証エラーになります。逆に組織側がブロックしているドメインをリクエスト側の許可リストに含めても、該当エントリを名指しした400エラーで拒否される仕様です。つまり、リクエスト単位のASCII監査だけでなく、組織レベルの許可リストも同じ観点でチェックする必要があります。
Managed Agentsでは制約がさらに厳しい
Claude Managed Agentsのツールセットでもweb_search・web_fetchエントリに同じallowed_domains・blocked_domainsフィールドを設定できますが、Messages APIとは制約が違います。
| 項目 | Messages API(web_search/web_fetch) | Managed Agents |
|---|---|---|
| エントリ数上限 | Messages API(web_search/web_fetch)明記なし | Managed Agents各リスト最大64件 |
web_fetchのパス指定 | Messages API(web_search/web_fetch)パス付きエントリは無視される(ドメインのみ照合) | Managed Agentsパスを含むエントリ自体が不可 |
max_uses / citations / cache_control | Messages API(web_search/web_fetch)利用可能 | Managed Agents利用不可 |
| Console組織設定との関係 | Messages API(web_search/web_fetch)両方が同時に効く | Managed Agents効かない(ツールセット側のリストのみが有効) |
Managed AgentsではConsoleの組織レベル設定が適用されない点が、Messages APIと最も違うところです。ツールセット側のリストだけがすべてになるため、ホモグラフ混入のチェックもツールセット定義の1か所に集約できます。大量のツールを扱う設計全般はAdvanced Tool Useで扱っているので、Managed Agentsのツールセット構成を広く見直す場合はあわせて参照してください。
許可リストに非ASCII文字が混ざっていないかを機械的に監査する
目視でのレビューはホモグラフ攻撃に対してほぼ無力です。а(キリル文字)とa(ラテン文字)はどのフォントでも同じ形に見えるため、コードレビューで気づける可能性は低いといえます。Pythonのstr.isascii()を使えば、非ASCII文字を含むエントリを機械的に洗い出せます。
python3 -c '
domains = ["example.com", "docs.python.org", "аmazon.com"]
suspicious = [d for d in domains if not d.isascii()]
print("非ASCII文字を含むエントリ:", suspicious)
'このワンライナーは、allowed_domainsに渡す配列をそのままdomainsに貼り付けるだけで使えます。CIのlintステップに組み込めば、非ASCIIドメインが許可リストへ紛れ込むのをコミット時点で検出できます。
よくある見落とし
- 目視レビューで済ませる: ホモグラフはフォント上区別がつかない設計そのものなので、人間の目でのチェックは検出手段として機能しない
allowed_domainsとblocked_domainsを両方指定してしまう: 同時指定は400エラーの対象で、どちらか一方しか選べないweb_fetchにパス付きエントリを書いて安心する:web_fetchはドメインのみ照合するため、パスによる絞り込みは効かない(web_searchとの差を混同しやすい)- リクエスト側だけを監査して組織側の許可リストを見ない: 組織レベルの許可リストにホモグラフドメインが混入していると、個別リクエストのASCII監査だけでは防げない
allowed_domainsとblocked_domainsで監査の優先度が変わる
blocked_domains(拒否リスト)にホモグラフドメインを書くのは、そもそも成立しにくい対策です。攻撃者が使う偽装ドメインは事前に予測できないため、既知の悪性ドメインを1件ずつ拒否リストに足していく運用では、新しいホモグラフドメインの出現に追いつけません。これに対してallowed_domains(許可リスト)は、通す対象を先に固定して残りをすべて弾く構成なので、リストに含まれる数十〜数百件のASCII監査さえ徹底すれば、未知のホモグラフドメインも構造的に排除できます。ドメインフィルタリングをセキュリティ目的で使うなら、blocked_domainsよりallowed_domainsを選ぶ理由がここにもあります。
ダイナミックフィルタリング以降、ドメイン比較は目視に触れない場所へ移る
_20260209以降のダイナミックフィルタリング機能では、検索結果に対する絞り込みをコード実行が内部で担うようになりました。ドメイン文字列の比較処理がコード実行の内側で完結する場面が増えるほど、開発者がリクエストや設定を書く時点で目視レビューを挟める余地は相対的に小さくなります。だからこそ、非ASCII混入のチェックは「書くときに人が気づく」運用ではなく、str.isascii()のような機械チェックをCIなど手前の工程に固定しておく必要があります。
まとめ
allowed_domains・blocked_domainsはドメインの文字列一致だけを見ており、文字種の正当性までは検証しません。既存の許可リストと組織レベルの設定をstr.isascii()のような機械チェックで定期的に監査し、web_searchとweb_fetchでパス照合の挙動が違う点も併せて確認しておくと、意図しないドメインへのアクセスを未然に防げます。