この章で分かること: セキュリティは後付けの機能ではなく、コードを書く実装者自身が"持つ"責任だということ。そして最小権限・多層防御・攻撃者視点という、その責任を果たすための3つの考え方。
聴く台本
この教科書の最初に、いちばん大事な話をするね。セキュリティっていうのは、誰か別の人が後からやってくれる作業じゃないんだ。コードを書いている、まさにあなた自身が持っている責任なんだよ。これが全部の土台になる。
まず、よくある誤解から崩していこう。多くの人はセキュリティを「機能のひとつ」だと思ってる。ログイン機能、検索機能、決済機能、そのリストのどこかに「セキュリティ機能」がある、みたいなイメージだよね。でもこれは間違いなんだ。セキュリティは機能じゃなくて、コードの書き方そのものに宿る性質なんだよ。たとえばユーザーが入力した名前をデータベースに保存する。これは普通の機能だよね。でもその名前をそのままSQL文に文字列でつなげて書くか、それともプレースホルダっていう仕組みを使って安全に渡すか。同じ「保存する」という機能でも、書き方ひとつでSQLインジェクション、つまり攻撃者が悪意あるSQLを紛れ込ませて勝手にデータを抜く攻撃が成立するかどうかが変わる。だからセキュリティは「後で足す機能」じゃなくて、一行一行を書く瞬間にもう決まっているんだ。
なぜ実装者が"持つ"必要があるのか。それは、セキュリティの穴は必ずコードの中に空くからなんだ。設計書がどれだけ立派でも、レビューがどれだけ厳しくても、最終的に脆弱性が生まれるのはキーボードを叩いている人の手元なんだよね。だからセキュリティを外部の専門家やレビュー工程に丸投げする発想だと、根本的に間に合わない。書く人が持っていないと、穴は塞がらない。これは僕らがやっている003のAI導入支援事業、つまり他社の社長の判断データや業務ナレッジっていう、めちゃくちゃ機密性の高いデータをAIに預かるビジネスを考えると、もっと切実になる。預かったデータが一回でも漏れたら、その瞬間に「データを預けられない会社」と判断されて事業ごと終わる。だから実装者ひとりひとりが「自分が穴を空けたら会社が死ぬ」という当事者意識を持つしかないんだ。
じゃあ、その責任を具体的にどう果たすのか。3つの考え方を順番に話すね。
ひとつめが、最小権限の原則。これは「必要最小限の権限しか与えない」という考え方なんだ。たとえばあるプログラムが、データベースから読み取りだけできればいい仕事をしているとする。そのとき、そのプログラムに書き込みや削除の権限まで与えちゃダメなんだよね。読み取りだけでいいなら、読み取りだけにする。なぜかというと、もしそのプログラムが乗っ取られても、できることが読み取りだけなら被害が読み取りで止まるからなんだ。逆に「とりあえず全権限」を渡しておくと、ひとつ穴が空いた瞬間に全部やられる。AIに渡すAPIキーも同じで、顧客のデータを読むだけのキーに、他の顧客のデータまで触れる権限を持たせちゃいけない。権限は「あったら便利」で渡すんじゃなくて、「今この仕事に必要か」で削るのが正解なんだ。
ふたつめが、多層防御。英語でディフェンス・イン・デプスっていうんだけど、これは「一枚の壁に頼らず、何重にも守る」という考え方だよ。お城を想像してみて。堀があって、城壁があって、その奥に天守閣がある。堀を越えられても城壁で止まる、城壁を越えられても天守で止まる。これと同じことをシステムでやるんだ。たとえば、入力チェックを通った値でも、画面に表示するときには表示用のエスケープをもう一回かける。認証を通ったユーザーでも、各操作の前にもう一回この人にこの操作の権限があるか確認する。一見、二重三重で無駄に見えるよね。でも、これが効くんだ。なぜなら、どんな防御も完璧じゃないから。最初の壁にバグがあって突破されても、次の壁が止めてくれれば被害は出ない。「この一枚さえあれば大丈夫」という設計は、その一枚が破られた瞬間に全部抜ける。だから一枚に賭けない。
みっつめが、これがいちばん大事なんだけど、「対策の周辺まで疑う」という設計思想なんだ。さっきSQLインジェクション対策の話をしたよね。でも、対策コードを書いたとして、それが本当に全部の経路に効いているか。たとえばユーザー入力には対策してるけど、管理画面からの入力には抜けがある、とか。あるいはバリデーションが本体には効いてるけど、エラー処理の分岐の中だけ素通りしてる、とか。攻撃者は正面の壁を叩かないんだ。横の小さなドア、裏の窓、対策が及んでいない隙間を探してくる。だから実装者は、自分が書いた対策の「周辺」まで疑わないといけない。この対策、別の入口からは回避できないか。例外が起きたときも守られているか。そういう疑いの目を持つのが、攻撃者視点なんだよ。
ここで「動いた」で満足してはいけない理由がはっきりする。普通の開発だと、機能が動けば仕事は終わりだよね。ログインできた、データが保存できた、画面が表示された、よし完成。でもセキュリティの観点では「動いた」はスタートラインでしかないんだ。なぜなら、正しく使ったときに動くのは当たり前で、問題は「悪意を持って変な使い方をされたとき、どう振る舞うか」だから。攻撃者はあなたが想定した使い方をしてくれない。想定外の値を入れ、想定外の順番で操作し、想定外の経路から入ってくる。だから「正常系で動いた」だけでは、守れているかどうかは何も分かっていない。攻撃者視点というのは、自分のコードに対して「自分ならどう壊すか」を考える視点なんだ。それを持てて初めて、対策の抜けが見えてくる。
で、実装ではどうするか。コードを書いたら、自分にこう問いかける習慣をつけるんだ。まず「この処理に渡している権限は、本当にこの仕事に必要な最小限か」。余分な権限があれば削る。次に「もしこの一枚の防御が破られたら、次に何が止めてくれるか」。何も止めてくれないなら、層が足りないサインだから二枚目を足す。最後に「この対策、別の入口や例外経路から回避できないか」。正常系のテストだけじゃなく、わざと壊しにいくテスト、つまり変な値・想定外の順番・権限のない操作を実際に投げてみる。この3つの問いを、機能が動いた"後"じゃなくて、書いている"最中"に回す。これがセキュリティを実装者が"持つ"ということの、具体的な姿なんだよ。
この章のまとめ(実装で守る一線)
セキュリティは後付けの機能じゃなく、コードを書く自分自身が持つ責任だ。だから権限は「必要か」で削り、防御は一枚に賭けず多層で組む。そして対策を書いたら、別の入口・例外経路・想定外の入力から回避できないかまで疑う。「正常系で動いた」は完成じゃなくスタートライン。自分のコードを「自分ならどう壊すか」で見直してから、はじめて手を離す。