0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CFOのClaude Codeが隠したAPIキーと鬼ごっこした話

0
Last updated at Posted at 2026-06-17

非エンジニアの役員が、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で最初に公開した記事の転載です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?