結論
- Python の
requestsで通信をデバッグするとき、loggingを DEBUG にしただけならAuthorizationヘッダーの値は出ませんでした。出たのは"GET / HTTP/1.1" 200のような1行だけです(2026-11-09、Python 3.13.5・requests 2.34.2・urllib3 2.8.0、ダミー値で実機確認)。 - ところが
http.client.HTTPConnection.debuglevel = 1を足すと、send:の行にAuthorization: Bearer ...がそのまま出ました。 -
auth=(user, key)の Basic 認証も同じ行に出ます。Base64 なので、そのまま元の文字列に戻せます。 - 逆に、
params={"api_key": ...}のように キーを URL に入れると、debuglevelなしでも urllib3 の DEBUG ログに URL ごと出ました。 -
send:の行はloggingではなくprint()で標準出力に出ます。ログの設定でマスクしたつもりでも効きません。
この記事は「AIエージェントと秘密情報」の検証シリーズです。新しい検証は X(@supportapp3_lab)でもお知らせしています。
試してみる(デバッグ設定が残っていないか)
コードに debuglevel を上げる行が残っていないかを探します。
grep -rnE --include='*.py' 'debuglevel|set_debuglevel' .
キーを URL のクエリに入れている箇所も探せます(見つかっても、そのAPIがヘッダーでの指定に対応しているかは各APIの仕様を確認してください)。
grep -rnE --include='*.py' "params=.*(api_?key|token)|[?&](api_?key|token)=" .
何が問題か
401 エラーなどを調べるとき、「通信の中身を全部見たい」と debuglevel = 1 を入れる書き方はよく見かけます。この出力には、送ったヘッダーがそのまま入ります。
ターミナルに出た出力は、ログファイルや CI のログ、画面共有、AI エージェントに渡すエラー全文などに、そのまま混ざることがあります。Codex や Claude Code のようにコマンドを実行して出力を読むツールでは、その出力も会話に入ります。
再現手順
ローカルに小さな HTTP サーバーを立てて、ダミーのキーを送りました(外部には送っていません)。
import logging, sys, http.client, requests
URL = "http://127.0.0.1:18081/"
HDR = {"Authorization": "Bearer sk-dummy-0000"}
logging.basicConfig(level=logging.DEBUG, stream=sys.stdout,
format="%(name)s %(levelname)s %(message)s")
requests.get(URL, headers=HDR) # 1. DEBUG だけ
http.client.HTTPConnection.debuglevel = 1
requests.get(URL, headers=HDR) # 2. debuglevel=1
requests.get(URL, auth=("user", "sk-dummy-1111")) # 3. Basic 認証
(サーバー部分は省略しています。全体は http.server で 200 を返すだけのものです。)
1. logging を DEBUG にしただけ
urllib3.connectionpool DEBUG Starting new HTTP connection (1): 127.0.0.1:18081
urllib3.connectionpool DEBUG http://127.0.0.1:18081 "GET / HTTP/1.1" 200 None
ヘッダーは出ていません。
2. debuglevel = 1 を足す
send: b'GET / HTTP/1.1\r\nHost: 127.0.0.1:18081\r\nUser-Agent: python-requests/2.34.2\r\nAccept-Encoding: gzip, deflate, br, zstd\r\nAccept: */*\r\nConnection: keep-alive\r\nAuthorization: Bearer sk-dummy-0000\r\n\r\n'
reply: 'HTTP/1.0 200 OK\r\n'
Authorization: Bearer sk-dummy-0000 がそのまま出ました。
3. Basic 認証(auth=)
send: b'GET / HTTP/1.1\r\n...\r\nAuthorization: Basic dXNlcjpzay1kdW1teS0xMTEx\r\n\r\n'
$ echo -n 'user:sk-dummy-1111' | base64
dXNlcjpzay1kdW1teS0xMTEx
Base64 は暗号ではないので、出力からそのまま user:sk-dummy-1111 に戻せます。
4. キーを URL のクエリに入れた場合(debuglevel = 0)
requests.get(URL, params={"api_key": "sk-dummy-2222"})
urllib3.connectionpool http://127.0.0.1:18082 "GET /?api_key=sk-dummy-2222 HTTP/1.1" 200 None
debuglevel を上げていなくても、DEBUG ログの1行に URL ごと出ました。
5. send: は標準出力、ログは標準エラー
ログの出力先を標準エラーにして、標準出力と分けて実行しました。send: 以下の行は標準出力側に出ました。
[stdout] send: b'GET / HTTP/1.1\r\n...Authorization: Bearer sk-dummy-3333\r\n\r\n'
[stderr] urllib3.connectionpool http://127.0.0.1:18082 "GET / HTTP/1.1" 200 None
手元の Python 3.13.5 の http/client.py でも、print("send:", repr(data)) と書かれていました。logging の Filter でマスクしても、この行には効きません。
対策
-
debuglevel = 1は調べ終わったら必ず消す。コミット前に上のgrepで確認する。 - どうしても通信を見たいときは、ダミーのキーか、すぐ無効にできる一時的なキーで試す。
- キーは URL のクエリではなく、ヘッダーで渡す(API が対応している場合)。クエリは DEBUG ログに URL ごと出ました。
- デバッグ出力を AI に貼ったりログとして保存したりする前に、
Authorizationやapi_keyの行がないか見る。 - AI にデバッグを頼むときは「
debuglevelは使わない」「キーをログに出さない」と条件を付ける。
ai-secret-check について
ai-secret-check scan は、AIが読めそうな場所にある .env 系のファイル名を探します(中身は表示しません)。ログやコードの中のキーまでは見ないので、上の grep とセットで使ってください。
npx github:supportapp3-lab/ai-secret-check scan
リポジトリ: https://github.com/supportapp3-lab/ai-secret-check (Node.js 20以上、MIT)
参考
- Python 公式ドキュメント: http.client(
HTTPConnection.set_debuglevel) https://docs.python.org/3/library/http.client.html#http.client.HTTPConnection.set_debuglevel - Python 公式ドキュメント: base64 https://docs.python.org/3/library/base64.html
- 関連: bash -x で鍵が出る / printenv / env で鍵が全部出る / 引数の鍵は ps から見える
※ 2026-11-09 に、Debian 13・Python 3.13.5・requests 2.34.2・urllib3 2.8.0、ダミー値・ローカルサーバーで確かめました。
更新情報やちょっとしたTipsは X(@supportapp3_lab)で発信しています。