ある朝、個人開発のデータ分析基盤(docker compose と MySQL 8.0 で構成)を起動したところ、アプリは正常に立ち上がるのに、データベースの中身が空になっていました。テーブルが 1 つも残っていません。原因を追うと、データを保存していた named volume が 2 本とも消えていました。

私たちが最初に疑ったのは、開発作業の一部を任せていた AI コーディングエージェントでした。折しも、AI エージェントが本番データベースを壊す事故が実際に報じられています。2025 年 7 月には Replit の AI 開発アシスタントが本番データベースを削除した事件が広く報道され(出典: Fortune、2025 年 7 月 23 日)、2026 年 4 月には Cursor(Claude Opus 4.6 搭載)が Railway 上の volume を削除し、本番データとバックアップを同時に失った事例が報じられました(出典: Tech Startups、2026 年 4 月 28 日)。同種の事故が直近に存在する以上、AI を疑うのは不自然ではありません。

しかし調査の結論を先に述べます。当初の「AI が該当の破壊的コマンドを実行した」という疑いは、否定されました。ただし、それで AI を潔白だと証明できたわけではなく、結局、最後に volume を消したのが誰なのかは特定できませんでした。この記事は、その調査の記録と、調査の過程で見つかった 3 つの穴の共有です。

この記事の要点

  • 事象: 個人開発のデータ分析基盤で named volume が 2 本同時刻に消え、DB が 0 テーブルになった。
  • 結論: 当初疑った「AI が該当の exact コマンドを実行した」筋は否定された(その deny は事件の 1 か月以上前から有効だった)。ただし deny 自体に後述の穴があり、AI を含め実行者・実行時刻の特定には至っていない。
  • 教訓 A: 完全一致の deny リストは表記ゆれで素通りする。
  • 教訓 B: シェル履歴を永続化していないと事後調査は成立しない。
  • 教訓 C: 危険コマンドを止めるガードと、実行を記録する監査ログは別物である。

消えた volume は、何事もなく空のまま再作成される

named volume が消えても、システムは「壊れた」ようには見えません。今回の症状も、docker compose up は正常終了し、アプリケーションも起動し、エラーも出ませんでした。ただデータベースだけが空だったのです。

この不気味さには技術的な理由があります。存在しない名前の volume を指定してコンテナや docker compose up を起動すると、Docker はその名前で新しい volume を自動作成します。既存データがなければ、空の volume として初期化されます(出典: Docker 公式ドキュメント「Volumes」、2026-07-08 取得)。つまり、named volume が何らかの理由で消えた後に起動し直すと、同名の空の volume が何事もなかったかのように再作成され、コンテナ自体は正常に立ち上がります。

「削除された」というより「静かに初期化された」に近い状態です。この種の消失に気づけるのは、たいてい中身を見に行ったときです。今回も、アプリの動作ではなくデータの中身で異常に気づきました。

docker volume inspect の CreatedAt が秒単位で一致していた

最初に確認したのは docker volume inspect の出力です。

docker volume inspect <volume名> --format '{{ .CreatedAt }}'

Docker Engine API の Volume 型定義では、CreatedAt フィールドは「volume が作成された日時」と定義されています(出典: Docker Engine API の Volume 型定義(pkg.go.dev)、2026-07-08 取得)。なお、docker CLI リファレンスのページ本文にはこのフィールドの意味を説明する文章がなく、JSON 出力例に値が示されているだけなので、定義の出所は Engine API 側です。

消えた 2 本の volume の CreatedAt は、秒単位で完全に一致していました。CreatedAt が作成日時である以上、2 本の作成日時が秒まで揃っているということは、この 2 本が同時刻に再作成されたことを意味します。今回のように「消えた後に空で作り直される」挙動を踏まえると、これは 2 本が同時に消え、同時に作り直されたことを示す痕跡と読めます。

ここから分かるのは、単発の誤操作ではないということです。1 本だけを狙った docker volume rm のような個別削除では、2 本の作成日時が秒単位で揃う説明がつきません。docker compose down -v のような一括操作、あるいは環境ごとの作り直しといった、2 本を同時に巻き込む操作が起きたと考えるのが自然でした。ただし「なぜ同時に作り直されたのか」という原因そのものは、この時点では特定できていません。

journald には同時刻の docker プロセスの痕跡が残っていた

次に、システム側のログを見ました。今回の環境は systemd を有効化した WSL2 だったため、journald(systemd のログ収集機構)が使えました。systemd 管理下のサービスは、既定で標準出力・標準エラーが journal に接続された状態で起動されます(出典: systemd-journald.service(8) man page、2026-07-08 取得)。WSL2 で systemd を使うには /etc/wsl.conf での明示的な有効化が必要で、既定では有効になっていません(出典: Microsoft Learn「Use systemd to manage Linux services with WSL」、2026-07-08 取得)。

CreatedAt で分かった時刻を挟む形で journald を絞り込みます。

journalctl --since "YYYY-MM-DD HH:MM:00" --until "YYYY-MM-DD HH:MM:59"

すると、同時刻に docker compose のプロセスと、コンテナランタイムやオーバーレイファイルシステムまわりの再作成の痕跡が残っていました。volume が同時刻に作り直されたという docker volume inspect の読みと、時刻が符合します。

ここで一つ注意点があります。これは今回の環境で追えたという個別の事実です。journald が dockerd や docker compose の操作をどこまで拾えるかは、docker の導入方式に依存します。たとえば Docker Desktop for Windows を使っている場合、dockerd は利用者の WSL2 ディストリビューションとは別の内部 VM で動くため、利用者側で systemd を有効化しても Docker Desktop 側のログには反映されない、という報告もあります。「WSL2 で systemd を有効にすれば必ず docker の操作が journald で追える」と一般化はできません。だからこそ、自分の環境で journalctl に何が残るかは、障害が起きる前に一度確認しておくことをおすすめします。

AI エージェントを疑ったが、当初の筋は成り立たなかった

私たちは AI コーディングエージェントに Bash の実行を任せていました。破壊的なコマンドを打ったのではないか、というのが当初の仮説です。

検証のため、エージェントの設定を確認しました。該当する破壊的コマンドは、deny リストに登録済みでした。さらに git の履歴をたどると、その deny 登録は事件の 1 か月以上前から有効だったことが分かりました。

Claude Code のパーミッションは、モデルではなく Claude Code というアプリケーション自体が強制する仕組みです。公式ドキュメントは「Permission rules are enforced by Claude Code, not by the model」と明記しており、permission も sandbox も、Claude Code がエージェントとして発行するツール呼び出しを対象にした介入として説明されています(出典: Claude Code 公式ドキュメント「Configure permissions」、2026-07-08 取得)。この記述に照らすと、少なくとも AI エージェント経由で当該の exact コマンドが実行される経路は、事件当時ふさがれていたことになります。

つまり、当初の推測、すなわち AI が該当の破壊的コマンドをそのまま実行したという筋は、誤りでした。deny 設定は事件の 1 か月以上前から効いており、その exact なコマンドが AI エージェント経由で通る経路は、疑うより前からすでに塞がれていたのです。

ただし、これで「AI は無関係だった」と言い切れるわけではありません。後述するように、この完全一致の deny は、表記を変えたコマンドやラッパー経由の呼び出しでは素通りしうるものでした(教訓 A)。そして、AI がそうした別経路を実行したのかどうかを確かめられる実行ログも、残っていませんでした(教訓 C)。この検証で分かったのは「当初疑った exact コマンドの筋は成り立たない」ことであって、「AI が潔白だ」という証明ではありません。むしろこの検証をきっかけに、deny 設定そのものに見落としがあることが見えてきました。

誰が消したのかは、復元できず「不明」で終わった

では、当初の筋が成り立たないいま、誰が、何が volume を消したのか。ここで調査は行き詰まりました。

シェル履歴を見ようとしましたが、当時の履歴は永続化されておらず、時刻情報もありませんでした。加えて、直前のシェルセッションは正常終了しておらず、未保存分の履歴はそこで失われていました。このセッションの異常終了が volume 消失と関係するのかどうかも、記録がない以上確かめられません。結果、手打ちコマンドの記録はゼロでした。エージェントのガードは存在していたものの、どのコマンドを通した・止めたという実行の記録は 1 行も残っていませんでした。

結果として、実行者も実行時刻も復元できませんでした。この調査は「不明」で終わります。整った成功譚として締めることはできません。分かったのは「当初疑った exact コマンドの筋は成り立たない」ことと「同時刻に 2 本が作り直された」ことだけです。最後の一手を打ったのが誰なのかは、AI なのか手打ちのターミナルなのか別の何かなのかも含めて、証拠が残っていない以上、断定できません。推測を事実のように書くこともしません。

原因を特定できなかったのは、犯人が巧妙だったからではなく、事前の記録設計の不備が招いた結果でした。その不備の中身が、この後の 3 つの教訓です。

教訓 A: 完全一致の deny リストは表記ゆれで素通りする

完全一致のみで登録した禁止コマンドは、長形オプション・フラグの前置・引数順の違い・別サブコマンドの利用で素通りしえます。 これは自社の設定ミスにとどまらない、パターンマッチ方式の構造的な限界です。

Claude Code の Bash パーミッションは、文字列パターンのマッチングです。指定した文字列に完全一致する呼び出しにだけマッチし、ワイルドカード * の挙動も前後の空白の有無で変わります(出典: Claude Code 公式ドキュメント「Configure permissions」、2026-07-08 取得)。この脆さは Claude Code に固有ではなく、各社の公式ドキュメントや第三者の検証が、それぞれ次のように認めています。

ツール・検証 認められている限界 出典
Claude Code(公式) 引数を制約する Bash パーミッションパターンは脆い。curl の URL 制限を例に、オプション前置・プロトコル違い・リダイレクト経由・シェル変数展開・余分な空白のいずれでも回避されうる Claude Code 公式ドキュメント「Configure permissions」(2026-07-08 取得)
Claude Code(公式) 複合コマンドは各サブコマンドごとに判定されるが、docker execnpxmise execdevbox run はラッパー除去の対象外で、ラップすると内側の deny が素通りしうる 同上
第三者検証(watany) rm -rf を完全一致で deny しても、rm -frrm --force --recursive と表記を変えると確率的に通過した Zenn(個人の検証記事。ベンダー公式ではない点に留保 / 2026-07-08 取得)
Cursor(公式) allowlist は best-effort であり、セキュリティ境界ではない(The allowlist is best-effort, not a security boundary)。hook 等との併用を推奨 Cursor 公式ドキュメント「LLM Safety and Controls」(2026-07-08 取得)
Backslash(実証) denylist を Base64 エンコードやサブシェル実行で回避できると実証。これを受けて Cursor は denylist 機能を後に非推奨化 Backslash Security Blog(2025 年 7 月 21 日)

「ガード機能を入れたら安心」という思い込みへの警鐘は、業界内で共有された認識になりつつあります。

もう一点、見落としやすい前提があります。これらの deny が効くのは、AI エージェントのツール実行に対してだけだという点です。公式ドキュメントが説明するアーキテクチャは、Claude Code が発行するツール呼び出しを対象にした介入であり、人間が Claude Code を介さず自分のターミナルで直接コマンドを打つ行為を制限する記載は見当たりません。これは公式ドキュメントの一文をそのまま引用した事実ではなく、記述されているアーキテクチャからの推論ですが、少なくとも「deny を設定したから、この環境で当該コマンドは絶対に実行されない」とは言えません。手打ちのターミナルには、エージェントの deny は届かないと考えるのが妥当です。

対策の方向は次のとおりです。引数を制約するパターンに頼らず、危険なコマンド自体を deny する。ワイルドカードの挙動を正しく理解して使う。公式ドキュメントが推奨するように、パターンで拾いきれない部分は PreToolUse hook の検証で受ける。さらに別レイヤーの制御として sandbox のような仕組みを重ねる手もあります。設定を書いたら、表記を変えたコマンドやラッパー経由の呼び出しで実際に止まるかを試す。「登録した」で終えず「本当に止まるか」まで確認することが、機能しない deny と機能する deny を分けます。

外部から AI に指示が注入される経路への対策は、別記事「プロンプトインジェクション対策:間接型攻撃の仕組みと入力・出力・運用の3層防御」で扱っています。本記事が扱う「AI 自身に与えた実行権限の設計不備」とは向きが逆で、外からの攻撃と内向きの権限設計は、両方を点検してはじめて権限まわりの抜けを塞げます。

教訓 B: シェル履歴を永続化していないと事後調査は成立しない

bash の既定では、シェル履歴はシェル終了時にまとめて書き込まれるため、クラッシュや並行セッションで失われ、時刻も残りません。 この状態では、いざ調査が必要になったときに手打ちコマンドの記録が存在しません。

GNU Bash のリファレンスによれば、履歴が有効なシェルが終了すると、Bash は履歴リストの直近 $HISTSIZE 件を $HISTFILE(既定では ~/.bash_history)にコピーします(出典: GNU Bash Reference Manual「Bash History Facilities」、2026-07-08 取得)。これが既定の書き込みタイミングです。裏を返せば、シェルが正常終了しなかった場合(クラッシュや強制終了)や、複数のセッションを並行して開いている場合は、終了時点までのコマンドがディスクに反映されないまま失われます。今回、履歴が揮発していたのはこの既定動作が原因でした。さらに、既定ではタイムスタンプも履歴に残りません。

対策は 2 行で済みます。

export HISTTIMEFORMAT='%F %T '
export PROMPT_COMMAND='history -a'

HISTTIMEFORMAT を設定すると、各履歴エントリに紐づくタイムスタンプが履歴ファイルに書き込まれます(出典: GNU Bash Reference Manual「Bash History Facilities」、2026-07-08 取得)。PROMPT_COMMAND はプロンプト表示の直前に毎回実行されるコマンドを指定する変数で、history -a は現在のセッションでまだ履歴ファイルに追記されていない行を追記する動作です(出典: GNU Bash Reference Manual「Bash Variables」「Bash History Builtins」、2026-07-08 取得)。この 2 つを組み合わせると、コマンドを打つたびに履歴がほぼリアルタイムでディスクに追記され、時刻も残ります。シェル終了時まで書き込まれない既定動作を回避できるので、クラッシュしても直前までの記録が手元に残ります。

事後調査は、事前に記録を残していた分しか成立しません。

教訓 C: 危険コマンドを止めるガードと、実行を記録する監査ログは別物である

危険なコマンドを止める仕組みがあっても、実行を記録する仕組みが別に用意されていなければ、後から「何が起きたか」を言うことはできません。 今回、私たちはこれを痛感しました。エージェントの deny というガードは存在したのに、そのガードが何を通し何を止めたかの記録が 1 行もなく、結果として実行者を追う手段がありませんでした。

これはセキュリティ分野の一般原則とも重なります。OWASP Top 10:2025 の A09(Security Logging and Alerting Failures)は、ログと監視の欠如について「ログと監視がなければ、攻撃と侵害は検知できない」と述べ、攻撃を止める防御の層と、何が起きたかを事後に可視化する検知・記録の層を、別の機能として扱っています(出典: OWASP Top 10:2025 A09、2026-07-08 取得)。ただし OWASP のこの項目が直接対象にしているのは Web アプリケーションセキュリティであり、CLI ツールや AI エージェントのツール実行ログという本記事の文脈とは領域が異なります。あくまで「防御と記録は別レイヤーである」という考え方の援用として引いています。

対策としてまず整えるべきは、エージェントのツール実行を、許可・拒否を問わず 1 行のログに残す仕組みです。止めた記録も、通した記録も、両方が後の調査の材料になります。技術的には PreToolUse hook のような介入点で実装できますが、実装の詳細は本記事の範囲を超えるので、ここでは「止める仕組みと記録する仕組みは分けて両方を用意する」という設計原則までを共有します。

なお、AI に機密データを持ち出されないという別カテゴリのリスク(情報の持ち出し 対 本記事のインフラ破壊)については「AI に会社の機密を渡す前に確認すること」で扱っています。与えた権限が「壊す」方向と「持ち出す」方向の両方を点検すると、AI エージェントに何をどこまで任せるかの判断がしやすくなります。

よくある質問

Q1. docker volume inspect で作成日時を見るとき、「volume が無い」場合と「空で作り直された」場合はどう見分けますか。

inspect の結果で区別できます。volume 自体が存在しなければ inspect はエラーを返します。一方、今回のように同名の空 volume が再作成された後だと、inspect は成功して CreatedAt などの値を返すのに、中身だけが空です。値が返るのに中身が無いなら、後者、つまり作り直された状態を疑います(出典: Docker Engine API の Volume 型定義、2026-07-08 取得)。複数の volume の CreatedAt が秒単位で一致していれば、それらが同時刻に作り直された痕跡です。コンテナが正常に起動していても中身が空という状態はありうるので、動作ではなく中身で確認してください。

Q2. AI コーディングエージェントを使っていない環境でも、この記事の教訓は当てはまりますか。

当てはまります。むしろ手打ち中心の環境ほど重要です。本文で見たとおり、エージェントの deny が効くのは AI のツール実行に対してだけで、人間が自分のターミナルで打つコマンドには届きません(出典: Claude Code 公式ドキュメント、2026-07-08 取得)。つまり AI を使っていない環境は、破壊的コマンドを止める仕組みがそもそも一枚少ない状態です。シェル履歴の永続化(教訓 B)と、何が実行されたかの記録(教訓 C)は、AI の有無にかかわらず事後調査の前提になります。

Q3. journald が使えない環境(非 systemd や Docker Desktop for Windows)では、事後調査はどうすればいいですか。

journald は環境依存の手がかりの一つにすぎません。本文でも触れたとおり、Docker Desktop for Windows では dockerd が別の内部 VM で動くため、利用者側で systemd を有効化しても操作が journald に出ないことがあります。journald に頼れない場合でも、シェル履歴の永続化(HISTTIMEFORMATPROMPT_COMMAND='history -a')と、AI エージェントのコマンド実行ログは、環境に依存せず残せます(出典: GNU Bash Reference Manual、2026-07-08 取得)。加えて、自分の docker がどこにログを出すのかを、障害が起きる前に一度確認しておくことをおすすめします。

あなたの環境を 3 点だけ点検する

最後に、この記事の教訓を自分の環境に当てはめる点検項目を挙げます。

  • [ ] deny リストが完全一致だけになっていないか。表記違いやラッパー経由の呼び出しで、実際に止まるかを試したか。
  • [ ] シェル履歴は時刻付きで、コマンドごとに即時保存されるようになっているか。
  • [ ] ガードが通した・止めたコマンドの記録が、1 行でも残るようになっているか。

私たちが実行者を突き止められなかった直接の理由は、疑いを検証できる記録が事前に残っていなかったことに尽きます。AI を疑う前に、その疑いを検証できる記録が自分の環境にあるか。ここを確認しておくことが、次に何かが消えたとき、原因を追えるかどうかを左右します。

AI エージェントに開発作業そのものをどこまで任せ、権限と記録をどう仕組み化するかは、別記事「AIコーディングエージェントに開発を任せる仕組みの作り方」で、複数のサブエージェントを実運用した記録として書いています。本記事で見た権限と記録の設計を、実際に手を動かす運用のレベルまで具体化した内容です。次に読むならこちらをおすすめします。

私たちは、企業や個人で開発する方の AI 導入とセキュリティ対策を支援しています。AI エージェントに実行権限を渡す際の権限設計や、実行を追跡できる監査の仕組みづくりについて、いまの設定が甘くなっていないか判断しきれないときは、お問い合わせからご相談ください。使っているエージェント・与えている権限・残している記録を教えていただければ、どこが手薄かを具体的に整理してお伝えします。

eight 編集チームAI SECURITY PRACTICE

AIとセキュリティの専門家チーム。OWASP・NISTなどの一次情報にあたり、出典を示して書く。「機密を守ってAIを使う」設計の支援を行っています。