
はじめに
エンジニアをやっている、@r9999 です。日夜奔走しながら開発を進め、エンジニアリングマネージャーというポジションで活動しています。よろしくお願いします。
今回のテーマは、レガシーコードのセキュリティ調査でClaudeを用いて、楽をして結果を出そうと思ったけど、最初のアプローチでは "間違った結論" のレポートを受け、余計に時間がかかってしまったので、何がよくなかったのか、どのように接したらいいんだよ、というのを述べます。
調査のきっかけ
レガシーシステムは長年の運用で複雑化し、コードの全体像を把握するのが難しくなります。今回、セキュリティ強化の一環として、まずはログイン機能から調査を開始しました。
最初の調査で、Claudeから「重大なセキュリティ脆弱性を発見!」というレポートがあがってきました。事前に自分で動作確認したときは問題なかったので、「何か見落としがあるのか?」と思いつつ、調査アプローチを見直してClaudeに再調査を依頼しました。すると、今度は「問題なし」という結論になりました。
この経験を通じて、AIと協働しながらレガシーコードを調査する際のコツが見えてきました。今回は、最初のアプローチは何が悪く、その後の調査は何が良かったのか、レガシーコード特有の難しさを踏まえて、まとめていきます。同じような調査をする方の参考になれば幸いです。
📖 目次
何が起きたのか
背景:セキュリティチェックの必要性
昨今、セキュリティ脆弱性のニュースがあり、自社のレガシーシステムも、長年の運用で複雑化しているので、改めて、セキュリティに問題・課題がないかの検証をすることにしました。そこで、認証・認可機能に関わっている、自分が関わっているサービスのリポジトリ(Laravel、Ethnam)に対して、Claudeを使って調査を開始しました。
初回調査:Claudeの報告
簡単なところから調査をしようと思い、データをもとにしたアクセス制御ができているのか?をClaudeに調べてもらおうと思い、「利用ユーザーのログインに対する認証認可の処理について脆弱性を調べて」と依頼しました。
数分後、Claudeから以下のような報告が上がってきました
🚨 重大なセキュリティ脆弱性を発見しました!
- 問題: ログイン認証処理で論理削除フラグのチェックが一切ありません
- 影響: 退会済みユーザーでもログインできてしまいます
- リスク: 個人情報保護法違反の可能性、不正注文、データ改ざんなど
- 緊急度: 最高 - 1週間以内の対応を推奨
最初の一文をみたときに、どんなところに脆弱性があるんだ?と、内心ワクワクしましたが、レポート結果を見ると期待と異なり、実際には制御できているはずのものが「制御できていない」とレポートされていました。「何か見落としがあるはずだ」と思い、調査を進めました。
なぜかというと、この調査を依頼する前に、ログイン周りの一通り検証を行い、その検証の中に、退会ユーザーでログインできないことを試していました。 結論は、"ログインできませんでした" つまり、Claudeのレポートは 実運用と矛盾していることの確認が取れました。
再調査:アプローチを変えて波及調査
調査を進めるにあたって、調査の指示を見直しました。
初回は「ログインの認証認可」という漠然とした指示だったので、今度は以下のように具体的な調査範囲を指定してClaudeに再調査を依頼しました
- 認証プロバイダー層だけでなく、モデル層、データ取得層まで調査
- フレームワーク固有機能(LaravelのGlobal Scopeなど)を考慮
- 実際のデータ取得メソッド(SQLの内容)まで確認
この指示で再調査した結果...
Laravel
※実際のコードではなく、表現を変えています。
public static function isDelete()
{
static::addGlobalScope('softDeleted', function (Builder $query) {
論理削除フラグがあるかの確認
});
}
発見した実装
- Global Scopeで、全てのEloquentクエリに自動的に論理削除フラグのチェックが追加されていた
- 認証プロバイダーには明示的なチェックがなくても、モデル層で自動的にフィルタリングされていた
一般的な手法 vs レガシー特有の問題:
- 一般的な手法:Global Scopeによる論理削除フィルタリング自体は、Laravel、Rails、Djangoなどでも使われる標準的なアプローチ
- レガシー特有:Laravel標準の
SoftDeletesではなく、プロジェクト独自のSoftDeletableトレイトを使用。さらに、認証層・モデル層・データ層の複数箇所に実装が分散していて、どこでフィルタリングされているか追いにくい
Ethnam
// app/model/User.php
public function isDelete($mailaddress)
{
return データ層に対して、論理削除フラグがあるかの確認;
}
発見した実装: - SQL直接記述で、ちゃんと論理削除フラグのチェックの条件を入れていた
一般的な手法 vs レガシー特有の問題:
- 一般的な手法:SQL WHERE句での論理削除フィルタリング自体は、古いシステムでは一般的なアプローチ
- レガシー特有:古いフレームワークのため、ORMの自動フィルタリング機能がなく、各データ取得メソッドに個別にSQL条件を埋め込む必要がある。コードを読まないと分からず、漏れが発生しやすい
結論:調査アプローチの重要性
再調査の結果、全てのシステムで論理削除されたユーザーは正しくブロックされていることが確認できました。セキュリティリスクなし。
初回調査は調査範囲が浅かったため、「明示的なチェックがない」という事実だけを報告してしまったのです。
しかし、適切な調査範囲を指示することで、Claudeは正しい結論を導き出すことができました。
この経験から分かったこと
- × AIは使えない、Claudeが間違っていた
- ◯ 指示の出し方が不十分だった、適切な指示があればAIは強力な調査ツールになる
初回調査の何が悪かったのか
初回調査で誤った結論が出た原因を分析しました。これは、レガシーコード調査でよくある落とし穴です。
問題点1: 調査範囲を限定しすぎた
× 初回調査の問題:認証プロバイダー層だけを見て終わり
調査範囲: ◯ 認証プロバイダー (EloquentUserProvider) ← ここだけ見た × モデル層 (Eloquent, Global Scope) ← 見なかった × データ取得層 (SQL) ← 見なかった
再調査での改善:多層アーキテクチャ全体を調査するよう指示
Webアプリケーションは層構造になっています。上の層にチェックがなくても、下の層でフィルタリングされているかもしれません。
レガシーコード特有の難しさ 長年の開発で、どの層にどんな処理があるか分かりにくくなっています。初回調査のような「一層だけ見て終わり」では、重要な実装を見逃します。認証プロバイダー層だけ見て「チェックなし」と判断してしまいましたが、実際にはモデル層でGlobal Scopeが動作し、データ取得層でもSQLフィルタリングが行われていました。
問題点2: AIへの指示が不十分だった
問題の本質はここです。
初回調査で実際に使った、不十分だったプロンプト
「利用ユーザーのログインに対する認証認可の処理について脆弱性を調べて」
何が悪かったか
- 調査範囲が曖昧:「認証認可の処理」という言葉だけで、どの層まで調べるか指示していない
- 段階的な深掘りの指示がない:「認証プロバイダーで見つからなければ、モデル層も調べて」という指示がない
- 暗黙的な動作を考慮していない:明示的な条件文がなくても、フレームワーク機能で自動フィルタリングされている可能性を考慮していない
この指示だと、Claudeは「認証認可の処理 = 認証プロバイダー」と解釈して、そこに論理削除フラグチェックのif文がないことを確認して「チェックなし」と結論づけてしまいます。
その結果、以下のような重要な観点が抜け落ちました
- フレームワーク固有機能の考慮漏れ:Global Scopeのような「自動的に動く機能」を調査対象に含めなかった
- 「見えない」=「ない」の誤解:コード上に論理削除チェックの条件文が見えないから「チェックしてない」と結論づけてしまった
- 実運用との照合不足:実際の動作と矛盾する結論を出してしまった
再調査で使った改善版プロンプト
では、再調査ではどんなプロンプトを使ったのか?核心は「複数の階層を段階的に調べる」です。
再調査での改善ポイント
- 複数の階層を段階的に調べるよう指示
- 「明示的なチェックがなければ、次の層も調べる」と明示
- フレームワーク機能や実運用状況との照合を含める
レガシーコード特有の難しさ
レガシーシステムでは、「ログイン処理」と一口に言っても、複数の層・複数のファイル・複数の実装パターンにまたがって実装されています。フレームワークの標準機能とプロジェクト独自のカスタマイズが混在し、「普通はこう書く」という常識が通用しないことも多いです。
「どこまで調べるか」「何を見るべきか」を明示しないと、AIは最も表層的な部分だけを見て終わってしまいます。また、「長年動いている」という実績があるのに、コード上は問題があるように見えるときは、「見落としているコードがある」と考えるべきです。
Webアプリケーションの層構造を理解する
Webアプリケーションは一般的に以下のような層構造になっています
┌─────────────────────────┐ │ 認証プロバイダー層 │ ← ログイン処理の入り口 │ (Authentication) │ ユーザー認証を担当 ├─────────────────────────┤ │ ビジネスロジック層 │ ← アプリケーションのルール │ (Business Logic) │ 処理の流れを制御 ├─────────────────────────┤ │ モデル層 / データアクセス層│ ← データの読み書き │ (Model / Data Access) │ ORMやGlobal Scopeが動作 ├─────────────────────────┤ │ データベース層 │ ← 実際のデータ保存 │ (Database) │ SQL WHERE句でフィルタリング └─────────────────────────┘
重要なポイント
- フィルタリングはどの層でも実装できる
- 上の層に明示的なチェックがなくても、下の層で自動的にフィルタリングされている可能性がある
- レガシーシステムでは、各層にロジックが分散していることが多い
今回の調査では、認証プロバイダー層(上)に論理削除フラグのチェックがなかったので「問題あり」と判断してしまいました。でも実際には、モデル層(Global Scope)とデータベース層(SQL WHERE句)でちゃんとフィルタリングされていたんです。
改善版プロンプト
この層構造を理解した上で、こういうプロンプトを使いました
【タスク】 論理削除されたユーザーのログイン防止機構を、認証層からデータベース層まで、複数の階層にわたって調査してください。 【重要な原則】 * 一つの層で「明示的なチェックがない」だけで結論を出さない * 見つからなければ、次の層(モデル層、データ層)も調査する * 全ての階層を確認してから結論を出す * 明示的な条件文がなくても、自動フィルタリングの可能性を考慮する 【最終確認】 実際のログインフローを追跡し、どの段階で論理削除されたユーザーが除外されるのか特定してください。
この指示で、Global ScopeやSQLフィルタリングまで調べてくれます。
AIを使ったコード調査の7つのチェックリスト
今回の経験から学んだ、AIを使ってコード解析をする際のチェックリストです。 一般的なコード調査でも使えますが、レガシーコードでは特に重要になります。 初回調査の失敗を繰り返さないために、これらを確認しましょう。
◯ 1. 多層アーキテクチャを意識する
□ 認証・認可層を調査した □ ビジネスロジック層を調査した □ モデル・データアクセス層を調査した □ データベース層(SQL, View等)を調査した □ ミドルウェア層を調査した
一つの層に「ない」だけでは結論を出さない
◯ 2. 標準機能と独自実装の両方を考慮する
□ 使用フレームワークの標準機能を把握した (例:LaravelのSoftDeletes、RailsのDefault Scope) □ Global Scope等の暗黙的な動作を調査した □ プロジェクト独自のカスタム実装も確認した (例:独自トレイト、カスタムミドルウェア) □ 標準機能の使用箇所と独自実装の使用箇所を区別した
AIは「明示的なコード」は得意だが「暗黙的な動作」は見落としがち
今回の例:Global Scopeによるフィルタリング自体は一般的な手法だが、Laravel標準のSoftDeletesではなくプロジェクト独自のSoftDeletableトレイトを使っていた。標準と独自の両方を確認する必要がある。
◯ 3. 「見えない」≠「ない」と理解する
□ 明示的なif文がなくても、他の方法を考慮した □ Global Scope, Middleware等の可能性を検討した □ SQL WHERE句でのフィルタリングを確認した □ データベースビューの可能性を確認した
コード上に見えないだけで、別の場所で処理されている可能性を常に考える
◯ 4. 実行フローを追跡する
□ 「このメソッドが呼ばれる」だけでなく、 「その中で何が起きるか」まで追った □ フレームワークのライフサイクルを理解した □ ブートストラップ処理を確認した □ 実際のSQLクエリを推定した
静的解析だけでなく、動的な動作の理解が重要
◯ 5. 実運用状況と照合する
□ 運用チームに実際の動作を確認した □ 過去の障害・問題報告を確認した □ 似たような機能の動作を確認した □ コード解析の結果が実運用と矛盾しないか確認した
コードと現実が矛盾したら、コード解析を疑え
◯ 6. AIへの指示を明確にする
□ 調査対象層を明示した □ フレームワーク固有機能の考慮を指示した □ 「見つからなければ深掘り」を指示した □ 段階的な調査フローを指定した
AIは指示されたことしかしない。想像で補ってはくれない
◯ 7. 結論を急がない
□ 一度の調査で結論を出していない □ 複数の視点から確認した □ 矛盾点を全て解消した □ 「分からない」を「ない」と誤解していない
「確認できなかった」≠「存在しない」
まとめ
今回の経験から、レガシーコード調査でAIを使う際の重要なポイントが明確になりました。
AIは強力、でも指示が全て
AIは指示されたことを真面目にやってくれます。でも、指示されていないことは想像で補ってくれません。
初回調査:× 「ログイン処理を調べて」→ 認証プロバイダーだけ見て終わり 再調査 :◯ 「認証層からデータ層まで多層的に調べて」→ 全体を調査
同じAI、違う指示で、結果が180度変わりました。
レガシーコード特有の複雑さを理解する
レガシーシステムには、以下のような特徴があります
- 多層にまたがる実装:一つの機能が複数の層・ファイルに分散
- 独自のカスタマイズ:フレームワーク標準と異なる実装
- 暗黙的な動作:明示的なコードが少ない
- 長年の実績:本当に問題があれば既に顕在化しているはず
これらを理解せずに、表面的な調査で結論を出すと、重要な実装を見逃します。
事前検証・実運用との照合が重要
今回、事前に退会ユーザーでログインを試していたことが、調査の方向性を修正するきっかけになりました。
コード解析の結果が実運用と矛盾する場合、コード解析の方を疑うべきです。
特にレガシーシステムは「長年動いている」という実績があります。その実績を無視してはいけません。
適切なアプローチで、AIは強力なツールになる
初回調査では誤った結論が出ましたが、再調査では正しい結論が得られました。ただ、AIを変えたわけではなく、バージョンも変えていないです。指示の出し方が変わっただけです。
- 調査範囲を明示する
- フレームワーク機能を考慮するよう指示する
- 段階的な深掘りを指示する
- 実運用との照合を求める
これらを意識すれば、AIは強力な調査ツールになります。
レガシーコードの調査は、確かに難しいです。でも、適切なアプローチを使えば、AIは強力な味方になってくれます。
この記事が、同じようにレガシーコード調査をしている方の役に立てば幸いです。
--
徒然なるままの話
今回のテーマで、セキュリティ調査をまとめながら、指示の出し方によって強力な味方になってくれるというようにまとめたときにふと、自分が子どものころのゲームを思い出しました。
どのようなゲームかというと、「ワンダープロジェクト」というスーパーファミコンです。このゲームは、ピーノというアンドロイドを育て、指示を出し、様々な障害や謎を解いていくというゲームでした。クリアはしていませんが。
なぜ思い出したかというと、障害や謎を解いていくために、ピーノにいろいろなことを教える必要があり、自分の持っている価値観や知っていることを教えることで、指示を出して実行できるようになり、問題を解決できる状態になります。Claudeと比べると性能は比較できないですが、観点を伝えながら課題解決していくプロセスは、同じようなことをしているような感覚になっています。
とはいえ、モノを教えて、指示を出して、問題解決するというのであれば、他にも似たようなゲームは多分あると思いますが、自分の価値観、知っている知識だけでは、解けない謎がありました。
それは何かというと、子どものころは、人にモノを投げてぶつけるという価値観がなかったため、友人に教えられるまで、ずっと解決できなかったということを思い出しました。なぜ、ぶつけたら解決するのかというと、通り道に、とある人物がずっと怒ってその場に立ち、その人物が動かないと先に進めませんでした。
自分の価値観では、喋りかけたり、気を引いたりするような行動を取ってもらっていましたが、何をしても、通してくれず、ものをぶつけると怒って追いかけて、通れるようになるということが起きました。
これは、自分の知識、概念を変えるようなことをしないと正解には辿り着けなかったと、ということを思い出し、昔遊んだゲームの感覚が、いま目の前に来ているんだなーと実感しています。
この感覚を元にすると、こうあるべき、こうしなければならない、という固定概念に囚われないよう、柔軟な発想で取り組んで、物事に取り組んでいかないといけないなーと改めて思いました。