response idは、ただの識別子に見える。
ログにも、URLにも、エラーメッセージにも、気軽に載せてしまう。
APIキーなら決して置かない場所に、平気で現れる。
ところが、共有AIゲートウェイの構成では、そのIDを知っているだけで、他人の会話が読める。
継続もできる。
つまりresponse idは、識別子の顔をした、権限そのものだ。
そして厄介なことに、この穴は公式ドキュメントのどこにも書かれていない。
response idはcapability tokenである
IDだけで会話が読める構造
まず、構造を確かめる。
Responses APIは store: true(デフォルト)で、リクエストとレスポンスをサーバー側に保存する。
保存された会話に対しては、IDだけで二つの操作ができる。
GET /responses/{id}:会話全体の取得previous_response_idの指定:会話状態を引き継いだ継続
肝心なのは、認可の粒度だ。
それはリソース単位(APIキーまたはEntraトークン)であって、ユーザー単位ではない。
同じ資格情報でアクセスしているかぎり、誰がリクエストしているのか、そのレスポンスへのアクセス権があるのかを検証する、組み込みの仕組みは存在しない。
共同利用ゲートウェイで認可の穴になる
単一ユーザーの利用なら、この設計は問題にならない。
問題が牙をむくのは、AIゲートウェイで複数のユーザーやチームがバックエンドを共同利用する構成だ。
この構成では、全ユーザーが同一のバックエンド資格情報を共有する。
だから、何らかの経路でresponse idを知った他のユーザーは、正規の認証を通したまま、他人の会話を読める。
引き継いで継続もできる。
IDが漏れることが、そのまま会話履歴の漏えいになる。
response idは、推測困難な文字列だ。
だが、APIキーのような機密情報としては扱われにくい。
ログ、URL、エラーメッセージ、デバッグ画面。
APIキーなら決して置かない場所に、平気で現れる。
識別子が所在を運ばない設計の悪さは前に扱ったが、今回は逆向きだ。
識別子が、権限そのものとして機能してしまっている。
「推測不能性だけに依存したcapability token」と捉え直すと、扱いの軽さと権限の重さの非対称が、はっきり見える。
LiteLLMとMicrosoftが独立に同じ結論に至っていた
この穴は、私だけが気づいたものではない。
少なくとも二つの組織が、独立に、同じ場所へ到達していた。
LiteLLM:製品機能として修正済み
LiteLLMは、この問題を2025年10月、セキュリティ修正として対応済みだった。
PR #15757のタイトルが、そのまま状況を語っている。
「Responses API - prevent User A from retrieving User B’s response, if response.id is leaked」。
- 共有アカウントでのretrieveに対する所有権チェックを実装(参考実装:
responses_id_security.py) - 別チームのサービスアカウント間の相互アクセスも防止
- デフォルト有効で、管理者がオプトアウト可能
ただし、CVE化や炎上ではない。
静かにPRで直された類だ。
共同利用ゲートウェイでResponses APIのステートフル機能を本格利用する組織がまだ少なく、露出面が顕在化しにくいためだと見ている。
Microsoft:公式ブログは指摘、公式ドキュメントは沈黙
Azure側の認知は、層によって温度差がある。
| 層 | 状況 |
|---|---|
| Microsoft Learn(公式ドキュメント) | 沈黙。取得・30日保持・削除を淡々と記述し、クロスユーザーアクセスへの警告なし |
| Microsoft Community Hubブログ | 問題を明示的に指摘し、解決策を実証(2025年9月) |
| Microsoft Q&A | 保存場所・保持期間の懸念のみで、認可の穴は素通り(代表例:回答は「response idを控えておくこと」と助言するのみ) |
| Azure-Samples/AI-Gateway(30以上のラボ集) | response所有権を扱う専用ラボなし |
決定的なのは、Healthcare and Life Sciencesブログの記事「Building Secure, Multi-User AI Workflows with the Responses API」(2025年9月)だ。
「Responses APIはデフォルトで、response IDさえあれば任意のレスポンスを取得できる。誰がリクエストしているか、アクセス権があるかを検証する組み込み機構が存在しない」と、明言している。
そのうえで、作成したユーザーだけがretrieve・継続できることをAPIMポリシーで保証するラボを構築し、こう結論づけた。
「モデルを守るだけでなく、レスポンスも守る必要がある」。
対応の非対称
つまり、LiteLLMとMicrosoftは、独立に同じ問題へ到達している。
だが、製品機能化の度合いが違う。
| LiteLLM | Azure/APIM | |
|---|---|---|
| 問題認識 | 内部チケットとして公式にトラッキング | 公式ブログで明示、Learnドキュメントは沈黙 |
| 対処 | 製品機能としてデフォルト有効 | DIYポリシーパターンの提示のみ |
| 位置づけ | セキュリティ修正 | ベストプラクティス・ラボ |
そして、いちばん怖いのは、この一点だ。
公式ドキュメントに載っていない以上、APIMでAIゲートウェイを構築する組織の大半は、この穴に気づかないまま、Responses APIを開放する可能性が高い。
ゲートウェイ側での対策
3つの選択肢
では、ゲートウェイ側で何ができるのか。
対策は、大きく三つだ。
store: falseの強制。APIMポリシーでリクエストボディを書き換える。単純だが、ステートフル機能そのものを放棄することになる- ゲートウェイ側での所有権マッピング。作成時に「ユーザーID → response id」の対応を記録し、取得・継続時に所有チェックを行う。LiteLLMの修正もMicrosoftブログのラボも、このアプローチだ
- テナント分離。ユーザーやチームごとにバックエンドの資格情報を分ける。確実だが、コストと運用が重い
ステートフル機能を活かしつつ守るなら、本命は所有権マッピングになる。
APIMでのDIY実装の注意点
その所有権マッピングを、APIMポリシーで実装するとしよう。
作成時に所有者IDを保存し、取得・継続時に照合して、不一致なら403を返す。
理屈は単純だ。
だが、ここで一つ、置き場所の問題が立ちはだかる。
APIMの組み込みキャッシュは、揮発性でリージョン独立だ。
Responses APIの保持期間である30日をまたぐマッピングの保存には、使えない。
RedisやCosmos DBのような外部ストアが要る。
APIMポリシー単体では状態管理が苦手なので、状態を扱う処理は、APIMとバックエンドの間に置くプロキシ層へ寄せる構成が自然になる。
ここに、もっと大きな前提の崩れがある。
ステートフルなAPIをゲートウェイで守るには、ゲートウェイ側も状態を持たざるを得ない。
「リクエストが通過する瞬間に、すべてを検査すればよい」。
従来のゲートウェイが立っていた、この前提が崩れている。
冒頭の、ログに気軽に載るIDに戻る。
それが権限を運んでしまう世界では、守るべき境界は通過点ではなく、状態のほうへ移っている。
