非エンジニアの役員が、AIに丸投げして2日で本番まで作ったtoB SaaSがあります。
ちゃんと顧客が使えている。動いている。機能の追加も速い。すごいんです、本当に。
で、そのインフラと運用を私が引き取ることになりました。「動いている本番」の裏側を一つずつ点検していく係です。これがまあ、宝探しというか、地雷探しというか。
今日はその中の、秘密情報(APIキーとか)の置き場所で繰り広げられた鬼ごっこの話をします。
結論から言うと、私が「それ危ないですよ」と指摘するたびに、秘密の隠し場所が引っ越しを繰り返したんですね。しかも、引っ越すたびに「これで安全になったでしょ?」という顔をされる。
隠すことと、秘匿することは、違うんです。この記事はその一点に尽きます。
一段目:ソースに直書き
最初に見つけたのは、ど真ん中の直球でした。
APIキーが、ソースコードに直に書いてある。
API_KEY = "(本物のキー文字列)" # ←こういうのが地の文に直接
vibe coding(=AIにふわっと指示して作らせるスタイル)あるあるです。動かすことが最優先なので、とりあえず動く形——つまり値をそのまま埋め込む——に着地しがち。AIも「動くコード」は出しますが、「秘密を秘密として扱うコード」までは、頼まないと出してこない。
で、私が言うわけです。「キーをソースに直書きはまずいです。リポジトリ見られたら一発で漏れます」と。
非エンジニアの役員、素直なので、すぐ直してくれました。直して、くれたんですが——
二段目:READMEに引っ越し
直したというので確認したら、APIキーがソースから消えている。お、やるじゃん。
と思ったら、README に書いてありました。
「セットアップ手順」として、ご丁寧に。
## 環境構築
1. リポジトリをクローン
2. 以下のキーを設定してください: (本物のキー文字列)
……これ、皆様わかりますか。秘密がソースから手順書に移動しただけで、リポジトリの中にいることは1ミリも変わっていない。むしろ、コードの奥に埋もれていたものが、トップに置かれる読み物(=README)の中で堂々と目立つ位置に昇格しています。
隠れんぼで言うなら、押し入れから出てきたと思ったら、玄関に立っていた感じです。発見しやすくなってどうする。
本人としては「コードからは消した」という達成感がある。気持ちはわかる。でも秘密管理の世界では、リポジトリの中にある時点で全部アウトなんです。クローンした人全員に配っているのと同じなので。
三段目:DBに保存(ここまでは前進)、しかし
「README もダメです。そもそもリポジトリの中に置いてはいけません」ともう一度説明しました。
今度は本気で考えてくれたようで、次に見たときにはデータベースに保存する形になっていました。
これは——方向としては正しいんです。リポジトリの外に出た。コードにもREADMEにも秘密が残らなくなった。前進です。素直に拍手しました。
したんですが。
よく中を覗いたら、その値、平文のまま入っていました。
暗号化も、マスキングも、何もなし。テーブルを開けば誰でも読める状態で、キー文字列がそのまま鎮座している。
DBに入れた=安全、ではない。置き場所を変えただけで、秘匿はまだ一度もできていなかったんですね。
ソースの中 → 手順書の中 → DBの中(ただし平文)。隠し場所が3回旅をして、ようやく「外には出た」。でも「秘匿できた」には、まだ一歩も近づいていない。鬼ごっこのオニは、ずっと同じ場所を探し続けています。
「隠す」と「秘匿する」は、別物
この鬼ごっこ、犯人(=役員)を笑う話にしたいわけじゃないんです。むしろ、直感としてはすごく自然な動きなんですよ。
人間の直感はこうです。
見えない所に置けば、安全。
押し入れ、屋根裏、金庫の「ふり」をした箱。見えなくすれば守った気になる。これは物理世界では半分正しい。
でもソフトウェアの秘密管理では、この直感がズレます。問われているのは「見えにくいか」ではなく、
- そこに置いて、配布物(リポジトリ)に混ざらないか
- 読める人を、本当に必要な人だけに絞れているか
- 読めたとしても、中身が暗号化されていて意味を成さないか
の方なんです。場所を移すのは「隠す」。これらを満たして初めて「秘匿する」。3か所を渡り歩いても、秘匿の条件を一度も満たしていなければ、鬼ごっこは終わりません。
じゃあどこに置くのが正解か
ざっくり言うと、こうです。
- コードにもリポジトリにも秘密を置かない(直書き・README・設定ファイルのコミット、全部NG)。コードは「秘密の名前」だけ知っていて、中身は知らない状態にする。
- 中身は実行環境の外側から注入する。環境変数や、秘密専用の保管庫(=シークレットマネージャ)から渡す。
- どうしてもDBに持つなら暗号化して保存する。平文で持つのは「DBが漏れたら全部漏れる」と同義。
- そして、漏れたかもしれないキーは作り直す(ローテーション)。一度でも平文でリポジトリやログに乗ったキーは、もう汚染されたものとして扱う。
この SaaS はもうリリース目前です。だから秘密まわりは最優先で潰しました。脆弱性についても外部のレッドチームにソースまで見てもらって、ひととおり確認は済んでいます。秘密は「漏れたら即終わり」なので、リファクタやテストより先に手を入れる——順序としてはここを一番に倒すべきでした。
一方で、明らかにリファクタしたほうがいいコードや、ごっそり抜けているテストは、一旦そのまま放置しています。だって、動いているので。リリースを越えてから、更新のたびに少しずつ綺麗にしていく予定です。CFOが前でゴリゴリ機能を作り、その後ろで私がバグを掃除して回る。そういう分担で、今はとにかく出すフェーズなんです。
いちばん面白かったのはOpus 4.8で起きた、ということ
ここまで読むと「まあ非エンジニアが作ったならそうもなるか」と思いますよね。私もそう思っていました。
でも、ここが今回いちばん面白かった点なんです。
この役員、最上位プランのAIを常にぶん回している人なんですよ。いちばん賢いとされる最新モデル(Opus 4.8)を、いちばん上の枠で。普通に考えたら、こんな初歩のミスはまず起きないはずの装備です。
なのに、起きた。秘密がソースに直書きされ、READMEに引っ越し、DBに平文で着地した。最強の道具を持っていても、です。
これ、AIが悪いわけじゃないんです。賢いモデルは「動くコード」を一瞬で出す。出すんですが、何を秘密として扱い、どこに置くべきかは、こちらが要求して初めて満たされる。要求しなければ、賢いまま、秘密を平文でDBに入れる手伝いを完璧にこなしてくれる。
つまり——
いちばん賢いモデルを使っても、使い手が「何を守るべきか」を知らなければ、賢さは安全には変換されない。
道具の性能と、出来上がりの安全性は、別の軸なんですね。包丁がよく切れることと、指を切らないことが別の話なのと同じで。
学び
- 「作る」は速くなったが、「秘密を秘密として扱う」は別スキル。AIに2日で本番を作らせる時代でも、ここは指示しないと埋まらない。光が強いぶん、影もくっきり出ます。
- 隠し場所を変えるのは「隠す」であって「秘匿する」ではない。場所の引っ越しに達成感を覚えたら、いったん立ち止まる合図。
- 引き取る側のレビューは「動くか」ではなく、どう漏れ得るかを見る。動いている本番ほど、裏で平文のキーが鎮座していたりします。
- 最上位プランの最も賢いモデルを使っても、起きるときは起きる。道具の性能と成果物の安全性は別の軸。賢さは、こちらが「何を守るか」を要求して初めて安全に変換される。
非エンジニアが本番を作れるのは、本当にすごいことです。否定する気はない。
ただ、秘密は「見えない場所」ではなく「正しい場所」に置く。
押し入れに突っ込んで安心する前に、それ、玄関から見えてませんか?を一回疑ってみてください。皆様もお気をつけて。
この記事はjunueno.devで最初に公開した記事の転載です。