私は20年以上、セキュリティと情報システム、ガバナンスの現場にいます。警察でのインシデント対応、コンサルでのセキュリティ支援、事業会社での情シス・インフラ。手を動かしながらも「守る側・統制する側」が多かったように思います。
その私が今、AI(Claude Code と Gemini)と組んで、一人でセキュリティSaaSをゼロから作っています。構想も、設計も、実装も、運用も。相棒はノートPC1台(Mac)とAIたちです。
やってみて、はっきり分かったことがあります。AIは確かに速いし便利で優秀。そして、その速さはセキュリティにおいて諸刃になり得る、ということ。だから私は、AIたちと作れば作るほど、人間の側で「再分析・判断」や「統制」を自分に課す必要がありました。この記事は、その具体的な記録です。うまくいった話だけでなく、実際に踏んだ地雷も、隠さず書きます。
なぜ「AI駆動開発」と「セキュリティ」は緊張関係にあるのか
AIたちに任せると、コードは驚くほど速く出てきます。動くものが、すぐにできる。
でも、セキュリティの本質は「単に速く動くこと」ではなく「壊れ方をコントロールできること」です。権限は最小か。バグったり事故ったときフェイルする方向は安全側か。誰かが設定を間違えても致命傷にならないか。監査で後から検証できるか。
AIたちは「動くコード」は得意ですが、この「壊れ方の設計」は、まだ人間が意図して課さないと抜け落ちます。しかも厄介なのは、抜け落ちても最初は動いてしまうこと。動くから気づかない。気づいたときには本番に出ている。
だから私は、開発の速度をAIたちに委ねる代わりに、「壊れ方」を仕組みで縛る役割を自分の仕事と決めました。以下、実際にあった3つの話です。
実話①:テストが、本番データベースを殴りかけた
ある日、ローカルのテスト設定ファイル(.env.test)が、テスト用ではなく本番のデータベースを指していることに気づきました。しかもそのキーは、行レベルセキュリティ(RLS)を丸ごとバイパスできる管理者権限のもの。
つまり、私がローカルでテストを流すたびに、本番に対して、テナントやユーザーを作っては消す破壊的な操作が走る状態だったのです。テスト後に自動で消える作りではありましたが、「本番に管理者権限で書き込み・削除が走ること自体」がリスクです。セキュリティを売る会社が、これではまずい。
ここで大事なのは、対処の仕方でした。設定ファイルを正しい値に直すだけでは、同じ事故がまた起きる。人は設定を間違えるからです。
私が課したのはフェイルクローズガードでした。「テストの接続先が、想定したテスト用プロジェクト以外だったら、DBに1行でも触れる前に、例外を投げて止まる」。許可リスト(テスト用だけ許す)+拒否リスト(本番は明示的に拒否)の二重防御です。
// イメージ(実際のコードは秘匿値を伏せています)
const TEST_REF = 'xxxxxxxx'; // 許可するテスト用プロジェクト
const PROD_REF = 'yyyyyyyy'; // 明示的に拒否する本番
export function assertTestDatabase(url: string) {
const ref = extractRef(url);
if (ref === PROD_REF) throw new Error('本番に接続しようとしました。停止します。');
if (ref !== TEST_REF) throw new Error('想定外の接続先です。停止します。');
}
そして、テストの一番最初(DBに触る前)に必ずこれを呼ぶ。ガード自体にもユニットテストを書いて、「本番参照なら必ず例外を投げる」ことを固定しました。
面白かったのは、この実装を入れた瞬間、まさに本番を指していた私の設定ファイルを、ガードが実際に止めたことです。「わざと壊して確認」ではなく、「実際に壊れていたものが、実際に止まった」。机上でなくリアルに効いた瞬間でした。
教訓:設定ミスは人が繰り返す。ファイルを直すのは対症療法。「同様のミス自体を構造的に不可能にする」のが根治。
実話②:同じ原因のバグを、3回連続で本番に出した
もっと恥ずかしい話をします。
新しいテーブルを追加するたびに、そのテーブルへの権限付与(GRANT)を付け忘れる、という同じ原因のバグを、3つのテーブルで3回連続、本番に出しました。ユーザーがその機能を触ると「権限がありません」で止まる。
なぜ気がつけなかったのか。私が書いていたテストは「関数が正しい値を返すか」を確かめる純粋なテストで、データベースの権限設定までは見ていなかったからです。ロジックは正しい。でもDBの入口で弾かれる。テストはグリーン(緑)なのに本番は壊れる。
3回目でようやく腹をくくりました。「同じ原因で3回やった。これは感覚や根性ではなく、仕組みで止める問題だ」と。
やったのは、CIに「全テーブルが、意図した権限を持っているか」を機械的に突き合わせる検査を足す設計を固めたこと(実装はこれから)です。ポイントは、単純な「全テーブルに権限必須」ではダメだった点です。実際に調べると、権限の正解が3パターン共存していました。
- 普通のアプリテーブル:フル権限が正解
- ログ系テーブル:追記だけが正解
- サービス専用テーブル:ユーザー権限なしが正解(意図的)
「全部に必須」だと、正解であるはずのログ系を誤ってレッド(赤)にしてしまう。誤検知が出る検査は、やがて無視され、存在意義を失う。だから「テーブルごとに"期待する権限"を宣言し、実際のDBと突き合わせる」方式にしました。新しいテーブルを足すときは、必ず「このテーブルは誰が何の権限を持つべきか」を明示する。権限設計の判断を、毎回強制的に言語化させるわけです。これは最小権限の原則そのものでもあります。
教訓:同じ原因で複数回やったら、それは"気をつける"問題ではなく"仕組みで防ぐ"問題。そして仕組みは、正解が複数あることを前提に設計する。
実話③:AIたちとの協業で一番効いたのは「記憶でなく記録」
技術以上に効いたのが、これでした。
AIたちとの対話は、セッションが切り替わると記憶がリセットされます。新しいチャットのAIは、昨日何を決めたか知りません。一人法人なので、私の頭の中にしか判断理由がない、という状態は、単一障害点そのものです。私が忙しくなった瞬間、あるいは忘れた瞬間、プロジェクトの背骨が失われる。
私は3つの記録を運用に組み込みました。
- 実装台帳(Handoff):何をしたか。コミット単位で残す。
- 意思決定ログ(Decision Log):なぜそう決めたか。「D-035:テストの本番誤接続をフェイルクローズで根治」のように、決定と理由を番号付きで積む。
- 原則集(Working Principles):どう考えるか。「推測で動かず実物で裏取り」「調査と実装を分ける」「対症療法でなく根治」といった型。
新しいチャットを始めるとき、AIたちにまずこの3つを読ませる。すると、別インスタンスのAIでも、同じ精度・同じ判断基準で動き始める。記録が、記憶の代わりに連続性を担保する。
さらに一歩踏み込んで、最近こう気づきました。「実装の記録(何をしたか)は回っているのに、対話で下した判断(なぜ決めたか)は、思い出したときにしか残っていない」。ここに穴があった。「対話で設計・戦略・優先順位を決めたら、そのセッションの区切りで必ず意思決定ログに起こす」を、原則として明文化しました。
教訓:AIたちと組むほど、"記憶でなく記録"が効く。記録は、忙しくなった一人法人の単一障害点を、仕組みで解決する。
共通しているのは、たった数個の原則
3つの話に共通していたのは、実は同じ数個の型です。
- 推測で動かず、実物で裏取りする(「たぶん直った」でなく、git や実データで確かめる)
- 調査と実装を分ける(原因を確定してから直す。混ぜると手戻りする)
- 対症療法でなく根治(表面を直すのでなく、その根本原因自体を潰す)
- デグレ厳禁(直すときに、動いていたものを壊さない)
これらは、AIたちに任せる速さの中で、人間が意図的に握り続けるハンドルです。AIたちは実装を代替してくれますが、「どう壊れないように作るか」の設計判断は、まだ人間の仕事だと感じています。
そして、これは"制度"が求めているものと同じだった
最後に一つ。この「規律あるAI活用」を続けていて気づいたのは、私が自分に課したこれらの統制が、そのままIT統制(ITGC)や、これから始まる制度が企業に求めるものと重なる、ということです。
変更管理(誰が・何を・なぜ変えたかの記録)、最小権限、フェイルセーフ・フェイルクローズ、継続的な検証。国が進めるサプライチェーンのセキュリティ評価制度でも、こうした「体制が継続的に機能しているか」が問われていきます。
私が作っているのは、まさに企業のこうした"現在地"を可視化するツールなのですが、その開発自体で同じ規律を自分に課していた、というのは、我ながら腑に落ちる話でした。統制を扱う道具を作る人間が、自分の開発に統制を課していない、では説得力がないからです。
透明性がないと、伝わらないこともある。うまくいった話だけでなく、3回連続で出したバグも含めて、正直に書きました。この記録が、AIたちと真剣にものづくりをしている誰かの参考になれば嬉しいです。
(この記事の姉妹編として、同じ話を「経営・監査の視点から、なぜこの規律が安心に繋がるのか」に翻訳したnote版を公開しています。)
https://note.com/securoi/n/n40687b3cf2a7