Claude Code を使っていると、コマンドを実行する前に確認が出ます。
表示されたコマンドをひととおり眺めて Yes を押す操作を、毎日何度も繰り返しているかと思います。
そこで気になったのが、自分は本当にあれを読めているのか、ということでした。
一見問題ないように見えても、実は危険なコマンドを実行しようとしている時はあるかもしれません。
また、注意するポイントが偏ってしまっていることにも気付きました。
そこで、承認プロンプトを模したコマンドを5つ用意しました!
この記事はその5問のクイズです。
はじめに
承認プロンプトとは、たとえば以下のように表示です。
Bash command
│ rm -rf xxxxx
Ask rule Bash(rm -rf *) overrides auto mode for this command.
/permissions to let auto mode decide
Do you want to proceed?
❯ 1. Yes
2. No
Esc to cancel · Tab to amend · ctrl+e to hide
あらかじめ確認するよう指定しておいたコマンド(settings.json の ask) や、そのまま実行してよいか Claude Code 側の auto mode でも判断がつかないコマンドは、実行前に確認が入ります。
このとき画面に出るのは、実行しようとしているコマンドと、引っかかったルールの名前です。
ここで見落としやすいのが、ルール名が指しているのは「ルールに触れた場所」であって「危ない場所」ではないということ。
たとえば Bash(rm -rf *) というルール名が表示されても、そのコマンドで一番危ないのが rm -rf の行だとは限りません。
とはいえ、確認が入る仕組みそのものは、きちんと働いています。
そのあと画面をどう読むかが、使う側に残っている仕事です。
なお、この画面から引き出せる情報はもう一段あります。
そちらは5問を解いたあとに置きます。
第1問
ビルド用のフォルダを掃除してから、ビルドし直すコマンドです。
BUILD_DIR=$(cat .buildrc)
echo "cleaning $BUILD_DIR"
rm -rf "$BUILD_DIR"/*
mkdir -p "$BUILD_DIR"
npm run build
この承認、Yes を押しますか?
答えと解説
押しません。
危ないのは3行目です。
ただし理由は1行目にあるので、上から順に見ます。
BUILD_DIR=$(cat .buildrc) # 1行目
rm -rf "$BUILD_DIR"/* # 3行目
1行目は「.buildrc というファイルの中身を読んで、BUILD_DIR という名前で覚えておく」という意味の行です。
ここで .buildrc が無かったり、中身が空だったりすると、BUILD_DIR は空っぽになります。
3行目の rm -rf は、指定した場所をフォルダごと、中身ごと、確認なしで消すコマンドです。
ごみ箱に入らないので、消えたら戻りません。
末尾に付いている * は「その中にあるもの全部」という意味になります。
その rm -rf に渡っているのが "$BUILD_DIR"/* です。
BUILD_DIR が空だと、ここは単なる /* になります。
# .buildrc が無い / 中身が空 のとき、実際に走る行
rm -rf /*
/ はパソコンの一番上の階層なので、そこにあるものを片端から消しにかかります。
ここまでの3行に、危ない文字列はどこにも出ていません。
"$BUILD_DIR" と書いてあるだけで、それが何に置き換わるかは画面からは決まらないからです。
ただ、上から読めば「3行目が何を消すのか、この画面だけでは決まらない」とは分かります。
それが分かること自体が、Yes を押さずに一度止まる理由になります。
第2問
CLI ツールを入れて、初期設定まで走らせます。
rm -rf ./node_modules/.cache
curl -fsSL https://get.example-cli.dev/install.sh | sh
export PATH="$HOME/.example-cli/bin:$PATH"
example-cli init
この承認、Yes を押しますか?
答えと解説
押しません。
危ないのは2行目です。
curl -fsSL https://get.example-cli.dev/install.sh | sh
1行目の rm -rf が消しているのは、キャッシュの置き場です。
消えても次のビルドで作り直されるので、実害はありません。
問題は2行目です。
ここは3つに分けて読みます。
-
curlは、指定した URL からファイルを取ってくるコマンド -
|(パイプ)は、左のコマンドの出力を右のコマンドに流し込む記号 -
shはシェルで、受け取った文字列をそのままコマンドとして実行する
つなげると、知らないサーバーからスクリプトを受け取って、中身を読まずに実行するという1行になります。
そのスクリプトの中で何が行われても、この Yes 一回で通ります。
1行目の rm -rf は安全な側で、危ないのは2行目のほうでした。
第3問
開発用の設定ファイルを用意して、データベースを初期化する場面。
cp .env.example .env
echo "DATABASE_URL=postgres://localhost:5432/app_dev" > .env
npm run db:setup
この承認、Yes を押しますか?
答えと解説
押しません。
危ないのは1行目です。
cp .env.example .env
rm も delete も出てきませんが、手元の .env は消えます。
cp A B は「A を B という名前でコピーする」コマンドです。
このとき B がすでにあっても、確認なしで上書きします。
.env には自分で入れた API キーやデータベースのパスワードが入っていることが多いので、それが .env.example(空欄のテンプレート)の中身に置き換わってしまいます。
echo "DATABASE_URL=postgres://localhost:5432/app_dev" > .env
そして2行目の > も上書きの記号です。
>> なら末尾に書き足すところですが、> は中身を全部捨ててから書き直します。
つまり1行目を切り抜けたとしても、2行目でもう一度潰されます。
しかも .env は git の管理から外してあるのが普通なので、git では戻せません。
第4問
開発用の URL を、本番の URL に一括で置き換えます。
grep -rl "localhost:3000" . | xargs sed -i '' 's|localhost:3000|api.example.com|g'
npm run build
この承認、Yes を押しますか?
答えと解説
押しません。
危ないのは1行目です。
grep -rl "localhost:3000" . | xargs sed -i '' 's|localhost:3000|api.example.com|g'
やろうとしていること自体は、ただの文字列の置き換えでしかありません。
ただ、その対象範囲がどこにも指定されていないのが問題です。
1行目は3つのコマンドをつないだものです。
-
grep -rl "文字列" .は、いまいる場所から下のフォルダを全部たどって、その文字列を含むファイルの一覧を出す -
xargsは、その一覧を次のコマンドへ順番に渡す -
sed -iの-iは、渡ってきたファイルをその場で書き換える指定。バックアップは残らない
. から下をたどるので、.git/(git がリポジトリの履歴を持っているフォルダ)も node_modules/(ライブラリの置き場)も対象に入ります。
除外の指定は、どこにも書かれていません。
その結果、.git/ の中のログや、node_modules/ に入っているライブラリの中身まで書き換わります。
自分が書いた覚えのないファイルが黙って変わるので、あとから「なぜか動かない」という形で出てきます。
第5問
配布前のパッケージが読み込めるかどうかを、使い捨ての場所で試すコマンドです。
mkdir -p /tmp/verify && cd /tmp/verify
npm install --ignore-scripts ../pkg.tgz
node -e "require('./node_modules/pkg')"
rm -rf /tmp/verify
この承認、Yes を押しますか?
答えと解説
押しません。
危ないのは3行目です。
node -e "require('./node_modules/pkg')"
この4行、一見するとかなり気をつけて書かれています。
/tmp の下に使い捨ての作業場所を作り、最後の行できちんと片付けもしています(&& は「左が成功したら右を実行する」という意味です)。
npm install --ignore-scripts ../pkg.tgz
2行目の --ignore-scripts は「インストールのときにパッケージが仕込んだスクリプトを走らせない」という指定で、これ自体は正しい安全策です。
ところが3行目で、そのパッケージを読み込んでいます。
node -e "..." は「続く文字列を JavaScript としてその場で実行する」という書き方で、ここでは require を呼んでいます。
require はファイルを開くだけの操作ではありません。
読み込んだファイルの中身を、その場で実行します。
つまり --ignore-scripts で止めたはずのスクリプトが、ここで動きます。
安全策が並んでいると、そこで安心して残りを読み飛ばしてしまいます。
なお4行目の rm -rf /tmp/verify は自分で作った作業場所の片付けなので、こちらは問題ありません。
5問に共通していたこと
5問を「rm -rf が出てきたか」と「実際に危なかったのはどこか」の2点で並べると、こうなります。
| 問 | rm -rf |
危なかったのは |
|---|---|---|
| 第1問 | あり | 空になった BUILD_DIR
|
| 第2問 | あり(無害) | スクリプトの丸呑み実行 |
| 第3問 | なし |
cp と > の上書き |
| 第4問 | なし | 範囲の無指定 |
| 第5問 | あり(無害) |
require での実行 |
rm -rf があるかどうかと、危ないかどうかは一致していません。
第2問と第5問の rm -rf はどちらも無害で、第3問と第4問には rm が一文字も出てきません。
長さも関係ありませんでしたよね。
たとえば、第3問はたった3行です。
危ないものは、長さでも派手さでもなく、見慣れた形の中に紛れています。
rm -rf を見張る習慣は役に立ちますが、それだけでは第3問と第4問を素通りしてしまいます。
ここに挙げた型のいくつかは、私も目の前に出されたら気づかずに Yes を押すかもしれません...!
そもそも何を確認対象にして何を禁止するか、設定そのものの組み立てはこの記事では扱いませんでした。
パーミッションの設計をもう少し体系的に見たい場合は、拙著『Claude Codeで作って学ぶ AI駆動アプリ開発入門』(技術評論社、2026年9月8日発売)の第3章で扱っています。
自分で読んでから、Ctrl+E で裏を取る
見慣れた形に紛れている以上、引っかかったと表示された行だけを見ても足りません。
承認プロンプトが出たら、ブロック全体を上から下まで読む。
順番として、これが先です。
そのうえで判断がつかないときに使えるのが、冒頭で触れた「もう一段」です。
承認プロンプトで Ctrl+E を押すと、そのコマンドが何をするのかの説明と、Low / Med / High のリスク判定が出ます。
押しただけでは実行されないので、迷ったときに開いて構いません。
ただし、いつでも説明が出るわけではありません。
Explanation unavailable と表示されて、何も情報が出ないこともあります。
とはいえ、読むのにかかるのは数秒ですし、スルーして誤って消えたファイルは戻りません。
だから Ctrl+E は最後の裏取りに置いて、自分の目を先に通しておくほうが確実です。
こればっかりは基礎力に近いですが、いま一度、注意を向けてみてはいかがでしょうか?
Xをフォローいただけると嬉しいです!
AI駆動開発(特に Claude Code)のノウハウや Tips をよく発信しています!