dangerouslyAllowBrowserエラーの原因 — Anthropic SDKのAPIキー露出と代替
Anthropic TypeScript SDKをブラウザで動かすとdangerouslyAllowBrowserのエラーが出ます。判定の仕組み、危険が小さい条件、バックエンド経由の代替構成を解説します。
ブラウザで動くコードの中で new Anthropic() を呼ぶと、SDKは例外を投げて止まります。原因はAPIキーを守るための既定の設計で、バグではありません。dangerouslyAllowBrowser: true を足せば動きますが、その時点でキーはクライアントコードに入ります。
結論から書くと、社内の信頼できる利用者だけが使うツールか、使い捨てのキーで試す場面を除けば、バックエンド経由の構成が基本になります。
エラーメッセージと発生条件
ブラウザ相当の環境でクライアントを作ると、次の趣旨のエラーが出ます。
It looks like you're running in a browser-like environment.
This is disabled by default, as it risks exposing your secret API credentials to attackers.
If you understand the risks and have appropriate mitigations in place,
you can set the `dangerouslyAllowBrowser` option to `true`, e.g.,
new Anthropic({ apiKey, dangerouslyAllowBrowser: true });文面は公開されているSDKのソース(src/client.ts)にある固定文字列です。投げられるのは AnthropicError で、API呼び出しより前、コンストラクターの時点で失敗します。つまりネットワークもAPIキーの正否も関係ありません。
判定は単純です。SDKの検出関数は、次の3つがすべて存在するかを見ています。
windowwindow.documentnavigator
3つが揃えば「ブラウザ相当」とみなされ、オプションが true でない限り例外になります。ブラウザ向けバンドルに限らず、window と document を持つ実行環境でも起こりえます。
SDKの要件ページでは、サポート対象のランタイムとしてNode.js 20 LTS以降、Deno、Bun、Cloudflare Workers、Vercel Edge Runtimeなどが並び、Webブラウザーは「既定で無効」と書かれています。React Nativeは対象外です。
有効化すると何が起きるか
dangerouslyAllowBrowser: true は、判定を外すだけのスイッチです。キーを隠す仕組みは付きません。
// 動くが、apiKeyがクライアントコードに入る
const client = new Anthropic({
apiKey: "sk-ant-...",
dangerouslyAllowBrowser: true,
});このオプションを有効にすると、SDKはリクエストに anthropic-dangerous-direct-browser-access: true というヘッダーを付けます。SDKのソースでは、このヘッダーはオプションが true のときだけリクエストの既定ヘッダーに加わります。オプションを付けない通常のサーバー利用では送られません。ブラウザからの直接アクセスだと、リクエスト側で示す役割と読めます。
SDKのオプションの説明も、リスクを理解し、適切な緩和策を取っている場合にだけ true にするよう求めています。エラー文の「適切な緩和策」の中身は具体的に書かれていません。次の節に挙げる運用が、その候補になります。
一方、危険の中身はSDKの要件ページに明記されています。ブラウザは本質的にサーバーより安全性が低く、ブラウザにアクセスできる利用者なら誰でも認証情報を調べ、取り出し、悪用できます。取り出されたキーは、あなたのアカウントへの不正利用につながります。
ヘルプセンターのAPIキー運用の記事は、キーを「クレジットカード番号のようなもの」にたとえています。第三者が使えば、利用料金はあなたに請求されます。
危険が小さい条件
要件ページは、リスクが小さくなりうる場面を2つ挙げています。
| 場面 | 成り立つ前提 |
|---|---|
| 社内ツール | 成り立つ前提利用環境が管理されていて、利用者が信頼できる |
| 開発・デバッグ | 成り立つ前提キーが短命、または本番と共用しない、もしくは頻繁にローテーションされる |
社内ツールでも、利用者全員にキーが見える点は変わりません。信頼できる範囲に閉じていることが前提です。
開発用途は、「一時的に有効化する」ことが条件になっています。デモ用のコードがそのまま公開環境へ出ていくのが典型的な事故なので、有効化したコードをリポジトリに残さない運用が欠かせません。
有効化するなら最低限そろえたいこと
ヘルプセンターの記事には、ブラウザ利用に限らない一般的な対策が並んでいます。有効化に踏み切るなら、次の項目が実質的な歯止めになります。
- 開発・テスト・本番でキーを分ける。漏れたときに、その用途のキーだけ無効化できる
- 90日ごとなど決まった周期でキーを作り直し、古いキーを無効にする
- ConsoleでログとUsageを定期的に見る
- カスタムレート制限の組織では利用額の上限を設定する。標準レート制限の組織では自動チャージの設定を見直す(記事は、自動チャージの上限が想定外の高額利用への歯止めにもなると説明しています)
キーが公開GitHubリポジトリに載った場合は、GitHubのシークレットスキャンがAnthropicへ通知し、キーが自動で無効化されて利用者にメールが届く、とも書かれています。ただしこれは公開リポジトリが対象です。ブラウザのバンドルに入ったキーは別の経路で見られるため、この仕組みには頼れません。
バックエンド経由にする
ブラウザからAnthropic APIを直接呼ぶ代わりに、自分のサーバーを1枚挟みます。キーはサーバーの環境変数にだけ置き、ブラウザは自分のサーバーのエンドポイントを呼びます。
// server.ts(Node.js)
import express from "express";
import Anthropic from "@anthropic-ai/sdk";
const app = express();
app.use(express.json());
// ANTHROPIC_API_KEY環境変数から読まれる
const client = new Anthropic();
app.post("/api/chat", async (req, res) => {
const message = await client.messages.create({
max_tokens: 1024,
messages: [{ role: "user", content: String(req.body.prompt) }],
model: "claude-opus-5-5",
});
const text = message.content
.filter((b) => b.type === "text")
.map((b) => b.text)
.join("");
res.json({ text });
});
app.listen(3000);// ブラウザ側: Anthropic SDKは使わず、自分のAPIだけを呼ぶ
const res = await fetch("/api/chat", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ prompt: "こんにちは" }),
});
const { text } = await res.json();上のコードは構成を示す例で、そのままでは本番に使えません。エンドポイントが無防備だと、今度はあなたのサーバーが「誰でもキーを使える窓口」になります。実運用では次の点を足します。
- ログイン済みの利用者だけが呼べるようにする
- 利用者ごとの回数制限や、
max_tokensの上限をサーバー側で固定する - モデル名やシステムプロンプトをサーバー側で決め、ブラウザから受け取らない
最後の項目が効くのは、ブラウザから受け取った値をそのままAPIへ渡すと、利用者がモデルや出力量を自由に選べてしまうからです。この3点はAPIキーを隠す以外の設計判断で、公式ページが定めた手順ではありません。
ストリーミング応答をブラウザへ流したい場合も、Anthropic側とのストリームはサーバーで受け、ブラウザへはServer-Sent Eventsなど自前の形式で中継します。SDKのヘルパーの使い方はClaudeのTypeScript SDKで実装するStreaming・Tool・MCPヘルパーにまとめています。
サーバー側でのキーの置き場所
バックエンドを挟んでも、キーをコードに直接書けば同じ事故が起きます。ヘルプセンターの記事は、環境変数で注入する方法を勧めています。クラウド環境にデプロイするときは、各社のシークレット管理の仕組みで環境変数としてアプリに渡します。
ローカルで .env ファイルを使うなら、.gitignore などで必ずバージョン管理の対象から外します。クラウド側では、.env ファイルより暗号化されたシークレットの保管先が望ましいと書かれています。
# .env(.gitignoreに含めておく)
ANTHROPIC_API_KEY=your-api-key-herenew Anthropic() は引数なしでも ANTHROPIC_API_KEY を読むため、コード側にキーの文字列は現れません。WebベースのIDEやCI/CDなど外部ツールにキーを預けるときは、そのツールの開発元にConsoleのアカウントへの入り口を渡すことになる、と同じ記事が注意しています。信頼できないツールには渡さず、渡す場合も暗号化されたシークレットとして登録します。
Cloudflare WorkersやVercel Edge Runtimeは、SDKの要件ページでサポート対象に挙がっています。ブラウザから呼ぶ窓口をこうしたサーバーレス環境に置き、キーは環境変数に入れる構成が取れます。
状況別の選び方
| 状況 | 向く構成 |
|---|---|
| 一般公開のWebアプリ | 向く構成バックエンド経由。有効化は避ける |
| 社内の限られた利用者向けツール | 向く構成有効化も選択肢。キーは専用にして上限とログを見る |
| ローカルでの試作・デバッグ | 向く構成一時的に有効化。使い終えたキーは無効にする |
| サーバーレス関数が使える | 向く構成その関数をプロキシにして、キーは環境変数に置く |
迷ったときの目安は、「そのコードを開いた全員にキーを渡してよいか」です。答えが「はい」にならないなら、バックエンド経由にします。
直しても出るときのつまずき
サーバー側のコードのつもりで書いたのに出る
Reactなどで同じモジュールがブラウザ側にも読み込まれると、クライアントの生成がブラウザで実行されます。new Anthropic() を呼ぶ場所が、ブラウザ向けバンドルに入っていないかを確認してください。判定はコンストラクターの呼び出し時に行われるため、サーバー専用のモジュールに閉じ込めるのが確実です。
テスト環境で出る
Jestは、"node" 環境が対象です。要件ページには、"jsdom" 環境はサポート外と書かれています。jsdomはブラウザ相当のグローバルを用意するため、判定に引っかかります。SDKを呼ぶテストはnode環境で実行し、ブラウザ側のテストではSDKをモックに差し替えます。
有効化したのに接続できない
エラーが AnthropicError ではなく APIConnectionError などに変わった場合、判定は通っています。以降はネットワークや認証の切り分けです。例外クラスの対応はClaude APIのエラー形式とSDK例外クラスの言語別対応表、疎通確認の手順はClaude API connection errorが出たときの切り分けと疎通確認で追えます。
キーを出してしまった後の動き
すでに有効化したコードを公開してしまった場合は、次の順で動きます。
- ConsoleでそのAPIキーを無効にし、新しいキーを作る。ヘルプセンターの記事は、キーの作り直しを「新しいキーを作り、古いキーを無効にする」操作として説明しています
- 公開済みのバンドルを差し替える。古いバンドルを持つ利用者が残るため、キーの無効化が先
- 無効化までのUsageとログを確認し、身に覚えのない利用がないかを見る。開発・テスト・本番でキーを分けていれば、無効にするのは漏れた用途のキーだけで済む
- リポジトリのシークレットスキャンを有効にし、再発を防ぐ。記事はGitleaksのようなSAST(静的解析)ツールで過去のコミットまで調べることと、CI/CDパイプラインにスキャンを組み込んでmainブランチに入る前に止めることを挙げています
エラー処理全般の設計はClaude APIのエラーハンドリング設計に整理しています。
まとめ
dangerouslyAllowBrowser のエラーは、ブラウザ相当の環境でクライアントを作った瞬間に出る、キー保護のためのガードです。外せば動きますが、外した分だけキーの管理責任がコードの外に出ます。
信頼できる社内利用や短命キーでの試作なら有効化は選択肢になり、それ以外はサーバーを挟む構成が基本です。サーバーを挟む構成では、キーを隠すだけでなく、誰が何回呼べるかまでサーバー側で決めておく必要があります。