結論
- シェルスクリプトを
bash -x(またはset -x)で動かすと、変数を展開したあとのコマンドが標準エラーに出ます。source .envした値も、curl -H "Authorization: Bearer $API_KEY"のキーも、そのまま表示されました(2026-11-05、bash 5.2.37・ダミー値で実機確認)。 -
curl -H @headers.txtのようにヘッダーをファイルから読ませると、bash -xの表示はファイル名だけになりました。ただしcurl -vを付けると、ファイルから読んだヘッダーも> Authorization: Bearer ...と表示されました(curl 8.14.1)。 - AIエージェントにスクリプトのデバッグを頼むと、
bash -xの出力がそのまま会話に入ることがあります(推測。今回はエージェント経由では試していません)。スクリプトにset -xが残っていないかは、下の1行で確かめられます。
この記事は「AIエージェントと秘密情報」の検証シリーズです。新しい検証は X(@supportapp3_lab)でもお知らせしています。
試してみる(set -x が残っているスクリプトを探す)
プロジェクトのフォルダで実行すると、set -x・set -o xtrace・bash -x を書いた行のファイル名と行番号を出します。
grep -rnE --include='*.sh' 'set -x|set -o xtrace|bash -x' .
出てきたスクリプトで .env を読んだり、キーをコマンドに渡したりしていれば、実行ログに値が出ます。
公式の説明
bash 組み込みの help set(bash 5.2.37)には次のように書かれています。
-x Print commands and their arguments as they are executed.
Using + rather than - causes these flags to be turned off.
curl の公式マニュアルの -H, --header には、@filename 形式で渡すと「adds a header for each line in the input file」と書かれています。
実機で確かめたこと
値はすべてダミーです。.env は API_KEY=sk-dummy-0000-not-real の1行、送り先は手元の python3 -m http.server(127.0.0.1)です。
1. source .env と curl のヘッダー
set -a; source ./.env; set +a
curl -s -o /dev/null -H "Authorization: Bearer $API_KEY" http://127.0.0.1:18765/
bash -x a.sh の出力です。
+ set -a
+ source ./.env
++ API_KEY=sk-dummy-0000-not-real
+ set +a
+ curl -s -o /dev/null -H 'Authorization: Bearer sk-dummy-0000-not-real' http://127.0.0.1:18765/
.env の中の代入行も、展開後の curl の引数も、両方にキーが出ました。
2. $(cat .env) で読んだ場合
API_KEY=$(cat ./.env | cut -d= -f2)
export API_KEY
++ cut -d= -f2
++ cat ./.env
+ API_KEY=sk-dummy-0000-not-real
+ export API_KEY
コマンド置換の結果も、代入の行で値が出ました。
3. ヘッダーをファイルから読ませる
headers.txt に Authorization: Bearer sk-dummy-0000-not-real を書き、chmod 600 にしました。
curl -s -o /dev/null -H @headers.txt http://127.0.0.1:18765/
+ curl -s -o /dev/null -H @headers.txt http://127.0.0.1:18765/
bash -x に出るのはファイル名だけでした。
4. ただし curl -v には出る
$ curl -sv -o /dev/null -H @headers.txt http://127.0.0.1:18765/ 2>&1 | grep -i authorization
> Authorization: Bearer sk-dummy-0000-not-real
-H に直接書いた場合も、ファイルから読ませた場合も、-v の表示には送ったヘッダーがそのまま出ました(curl 8.14.1)。
5. 一部だけ止める(set +x)
set -a; source ./.env; set +a
{ set +x; } 2>/dev/null
curl -s -o /dev/null -H "Authorization: Bearer $API_KEY" http://127.0.0.1:18765/
set -x
echo done
+ set -a
+ source ./.env
++ API_KEY=sk-dummy-0000-not-real
+ set +a
+ echo done
{ set +x; } 2>/dev/null のあとの curl は出なくなりました。ただし止める前の source ./.env は出ています。止めるなら .env を読む前に止める必要があります。
AIエージェントとの関係(ここからは推測を含みます)
- AIエージェントは、コマンドの標準出力と標準エラーを読んで次の手を考えます。
bash -xで動かせば、上の出力がそのまま会話に入り、ログやクラウド側の会話記録にも残ると考えられます(推測)。会話記録の保存先は Claude Code の会話記録の記事で確かめています。 - 「動かないので
bash -xで見て」と頼むのはよくある使い方なので、.envを読むスクリプトでは特に注意が要ると考えられます(推測)。
対策
- 上の1行で、
set -xが残っていないか確かめる。 - デバッグで
set -xを使うときは、.envを読む前と、キーを渡すコマンドの前で{ set +x; } 2>/dev/nullで止める。 - curl のヘッダーは
-H @ファイルで渡す(bash -xには出なくなる)。ただし-vは付けない。 - AIに
bash -xの出力を貼るときは、キーが入っていないか先に見る。
ai-secret-check について
筆者が作った読み取り専用の CLI 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)
参考
※ 2026-11-05 に、Debian の使い捨てフォルダとダミー値で確かめました(bash 5.2.37、curl 8.14.1)。
更新情報やちょっとしたTipsは X(@supportapp3_lab)で発信しています。