結論
-
export OPENAI_API_KEY=sk-...のようにキーをコマンドに直接書くと、bash はその行を~/.bash_historyに平文で保存します。curl -H "Authorization: Bearer ..."も同じでした(筆者の環境で確認)。AIエージェントがファイルを読める場所なら、履歴ファイルも読める場所です。 -
行の先頭にスペースを入れると残らないのは、
HISTCONTROLにignorespace(またはignoreboth)が入っているときだけです。設定がないシェルでは、スペースで始まる行もそのまま残りました(bash公式・筆者の環境)。 - 打ってしまった直後なら
history -d -2で消せます。-1はhistory -d自体を指すので、history -d -1ではキーの行は消えませんでした。キーはread -rsで入力すると履歴に残りません(筆者の環境で確認)。
この記事は「AIエージェントと秘密情報」の検証シリーズです。新しい検証は X(@supportapp3_lab)でもお知らせしています。
AIエージェントのCLIを試すとき、「とりあえず export でAPIキーを入れる」ことがよくあります。.env を読ませない設定をしていても、同じキーがシェルの履歴ファイルに平文で残っていれば意味が薄れます。どの打ち方で残り、どうすれば残らないかを bash で確かめました。
検証環境と範囲
- Debian 13.7 / GNU bash 5.2.37
- 一時ディレクトリを
HOMEにし、HISTFILEを指定した対話シェル(bash --norc -i)にコマンドを流し込み、終了後に履歴ファイルの中身を見ました。 - キーはすべてダミーの文字列です。zsh・fish は試していません。
1. export で直接書いたキーは、そのまま残る
export DEMO_API_KEY=sk-dummy-AAAA1111
終了後の履歴ファイル:
export DEMO_API_KEY=sk-dummy-AAAA1111
echo done
exit
| 打ち方 | 履歴ファイルに残ったか |
|---|---|
export DEMO_API_KEY=sk-dummy-... |
残った |
curl -H "Authorization: Bearer sk-dummy-..." ... |
残った |
先頭スペース+HISTCONTROL=ignorespace
|
残らない |
先頭スペース+HISTCONTROL なし |
残った(先頭のスペースごと) |
read -rs DEMO_API_KEY で入力 |
残らない(read -rs DEMO_API_KEY という行だけ残る) |
bash -c '...' の非対話シェル |
履歴ファイル自体が作られない |
最後の行のとおり、AIエージェントが bash -c のような形で実行したコマンドは、この検証では履歴ファイルに入りませんでした。ただしエージェントによっては、実行したコマンドを自分の会話記録に保存します(Claude Code の例はこちらの記事)。
2. 先頭スペースが効くのは設定があるときだけ
bash 公式マニュアルでは、HISTCONTROL に ignorespace が含まれるとき、スペースで始まる行を履歴に保存しない、と説明されています。ignoreboth は ignorespace と ignoredups をまとめた指定です(Bash Variables)。
Debian の /etc/skel/.bashrc には HISTCONTROL=ignoreboth が書かれていました。これは新しいユーザーの .bashrc のひな形ですが、.bashrc を読まないシェルや、別の内容の .bashrc を使う環境では効きません。自分のシェルで効いているかは、次で確かめられます。
echo "$HISTCONTROL"
ignorespace か ignoreboth が出なければ、先頭スペースでは防げません。
3. 打ってしまったら、終了前に history -d -2
bash は、シェルを終了するときに履歴を $HISTFILE に書き出します(Bash History Facilities)。実際、シェルの実行中は履歴ファイルにキーの行はなく、終了後に入りました。
そのため、打った直後なら履歴の一覧から消せば、ファイルには書かれません。
export DEMO_API_KEY=sk-dummy-GGGG7777
history -d -2
終了後の履歴ファイルは history -d -2 と exit の2行だけでした。
history -d -1 では消えませんでした。マニュアルでは負の数は末尾から数え、-1 は今打った history -d 自体を指します(Bash History Builtins)。
すでにファイルに書かれてしまった場合は、すべての端末を閉じてから履歴ファイルを編集します。開いたままのシェルがあると、終了するときにそのシェルの履歴でファイルが書き直されます(マニュアルの「終了時に書き出す」動きから考えた推測で、複数端末の組み合わせは試していません)。漏れた可能性があるキーは再発行するのが確実です。
4. そもそも打たない:read -rs
キーを画面にも履歴にも出さずに環境変数へ入れるには、read を使います。
read -rs OPENAI_API_KEY # 入力しても表示されない
export OPENAI_API_KEY
履歴に残るのは read -rs OPENAI_API_KEY と export OPENAI_API_KEY の行だけで、キーの値は残りませんでした。
5. AIエージェントに履歴ファイルを読ませない
Claude Code では、Read の deny ルールにホームからのパス(~/)を書けます(Configure permissions)。
{
"permissions": {
"deny": [
"Read(~/.bash_history)",
"Read(~/.zsh_history)"
]
}
}
公式によると、Read の deny は Claude の組み込みのファイルツールと、Bash で cat などファイル名を指定するコマンドには効きますが、Python スクリプトのように中でファイルを開く処理には効きません。確実に止めるにはサンドボックスを使うよう案内されています。.env の deny の書き方はこちらの記事にまとめています。
6. 履歴に残っていないか確かめるスクリプト
値は表示せず、ファイル名と行番号だけを出します。キーらしい文字列の判定は目安で、見落としも誤検知もあります。
#!/usr/bin/env bash
# シェル履歴に秘密らしい文字列が残っていないかを調べる(値は表示しない)
for f in "$HOME/.bash_history" "$HOME/.zsh_history" "$HOME/.local/share/fish/fish_history" "$HOME/.python_history" "$HOME/.psql_history" "$HOME/.mysql_history"; do
[ -f "$f" ] || continue
grep -nE '(API_KEY|TOKEN|SECRET|PASSWORD|PASSWD)[A-Z_]*=|Authorization: *Bearer|sk-[A-Za-z0-9_-]{8,}|ghp_[A-Za-z0-9]{20,}|github_pat_|AKIA[0-9A-Z]{16}|xox[bp]-' "$f" \
| cut -d: -f1 | sed "s|^|$f: 行 |"
done
echo "確認おわり(何も出なければ該当なし)"
ダミーの履歴で試した出力:
/tmp/tmp.XXXX/.bash_history: 行 2
/tmp/tmp.XXXX/.bash_history: 行 4
確認おわり(何も出なければ該当なし)
出た行は sed -n '2p' ~/.bash_history などで自分の目で確かめてください(画面に値が出るので、画面共有中は避けます)。
まとめ
-
exportやcurl -Hに直接書いたキーは~/.bash_historyに平文で残る(実機)。 - 先頭スペースで防げるのは
HISTCONTROLにignorespace/ignorebothがあるときだけ(実機・bash公式)。 - 打った直後なら
history -d -2。-1では消えない(実機・bash公式)。 - キーは
read -rsで入れる。AIエージェントには履歴ファイルも読ませない。
AIエージェントの手元で .env が読めるかどうかは、ai-secret-check(npx github:supportapp3-lab/ai-secret-check scan、値は表示せず通信もしません)で点検できます。シェル履歴はまだ点検対象ではありません。
関連記事:
参考(公式):
- Bash Variables(HISTCONTROL)
- Bash History Builtins(history -d)
- Bash History Facilities
- Configure permissions(Claude Code)
※ 仕様は 2026-10-23 時点の公式ドキュメントに基づきます。検証は GNU bash 5.2.37 で、Linux の使い捨て環境でのみ行いました。
更新情報やちょっとしたTipsは X(@supportapp3_lab)で発信しています。