コマンドラインツールllmが、api_baseで指定した別のサービスへOpenAIのAPIキーを送っていた問題|0.37.2での修正と、当てはまる設定の見分け方
llm 0.37.2で、OpenAIのAPIキーを別のサービスへ送る問題が2件直った
Simon Willison氏が開発するコマンドラインツールllmの0.37.2が、2026年11月12日(日本時間)に公開されました(0.37.2のリリースノート)。llmは、ターミナルからAIモデルに質問できるツールです。OpenAIのモデルのほか、設定ファイルextra-openai-models.yamlにモデルを登録すると、OpenRouterのようにOpenAIと同じ形で呼べる別のサービス(OpenAI互換のサービス)も同じコマンドで使えます。登録では、接続先のURLをapi_baseに、送る鍵の名前をapi_key_nameに書きます。
0.37.2で直ったのは、このapi_baseを設定したモデルで、利用者のOpenAIのAPIキーを接続先へ送ってしまう問題2件です。GitHubで2件のセキュリティ勧告が公開されています(GHSA-v9h7-jj4g-f862、GHSA-r639-rr7r-x72w。深刻度はどちらも「medium」)。1件目は、api_key_nameに書いた名前の鍵を保存していないと、環境変数OPENAI_API_KEYの値を送っていた問題で、2023年7月の0.6から続いていました。2件目は、OpenAIの新しい呼び出し方であるResponses APIで呼ぶモデル(responses: true)のうち、api_key_nameを書いていないものを、従来のChat Completions APIに切り替える-o chat_completions 1付きで呼んだときに、保存したOpenAIの鍵も送っていた問題です。llmの公式ドキュメントは以前から「api_baseを設定するとOpenAIの鍵は送らない」と説明していましたが、この2つの使い方では送っていました。
この記事では、受け取った鍵を記録するだけの検証用サーバーを手元に立て、0.37.1と0.37.2に同じ設定とコマンドを渡して、サーバーに何が届くかを比べました。使った鍵はすべてダミーです。当てはまる設定の見分け方、0.37.2へ上げる手順、上げたあとに出るエラーへの対処、OpenAIの鍵を作り直すかの判断をまとめます。
要点
0.37.2では、api_baseの接続先へOpenAIの鍵が届かなくなった
0.37.1と0.37.2で、15通りの設定と使い方を比べた結果です。
1件目:OPENAI_API_KEYを送る条件
api_key_nameの鍵を保存していない
0.37.1は、代わりにOPENAI_API_KEYの値を接続先へ送りました。
2件目:chat_completionsで鍵を送る条件
api_key_nameのないresponses: trueのモデルに-o chat_completions 1
0.37.1は、llm keysに保存したOpenAIの鍵も、環境変数の鍵も送りました。
0.37.2の動き
api_baseの接続先へOpenAIの鍵を送らない
1件目の使い方は、送信する前に「No key found」で止まりました。2件目の使い方は、DUMMY_KEYを送って答えが返りました。
0.37.2へすぐ上げられないとき
api_key_nameの鍵をllm keys setで保存する
1件目は、0.37.1でも接続先の鍵だけが届きました。2件目は-o chat_completions 1を付けなければ届きません。
api_key_nameの鍵が未保存だと、OPENAI_API_KEYを送っていた
1件目の問題(GHSA-v9h7-jj4g-f862)から見ていきます。api_key_nameには、llm keys setで保存した鍵の名前を書きます。この記事では、api_key_nameに書いた名前で保存した鍵を「api_key_nameの鍵」と呼びます。たとえば、OpenRouterの鍵をllm keys set openrouterで保存し、設定ファイルに次のように書くと、このモデルを呼んだときにOpenRouterの鍵が送られます(model_idはllmで使う名前、model_nameは接続先のサービスでのモデル名です)。
# extra-openai-models.yaml の例
- model_id: or-llama
model_name: meta-llama/llama-3.3-70b-instruct
api_base: "https://openrouter.ai/api/v1"
api_key_name: openrouter
0.37.1までは、openrouterという名前の鍵が保存されていないと、環境変数OPENAI_API_KEYの値を代わりに使い、接続先へ送っていました。GHSA-v9h7-jj4g-f862の勧告は、きっかけとして次の3つを挙げています。
api_key_nameの名前を書き間違えたllm keys setで鍵を保存し忘れた- 新しいマシンで、
OPENAI_API_KEYだけを設定していた
同じ勧告によると、2023年7〜8月に出た0.6〜0.7.1では、api_key_nameの鍵を保存していてもOPENAI_API_KEYの値が優先して送られていました(0.8で修正)。この時期の版で、api_key_nameのモデルとOPENAI_API_KEYを一緒に使っていた場合は、鍵を保存していても当てはまります。
llm openai endpoint addコマンドでモデルを登録するときは、--keyに保存していない名前を渡すと「No stored key named 'thirdparty' - run 'llm keys set thirdparty' first」と表示されて登録できません(0.37.1と0.37.2で確認)。設定ファイルを手で書いたり、別のマシンへ移したりした場合に当てはまりやすい問題です。
手元では、api_baseを検証用サーバー(127.0.0.1)に向け、api_key_name: thirdpartyの鍵を保存しないまま、OPENAI_API_KEYにダミーの値を入れて同じコマンドを実行しました。サーバーが受け取ったAuthorizationヘッダー(リクエストに付けて送る鍵)を、「→」のコメント行に書いています。
$ export OPENAI_API_KEY=sk-CANARY-env-OPENAI_API_KEY # ダミーの鍵
# 0.37.1
$ llm -m tp-named hi
Hello from canary server
# → サーバーが受け取った鍵: Bearer sk-CANARY-env-OPENAI_API_KEY
# 0.37.2
$ llm -m tp-named hi
Error: No key found - add one using 'llm keys set thirdparty'
# → サーバーへの送信なし
0.37.1では、OpenAIの鍵が接続先へ届き、検証用サーバーの答えも表示されました。0.37.2では送信する前に止まり、保存すべき鍵の名前を示すエラーになります。responses: trueのモデル(Responses APIで呼ぶ)、completion: trueのモデル(Completions APIで呼ぶ)、--no-streamを付けた場合も、届いた鍵と0.37.2のエラーは同じでした。llmをPythonから使う場合も同じで、0.37.1ではllm.get_model("tp-named").get_key()がダミーのOpenAIの鍵を返し、非同期のモデル(llm.get_async_model())もその鍵を送りました。0.37.2ではどちらもNeedsKeyExceptionで止まります。
1件目で送られるのは、環境変数OPENAI_API_KEYの値だけです。環境変数を設定せず、llm keys set openaiで保存したOpenAIの鍵だけがある状態では、0.37.1でも何も送らずにエラーになりました。このとき0.37.1は「No key found - add one using 'llm keys set thirdparty' or set the OPENAI_API_KEY environment variable」と表示し、OPENAI_API_KEYを設定するように案内します。0.37.2のエラー文からは、この案内が消えました。
api_key_nameのないresponses: trueのモデルは、-o chat_completions 1でOpenAIの鍵を送っていた
2件目の問題(GHSA-r639-rr7r-x72w)は、responses: trueを書き、api_key_nameを書いていないモデルで起きます。responses: trueは、Responses APIで接続先を呼ぶ指定です。-o chat_completions 1は、そのモデルを従来のChat Completions APIで呼ぶように切り替えるオプションです。llmのオプションの説明には、互換性のために使うものと書かれています。
api_key_nameを書いていないresponses: trueのモデルは、通常の呼び出しでは鍵の代わりにDUMMY_KEYという固定の文字列を送ります。0.37.1では、-o chat_completions 1を付けたときだけ、OpenAIの鍵を送っていました。1件目と違い、llm keys set openaiで保存した鍵も送られます。
# tp-resp: api_base と responses: true だけを書いたモデル
$ llm -m tp-resp hi
# → 両方の版: Bearer DUMMY_KEY
$ llm -m tp-resp -o chat_completions 1 hi
# → 0.37.1: Bearer sk-CANARY-env-OPENAI_API_KEY
# → 0.37.2: Bearer DUMMY_KEY
# 設定ファイルを使わずに接続先を指定するコマンド(--key なし)
$ llm openai endpoint http://127.0.0.1:18437/v1 -m remote-model \
--responses -o chat_completions 1 hi
# → 0.37.1: Bearer sk-CANARY-env-OPENAI_API_KEY
# → 0.37.2: Bearer DUMMY_KEY
環境変数を外し、llm keys set openaiで保存したダミーの鍵だけにして同じことを試すと、0.37.1ではその鍵がサーバーへ届きました。api_key_nameを書き、その名前の鍵を保存しているモデルでは、0.37.1でも-o chat_completions 1のときに保存した鍵が正しく届きます。勧告によると、2件目は0.32a2から0.37.1までが対象です。
api_baseだけのモデルとapi_key_nameの鍵を保存したモデルは、0.37.1でもOpenAIの鍵を送らなかった
15通りのうち、届く鍵が0.37.2で変わったのは、ここまでの2件に当てはまる使い方だけです。api_baseだけを書いたモデルや、api_key_nameの鍵を保存したモデルは、0.37.1でもOpenAIの鍵を送っていませんでした。代表的な組み合わせを表にまとめます。
| 設定と使い方 | 鍵の置き場所 | 0.37.1で届いた鍵 | 0.37.2で届いた鍵 |
|---|---|---|---|
| api_key_nameあり(その鍵は未保存) | OPENAI_API_KEY | OpenAIの鍵 | 送信なし(No key found) |
| api_key_nameあり(その鍵は未保存) | llm keysのopenaiだけ | 送信なし(No key found) | 送信なし(No key found) |
| api_key_nameあり(その鍵を保存済み) | OPENAI_API_KEYも設定 | 保存した接続先の鍵 | 保存した接続先の鍵 |
| api_baseだけ(Chat Completions) | OPENAI_API_KEY | DUMMY_KEY | DUMMY_KEY |
| responses: true、api_key_nameなし(通常の呼び出し) | OPENAI_API_KEY | DUMMY_KEY | DUMMY_KEY |
| responses: true、api_key_nameなしに-o chat_completions 1 | OPENAI_API_KEY | OpenAIの鍵 | DUMMY_KEY |
| responses: true、api_key_nameなしに-o chat_completions 1 | llm keysのopenaiだけ | OpenAIの鍵 | DUMMY_KEY |
| llm openai endpoint URL(--keyなし、Chat Completions) | OPENAI_API_KEY | DUMMY_KEY | DUMMY_KEY |
| llm openai endpoint URL --responses -o chat_completions 1(--keyなし) | OPENAI_API_KEY | OpenAIの鍵 | DUMMY_KEY |
「OpenAIの鍵」は、鍵の置き場所の列に書いたOpenAIの鍵がそのまま届いたことを表します。「DUMMY_KEY」は、llmが鍵の代わりに送る固定の文字列です。2026年11月12日、macOSでllm 0.37.1と0.37.2(PyPI)を使い、127.0.0.1の検証用サーバーが受け取ったAuthorizationヘッダーで確かめました。
表の9行のうち、0.37.1で手元のサーバーにOpenAIの鍵が届いたのは太字の4行です。15通り全体では、0.37.1の7通りで届きました。届いた7通りのうち表に載せていない3通りは、1行目と同じ条件で、responses: trueのモデル、completion: trueのモデルを使った場合と、--no-streamを付けた場合です。0.37.2では、15通りのどれでもOpenAIの鍵は届きませんでした。
OpenAIの鍵を送る設定かを、3つのコマンドで確かめる
1件目に当てはまるかは、設定ファイルの中身、保存済みの鍵の名前、環境変数の3つで決まります。2件目は、これに加えて-o chat_completions 1を使ったかどうかで決まります。次の順に確かめます。
OpenAIの鍵が接続先へ届く使い方かを確かめる順番
1. 設定ファイル
extra-openai-models.yamlにapi_baseを書いたモデルがあるか。なければ1件目は当てはまらず、2件目は3のllm openai endpointの使い方だけを確かめます。
2. 1件目(OPENAI_API_KEYを送る)の条件
api_key_nameの名前がllm keys listに出てこず、OPENAI_API_KEYも設定している。両方そろうと、0.37.1はOpenAIの鍵を送ります。0.6〜0.7.1を使っていた時期は、鍵を保存していても当てはまります。
3. 2件目(chat_completionsで送る)の条件
api_key_nameのないresponses: trueのモデルを-o chat_completions 1で使った。llm openai endpoint URL --responses -o chat_completions 1を--keyなしで使った場合も同じです。
2か3に当てはまったら
0.37.2へ上げます。2に当てはまったモデルは、エラー文に出た名前で接続先の鍵をllm keys setで保存します。そのうえで最後の節に従い、OpenAIの鍵を作り直すかを決めます。
確かめるコマンドは次の3つです。設定ファイルは、鍵を保存するkeys.jsonと同じフォルダーにあります。echoの行は、OPENAI_API_KEYが設定されているときだけ文を表示し、鍵の値は画面に出しません。
# 1. 設定ファイルで、api_base・api_key_name・responses を書いたモデルを探す
grep -n -E 'model_id|api_base|api_key_name|responses' "$(dirname "$(llm keys path)")/extra-openai-models.yaml"
# 2. 保存済みの鍵の名前を表示する(値は出ない)
llm keys list
# 3. OPENAI_API_KEY が設定されているかを表示する(値は出ない)
echo "${OPENAI_API_KEY:+OPENAI_API_KEY は設定されています}"
検証用の設定で実行した結果です(設定ファイルの行は一部を省いています)。api_key_name: thirdpartyのthirdpartyがllm keys listに出てこず、OPENAI_API_KEYも設定されているため、tp-namedは1件目に当てはまります。tp-respは、-o chat_completions 1を付けて使っていれば2件目に当てはまります。
$ grep -n -E 'model_id|api_base|api_key_name|responses' "$(dirname "$(llm keys path)")/extra-openai-models.yaml"
1:- model_id: tp-named
3: api_base: "http://127.0.0.1:18437/v1"
4: api_key_name: thirdparty
...
18:- model_id: tp-resp
20: api_base: "http://127.0.0.1:18437/v1"
21: responses: true
$ llm keys list
openai
$ echo "${OPENAI_API_KEY:+OPENAI_API_KEY は設定されています}"
OPENAI_API_KEY は設定されています
3つのコマンドで分かるのは、いまの設定です。以前は鍵を保存していなかった、別のマシンで使っていた、という場合は、そのときの設定に当てはめて判断します。
0.37以降なら、llm openai endpoint list --jsonでもapi_key_nameを表示できます。ただし0.37.1では、Azure向けの設定などでapi_version: 2024-02-01のように引用符なしの日付を書いていると、TypeError: Object of type date is not JSON serializableで止まりました(0.37.2で修正済み)。上のgrepは、版や設定ファイルの書き方にかかわらず使えます。
当てはまるモデルを実際に使ったかは、llm logs -m モデルID -n 0 -sで調べられます。このコマンドは、そのモデルの実行記録を日時(UTC)とプロンプト付きで一覧します(-n 0は全件を表示する指定です)。-sの代わりに--jsonを付けてllm logs -m モデルID -n 0 --jsonとすると、-o chat_completions 1を付けた実行には"chat_completions": trueが残っています。
この一覧に出ない実行もあります。エラーで止まった実行(最後の節の401の例で確かめました)、llmでプロンプトを実行するときに-n(--no-log)を付けた実行、llm logs offで記録を止めていた間の実行、llm openai endpoint URLで実行した分、Pythonから呼んだ分です。一覧に出てこなくても、使っていなかったことの裏付けにはなりません。いま記録が止まっているかは、llm logs statusで分かります(止まっていればLogging is OFFと表示されます)。
0.37.2へ上げ、「No key found」が出たらapi_key_nameの鍵を保存する
llmのインストール手順のページにある、入れた方法ごとの更新コマンドです。
# 入れた方法に合わせて、どれか1つを実行する
uv tool upgrade llm # uv
pipx upgrade llm # pipx
pip install -U llm # pip
brew upgrade llm # Homebrew
llm --version # 0.37.2 以降になったかを確かめる
Homebrewのllmは、2026年11月12日6時44分(日本時間)の時点で0.37.1でした。brew upgrade llmのあともllm --versionが0.37.1のままなら、インストール手順のページが案内しているllm install -U llmで、llmが入っている環境の中から更新できます。uvの仮想環境に入れた0.37.1で実行すると、0.37.2に上がりました。
$ llm --version
llm, version 0.37.1
$ llm install -U llm
...
Successfully installed llm-0.37.2
$ llm --version
llm, version 0.37.2
0.37.2へ上げたあと、api_key_nameの鍵を保存していないモデルは「Error: No key found - add one using 'llm keys set thirdparty'」で止まります。エラー文に出た名前で、接続先のサービスの鍵を保存すれば使えるようになります。
# エラー文に出た名前で、接続先のサービスの鍵を保存する(入力した鍵は画面に出ない)
llm keys set thirdparty
このエラーは、OPENAI_API_KEYに接続先のサービスの鍵(OpenAIの鍵とは別のもの)をわざと入れて使っていた人にも出ます。0.37.1までは、api_key_nameの鍵がないときにOPENAI_API_KEYの値を送っていたので、その使い方でも動いていました。0.37.2からは、api_baseを設定したモデルではOPENAI_API_KEYを使わないため、同じようにllm keys setで保存し直します。
すぐに0.37.2へ上げられない環境でも、api_key_nameに書いた名前の鍵をすべてllm keys setで保存すれば、0.37.1で接続先の鍵だけが届きました。2件目については、responses: trueのモデルに-o chat_completions 1を付けない運用にするか、api_key_nameを書いてその鍵を保存すれば、0.37.1でもOpenAIの鍵は届きませんでした。llm openai endpointで接続先を直接指定する場合も、--responsesと-o chat_completions 1を一緒に使わなければ、0.37.1で届いたのはDUMMY_KEYだけでした。
OpenAIの鍵を作り直すかは、接続先を誰が管理しているかで決める
当てはまる使い方をしていた場合、OpenAIの鍵はapi_baseの接続先へ届いています。コマンドが成功したか失敗したかでは、届いたかどうかを見分けられません。鍵を作り直すかは、接続先を誰が管理しているかで決めます。
自分だけが管理するサーバー
OpenAIの鍵は作り直さず、0.37.2へ上げる
手元のパソコンで動かすOpenAI互換のサーバーなどです。届いた鍵は自分の管理下にあります。リクエストのヘッダーを記録する設定にしていたら、その記録を消します。
他社のサービス、共有のサーバー、中継するプロキシー
OpenAIの鍵を作り直す
鍵は相手に届いています。社内の共有サーバーなど自分以外も管理する接続先や、暗号化しないhttp://で別のマシンへ送っていた接続先も、こちらに入れます。
成功と失敗の両方で鍵が届くことは、手元で確かめました。すべてのリクエストを401で断る検証用サーバーに向けると、0.37.1では、サーバーがダミーのOpenAIの鍵を受け取ったうえで、llmに「Error: Error code: 401 - Incorrect API key provided」と表示されました(この文言は検証用サーバーが返したもので、実際のサービスでは各社のエラー文になります)。llm logsで一覧しても、この実行は出てきませんでした。一方、鍵を確かめずに答える検証用サーバーでは、答えが普段どおり表示され、実行記録にも残りました。受け取った鍵でOpenAIへ中継するプロキシーのように、鍵を確かめずに答える接続先では、成功した実行でも鍵は相手に届いています。
他社のサービスなどへ届いた可能性がある場合は、OpenAIの管理画面で新しいAPIキーを作り、llm keys set openaiとOPENAI_API_KEYを新しい鍵に差し替えてから、古い鍵を削除します。シェルの設定ファイルなど、古い鍵を書いたほかの場所も差し替え、管理画面の利用状況に覚えのない利用がないかも確かめます。自分だけが管理するサーバーでも他社のサービスでも、0.37.2へ上げれば、この2つの使い方でOpenAIの鍵が送られることはなくなります。
参考資料
- llm 0.37.2のリリースノート(2026年11月12日、日本時間)
- GHSA-v9h7-jj4g-f862: api_key_name falls back to OPENAI_API_KEY and sends it to a custom api_base
- GHSA-r639-rr7r-x72w: OpenAI API key sent to custom api_base by the -o chat_completions 1 delegate
- 修正のコミット「Never send the OpenAI key to a custom api_base」(simonw/llm)
- llmの文書「OpenAI-compatible models」:
api_baseとapi_key_nameの説明 - llmの文書「Setup」: 更新のコマンドと
llm install -U llm、鍵の保存 - コマンドラインツールllmのembed-multiで日本語ファイルが文字化けしていた不具合|0.37での直し方と、作り直すファイルの見分け方(この記事の関連記事)
検証環境
- macOS 27.0.1(Apple Silicon)、Python 3.13.14(uv 0.11.32の仮想環境)
- llm 0.37.1と0.37.2(PyPI)、OpenAI Python SDK 3.49.0
- 検証日: 2026年11月12日
この記事は、AIが調査と下書きを担当し、上記の環境で実際に動かして結果を確かめ、下書きとは別の担当が公開前に本文と出典を確認しています。制作と訂正の方針は編集・訂正方針で公開しています。












