Claude Codeがpsqlやsshの実行許可を求めてきたとき、「Yes, and don’t ask again」を選ぶと、そのコマンドは文字列のまま.claude/settings.local.jsonに保存されます。コマンドにパスワードが入っていれば、パスワードも一緒に残ります。
厄介なのは、自分のPCでgit statusを見ても、このファイルが表示されないことです。Claude Codeが、このPCのgitだけに「無視する」設定を入れているためです。除外ルールのない別のPC、コンテナ、CIでgit add -Aすると、そのままコミットされました。一時リポジトリで再現した結果と、手元のリポジトリを1分ほどで点検するスクリプトを載せます。
確認しておくことは3つです。ファイルがgitで追跡されているか、過去のコミットに残っていないか、許可ルールに秘密値が入っていないか。見つかった場合は、ファイルを消すより先に、そのパスワードやトークンを無効にして作り直します。
公開GitHubの1,122ファイルに、本物のパスワードやキーが残っていた
セキュリティ企業Apiiroの調査を報じたCyber Security Newsの11月7日の記事によると、公開されている.claude/settings.local.json104,185ファイルのうち、1,122ファイルに実在の秘密値が入っていました。異なる秘密値は1,379件です。過去のコミットや会話記録まで含めると、1,994リポジトリで2,539件にのぼります。
中身はデータベースのパスワードが379件、Supabaseのservice-roleキーが122件、SSHのログイン情報が43件などです。悪用されたかどうかは、記事では示されていません。
TruffleHogは643ファイル、Gitleaksは564ファイルを検出し、215ファイルはどちらの検出ツールも見逃しました。PGPASSWORD=のあとに書いたパスワードは、サービスごとに決まった形式をもつキーと違い、検出ツールが見分けにくいためです。この問題自体は新しくありません。2025年8月には、認証情報を含むコマンドが平文で保存されるという報告(Issue #5544)が出ています。
自分のPCでgit statusを見ても、ファイルが表示されない理由
Claude Codeの設定に関する公式ドキュメントには、Claude Codeがこのファイルを初めて書いたとき、**/.claude/settings.local.jsonをgitのグローバル除外ファイルに追加すると書かれています。core.excludesFileを設定していないmacOSでは、~/.config/git/ignoreです。私のMacでも、このファイルに同じ1行が入っていました。
この除外ルールはPCごとに入るもので、リポジトリには含まれません。私のDesktop配下にあるsettings.local.json50個を調べると、41個はこのPCのグローバル除外だけで隠れていて、リポジトリの.gitignoreには書かれていませんでした。今のところ追跡されているファイルはありません。それでも、別の環境で作業すれば守られない状態です。
同じドキュメントには、ファイルを手で作った場合は自分で.gitignoreに足すようにとも書かれています。また、VS Code拡張では承認時に保存先を選べて、チームで共有する.claude/settings.jsonも選べます。共有ファイルに保存されてしまうという報告(Issue #77530)もVS Code上のものでした。CLIは、ローカルのファイルにだけ書き込みます。
一時リポジトリで再現:コミットされたのは、除外ルールがない環境と一度追跡されたとき
macOS、Claude Code 2.1.318、git 2.50.1で一時リポジトリを作り、ダミーのパスワードを書いたsettings.local.jsonと、共有用のsettings.jsonを置きました。除外ルールのない環境は、core.excludesFileを/dev/nullにして再現しています。
- このPCの通常の
git statusでは、settings.jsonだけが表示され、settings.local.jsonは隠れた。 - 除外ルールのない環境では、両方が表示された。
- その環境で
git add -Aすると、両方がコミット対象に入った。 - 一度コミットされたファイルは、このPCで書き換えても変更として表示された。無視の設定は、追跡済みのファイルには効かない。
git rm --cachedで追跡をやめてコミットしたあとも、過去の2コミットにダミーのパスワードが残っていた。- リポジトリの
.gitignoreに書くと、除外ルールのない環境でも隠れた。
つまり、このPCで作業を続けるだけなら、普段は表示されません。危ないのは、Claude Codeが入っていない環境で作業するとき、Claude Codeがファイルを書く前に手で作ったとき、すでに一度コミットしてしまったときです。とくに3つ目は、いま見えていないので気づけません。
まずはリポジトリで3行:別のPCからどう見えるか
点検したいリポジトリのルートで実行します。1行目は、除外ルールのないPCから.claudeがどう見えるかを表示します。?? .claude/settings.local.jsonと出たら、そのPCではgit add -Aでコミットされます。2行目に何か出たら追跡済み、3行目に何か出たら過去にコミットされています。
git -c core.excludesFile=/dev/null status --porcelain --untracked-files=all -- .claude
git ls-files .claude
git log --all --oneline -- .claude/settings.local.json .claude/settings.json複数のリポジトリをまとめて調べるスクリプト
フォルダの下にある.claude/settings.jsonと.claude/settings.local.jsonを探し、追跡の有無、今の中身と過去のコミットに含まれる「疑わしい箇所」の数、効いている無視ルールを1行ずつ出します。秘密値そのものは表示しません。scan.shとして保存し、bash scan.sh ~/Projectsのように探すフォルダを渡します。
#!/bin/bash
# 使い方: bash scan.sh [探すフォルダ](既定は現在のフォルダ)。秘密値そのものは表示しない
ROOT="${1:-.}"
PAT='PGPASSWORD=|MYSQL_PWD=|sshpass -p|mysql[^"]* -p[^ "]|Authorization: *(Bearer|Basic)|[Tt]oken[=: ]|api[_-]?key[=: ]|AKIA[0-9A-Z]{16}|gh[pousr]_[A-Za-z0-9]{20,}|://[^/ :"]+:[^@/ "]+@'
find "$ROOT" -type f -path '*/.claude/settings*.json' -not -path "$HOME/.claude/*" 2>/dev/null | while read -r f; do
d=$(dirname "$(dirname "$f")"); now=$(grep -Eo "$PAT" "$f" | wc -l | tr -d " "); tracked=- ; past=- ; ign=-
if git -C "$d" rev-parse --git-dir >/dev/null 2>&1; then
b=$(basename "$f")
git -C "$d" ls-files --error-unmatch ".claude/$b" >/dev/null 2>&1 && tracked=yes || tracked=no
past=$(git -C "$d" log --all -p -- ".claude/$b" 2>/dev/null | grep -E '^\+' | grep -Eo "$PAT" | wc -l | tr -d " ")
ign=$(git -C "$d" check-ignore -v ".claude/$b" 2>/dev/null | cut -d: -f1); ign=${ign:-なし}
fi
echo "$f 追跡:$tracked 履歴の疑わしい箇所:$past 現在の疑わしい箇所:$now 無視ルール:$ign"
done私のDesktop配下では51ファイルを34秒で調べ終え、該当はありませんでした。ダミーで試すと、まだコミットしていないファイルでは「現在の疑わしい箇所:5」、一度コミットしてから中身を消したファイルでは「追跡:yes 履歴の疑わしい箇所:5 無視ルール:なし」と表示されました。追跡済みのファイルにはgit check-ignoreが反応しないので、無視ルールは「なし」と出ます。
検出に使う文字列のパターンは、報道で漏れていた種類を拾えるよう広めにしています。国内の解説記事で紹介されていた--password|AKIA|sk-|ghp_で同じダミーを調べると、PGPASSWORD=、sshpass -p、Authorization: Bearerヘッダー、mysql -pの直後に書いたパスワード、URLに埋め込んだ認証情報の5種類をすべて見逃しました。このスクリプトは5種類とも検出し、npm testには反応しませんでした。
ただ、パターンで探す以上、漏れは残ります。GitleaksやTruffleHogを使っているなら、併用してください。私はどちらも導入していないので、このスクリプトとの比較はしていません。
見つかったら、ファイルを消す前にパスワードを無効にする
公開リポジトリに一度でも入っていたなら、すでに誰かに見られたと考えて動きます。再現の5番目のとおり、ファイルを消しても履歴に残ります。forkやclone、検索サービスのキャッシュにも残るため、履歴を書き換えても取り戻せません。
- 漏れたパスワードやトークンを変更・失効させ、新しく作り直す。データベースのパスワード、SSHの鍵、各サービスのAPIキーなど種類ごとに行う。
- 使える範囲で、そのキーを使ったアクセスの記録を確かめる。
- 追跡をやめ、リポジトリの
.gitignoreに追加する(次のコマンド)。 - 手元の
settings.local.jsonからも、古い秘密値を含む許可ルールを削除する。
git rm --cached .claude/settings.local.json
echo '.claude/settings.local.json' >> .gitignore
git add .gitignore
git commit -m "Stop tracking Claude Code local settings"非公開リポジトリだけで済んでいても、リポジトリを読める人全員と、そのリポジトリを取り込むCIにパスワードが渡っています。重要度が高いものから作り直すのが無難です。
「Yes, and don’t ask again」は、秘密値のないコマンドにだけ使う
私はこの機能をやめるべきだとは考えていません。npm testや決まったスクリプトの実行のように、中身が変わらず秘密値も含まないコマンドなら、毎回承認する手間が省けます。私の手元のsettings.local.jsonにも、実行IDまで含めたコマンドがそのまま保存されていました。保存されるのは、承認したそのコマンドの文字列だと考えてください。
パスワードやトークンを含むコマンドが出てきたら、「Yes」(今回だけ許可)を選ぶか、断ってから、パスワードをコマンドに書かない方法に変えるよう指示します。データベースなら~/.pgpass、APIなら環境変数や秘密情報を管理するツールから読み込む方法があります。変数名だけを書いたコマンドでどう保存されるかは、まだ実機で確かめていません。そのため、承認を残す前に一度settings.local.jsonを開いて中身を確認してください。
チームで使うリポジトリには、.claude/settings.local.jsonを.gitignoreに入れておきます。それだけで、メンバーのPCやCIに除外ルールがあるかどうかに左右されなくなります。共有の.claude/settings.jsonは意図してコミットするファイルなので、レビューのときにpermissions.allowへ秘密値が混ざっていないかを見てください。
Claude CodeやCodexの設定ファイルには、本人が気づかないうちに承認の記録が残ります。プラグインを消しても承認の記録が残っていた例は、Postmanが入れたClaude Code・Codexプラグインの確認方法と消し方にまとめています。
