Cloudflareを本番で安全に回すための「落とし穴」総点検

ミドリ: タツヤさん、うちのプロダクトをCloudflare WorkersとPagesに寄せてから1年経つんですけど、最近ヒヤッとした事故が立て続けにあって……。脱エンジニアの視点で運用を考えると、こういう落とし穴って事前に教えてもらえると助かるんですよね。

タツヤ(nodding): うん、Cloudflareは「動かすだけ」なら本当にラクなんだけど、本番運用に入ると別の難しさが出てくるんだ。今日はうちで実際に踏んだ事例を中心に、落とし穴を整理しようか。

ミドリ: 具体的な事故ベースで聞きたいです。

タツヤ: いいよ。大きく5つある。Wranglerのバージョン管理、.env系のリーク、Worker SecretとローカルEnvの使い分け、Pages Functions middlewareでのエッジ認証、それからマイナーバージョンの挙動変化。順番に行こう。

はじめに:なぜ「便利だから怖い」のか

ミドリ: Cloudflareって導入のハードルが低い分、つい油断しがちなんですよね。

タツヤ: そこが本質だね。AWSなら「IAMで権限設計」みたいな儀式があるけど、Cloudflareはwrangler deploy一発で世界中のエッジに反映される。30秒で本番が変わるってことは、30秒で本番を壊せるってことでもある。

ミドリ: スピードと事故率はトレードオフ、と。

タツヤ: うちの実体験だと、本番Workerが24時間365日で複数PCから叩かれてる構成があるんだけど、依存を一つ更新しただけで2プロジェクトのCIが落ちたことがある。便利な道具ほど運用ルールの整備が要るんだ。

課題1:Wranglerのバージョン管理が事故の起点

ミドリ: Wranglerって普通に最新を入れておけばいいんじゃないんですか?

タツヤ: それが落とし穴その1なんだ。Wranglerは2025年3月にv4が出て、いくつかbreaking changeが入った。代表的なのはこれ。

ミドリ: ローカル既定って、たとえばどう困るんですか?

タツヤ: たとえばwrangler kv key put MY_KEY valueを打つと、v3までは本番KVに書き込まれてた。v4だとローカルのminiflareに書き込まれて終わり。本番には何も反映されてない。

ミドリ(surprised): え、それサイレントに本番が更新されないってことですよね……。

タツヤ: そう。CHANGELOGを読まずにアップグレードして、「いつもの手順なのに反映されない」ってパニックになった事例がうちでもあった。4つあるWorkerのうち2つでNode 22必須エラーが出てCIが落ち、原因を特定するのに半日かかった。

解決策:複数Wrangler系統を平和に共存させる

ミドリ: 「とりあえず全部最新に上げる」じゃダメってことですね。

タツヤ: ダメ。原則として1 major / 1 PR / 1 deployを守る。具体的にはこう。

ミドリ: 「動いているうちは触らない」も大事ですか?

タツヤ: そこは半々だね。動いてるからこそ放置すると、いざ移行したい時にNode・esbuild・Wranglerが3世代飛んでて、ぜんぶまとめてbreakingを引き受ける羽目になる。半期に1回、依存棚卸しの日を設けるのがオススメ。

課題2:.env系のリークが10分で検知される時代

ミドリ: APIキーって、どう管理してます?

タツヤ: ここが落とし穴その2。うちでは過去に.env.sharedというファイルをGitに載せてた時期があって、これが大事故の原因になった。

ミドリ(surprised): Gitに載せちゃったんですか? プライベートリポジトリですよね?

タツヤ: プライベートリポジトリ。それでも事故った。

何が起きたか

タツヤ: GoogleのGemini APIから突然403が返るようになった。エラーメッセージはこう。

403 Forbidden
"Your API key was reported as leaked"

ミドリ: え、誰かが報告したってことですか?

タツヤ: そう。GitHub Secret Scanningがprivate repoの中身も対象にしていて、Googleなどのパートナー企業に自動通知される仕組みになってるんだ。発見から約10分で旧キーがrevokeされた。

ミドリ: 10分って早すぎますね……。

タツヤ: 早いのは助かるんだけど、その瞬間に画像生成機能が止まる。複数プロジェクトが同じキーを参照してたから、影響範囲は1リポジトリでは収まらなかった。

原因の本質

タツヤ: 原因は単純で、.gitignore.envは書いてあったけど.env.sharedが抜けてた。.gitignoreのパターンは厳密一致だから、.envだけ書いても.env.sharedは除外されない。

ミドリ: 知らなかった……。

タツヤ: よくある勘違いだね。それと「private repoだから漏れない」っていう神話。privateは最後の防衛線にすぎなくて、Secret Scanningの自動通報経路があることは押さえておきたい。

解決策:.env*ワイルドカード+!許可リスト

ミドリ: どう修正しました?

タツヤ: .gitignoreを「ワイルドカード除外 + テンプレートだけ!で許可」に切り替えた。

# 全 .env* 系を除外
.env
.env.*
# テンプレートだけ許可
!.env.example
!.env.shared.example

これで.env.production.env.stagingを新規に作っても自動でブロックされる。

ミドリ: 既に追跡されてたファイルはどうしました?

タツヤ: git rm --cached agent/.env.shared追跡だけ外してローカルファイルは保持。これでローカル開発は続けつつ、Gitには載せなくなる。

解決策:gitleaks をCIに入れる

タツヤ: もう一段安全網として、gitleaksをGitHub Actionsに入れた。push/pull request時にcommit内容を自動スキャンしてくれる。

on: [push, pull_request]
jobs:
  gitleaks:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - uses: gitleaks/gitleaks-action@v2

ミドリ: これでpush段階で止まるんですね。

タツヤ: そう。レビューに回る前に物理的にブロックされるから、ヒューマンエラーの吸収率がぐっと上がる。月次で全リポジトリの.env*追跡状況を一括チェックするシェルスクリプトもcronで回している。

コンソール出力にも気をつける

タツヤ: あと運用面で重要なのは、APIキーをコンソールやチャットに絶対表示しないこと。うちでは社内ルールとしてこれを徹底してる。

ミドリ: 「キーは見ないで運用する」が原則なんですね。

課題3:Worker Secret と .env の使い分け

ミドリ: Cloudflare側にもSecret管理ってありますよね。.envとの違いは?

タツヤ: 落とし穴その3はここ。Wrangler Worker SecretはCloudflare側に暗号化保存される本番用シークレットで、ローカルの.envとは別物。

鉄則:wrangler.tomlに平文を書かない

タツヤ: これは絶対やってはいけない例。

# wrangler.toml — これは絶対NG
[vars]
GEMINI_API_KEY = "sk-***"

ミドリ: [vars]に書いちゃダメなんですか?

タツヤ: [vars]公開前提の環境変数。Workerのバンドルに含まれてしまうし、wrangler.tomlはGit管理されるから事実上の平文公開と同じ。

正しい注入方法

タツヤ: Secretは必ずwrangler secret putで対話的に入れる。

# 対話プロンプトに値をペースト
npx wrangler secret put GEMINI_API_KEY

ミドリ: 値はターミナルに表示されますか?

タツヤ: 対話プロンプトでは伏字になる。値はCloudflare側に暗号化保存され、Worker内ではenv.GEMINI_API_KEYで読める。wrangler secret listでキー一覧だけ確認できて、値そのものは表示されない設計。

.envはあくまでローカル開発用

ミドリ: じゃあ.envはどう使うんですか?

タツヤ: wrangler dev時にローカルで読まれる開発用Env、もしくはAgent(自前のNode.jsプロセス)用のEnvに限定する。本番Worker用には使わない。「ブートストラップ用最小限のキーだけ」ローカルに残し、Vendor APIキー(Gemini、Anthropic、OpenAI等)は全部Worker Secret経由にする方針が安全。

Worker Proxyパターン

タツヤ: うちでは現在、AgentからVendor APIを直接叩いてた構成をWorker Proxy経由に段階移行してる。

Before:
  Agent (.env: GEMINI_API_KEY) → Gemini API

After:
  Agent (.env: API_KEY のみ) → Worker (secret: GEMINI_API_KEY) → Gemini API

ミドリ: Worker側に集約するメリットは?

タツヤ: 3つある。

ミドリ: プロキシ層がボトルネックになりませんか?

タツヤ: レイテンシは+200ms前後増える。ただCloudflare Workersは世界330拠点に分散されるから、Vendor APIへの距離は逆に短くなることも多い。並列実行は変わらず維持できる。

Rollbackも忘れずに

タツヤ: 移行時に重要なのがfeature flagUSE_GEMINI_PROXY=true/falseで従来パスと新パスを切り替えられるようにしておく。1台ずつ慎重に切り替えて、何かあったら即時false。

ミドリ: 一斉切替は怖いですもんね。

課題4:Pages Functions middlewareによるエッジ認証

ミドリ: Workerじゃなくて、Cloudflare Pagesにもセキュリティ機能ってあるんですか?

タツヤ: 落とし穴その4はここ。Pagesで静的サイトをホストしてる時、社内ドキュメントとか、検索エンジンに載せたくないけど厳密な認証もオーバースペックみたいなサイトをどう守るか。

ミドリ: Basic認証ですか?

タツヤ: Basic認証はブラウザのUXが古いし、TLSの上とはいえパスワードがクリアテキストでHTTPヘッダーに乗る。Pages Functions middlewareで、cookieとHMAC署名でやるのが今風だね。

仕組みの全体像

タツヤ: こんな流れ。

[User] → example-docs.pages.dev/
   ↓
[Pages Functions: docs/functions/_middleware.js]
   ↓ Cookie docs_auth=<HMAC token> なし?
   → カスタムログインHTMLを返す (200)
   ↓ POST /__docs_login__ with password
   → env.DOCS_PASSWORD と constant-time 比較
   → 一致なら HMAC 署名 Cookie を Set-Cookie → 元URLへ302
   ↓ Cookie あり & 署名検証 OK
   → next() で静的サイトを返す

ミドリ: middlewareが全リクエストを横取りして、Cookie見て分岐するんですね。

タツヤ: そう。実装のコアはWeb Crypto APIだけで完結する。

// HMAC-SHA256 で署名検証 (擬似コード)
const enc = new TextEncoder();
const key = await crypto.subtle.importKey(
  "raw",
  enc.encode(env.DOCS_AUTH_SECRET),
  { name: "HMAC", hash: "SHA-256" },
  false,
  ["sign", "verify"]
);
const sig = await crypto.subtle.sign("HMAC", key, enc.encode(payload));

ミドリ: Cookieの有効期間は?

タツヤ: うちでは30日。HMAC payloadにexpを埋め込んで、middlewareでチェックする。

crypto.timingSafeEqual 相当を自前で

ミドリ: パスワード比較って==じゃダメなんですか?

タツヤ: ダメ。タイミング攻撃を防ぐためconstant-time比較が必須。Web Crypto APIのcrypto.subtle.verifyを使うか、自前で長さチェック+XOR走査の関数を書く。

function timingSafeEqual(a, b) {
  if (a.length !== b.length) return false;
  let diff = 0;
  for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
  return diff === 0;
}

よくあるハマりどころ

タツヤ: Pages Functions middlewareをデプロイする時に、3つの典型的なハマりどころがある。

ミドリ: 細かい罠が多いですね……。

タツヤ: そう。だから初回デプロイ後は必ずライブ検証を入れる。

URL="https://example-docs.pages.dev"
curl -s "$URL/" | grep -c "ログイン"   # > 0 でログインHTMLが返ってる
curl -s -X POST "$URL/__docs_login__" -d "password=wrong" \
  -o /dev/null -w "%{http_code}\n"     # 401 が返る

push成功とライブ反映は別物だからね。

内部APIトークンとedge認証は別レイヤー

ミドリ: DASHBOARD_TOKENみたいな内部API認証も入れてる場合、edge認証と二重になりますか?

タツヤ: 二重にする。edge認証は「そのオリジンへ到達できるか」、内部APIトークンは「そのエンドポイントを叩けるか」。役割が違うんだ。外側でedge認証を破られても、内側のBearerトークンで止まる多層防御になる。

課題5:マイナーバージョンでも挙動が変わる

ミドリ: メジャーバージョンを慎重に上げるのはわかったんですが、マイナーバージョンも怖いんですか?

タツヤ: 落とし穴その5。WranglerとかViteはマイナーリリースで挙動が変わることが普通にある。実例を挙げると、Wrangler 4.86でCloudflare APIが「commit messageは有効なUTF-8文字列でなければならない」チェックを厳格化したことがあって、日本語+括弧を含むメッセージが8000111: Invalid commit messageで弾かれた。

ミドリ: マイナー番号でそんな変更が……。

タツヤ: そう。だから「マイナーは安全」っていう先入観は捨てた方がいい。対策は3つ。

ミドリ: push成功=ライブ反映、じゃないんですね。

タツヤ: ここ、本当に強調したい。Cloudflare PagesのGitHub連携が止まっていて、git pushしても本番に反映されなかった事故が18日間続いた事例もあるんだ。

実践手順:今すぐできる5ステップ

ミドリ: ここまでの話を実務に落としたいんですけど、最初に何から手を付けるべきですか?

タツヤ: こんな順序で進めるといい。

  1. .gitignore.env*ワイルドカード+!許可リスト方式に書き換える — 5分で終わる。最大のヒューマンエラー予防
  2. git ls-files | grep -E '\.env(\..*)?$'で全リポジトリの追跡漏れをチェック — 漏れがあればgit rm --cachedで外す
  3. gitleaksをGitHub Actionsに追加 — Marketplaceから2行コピペ
  4. Worker Secretの棚卸しwrangler secret listで各Workerの登録キーを確認。wrangler.toml[vars]に平文が無いか目視
  5. Wrangler/Node/Honoのバージョン棚卸し表を作る — どのプロジェクトがどのバージョンか、半期に1回更新

ミドリ: 半日でできそうですね。

期待される効果

タツヤ: うちでの実績ベースだと、こんな効果が出てる。

施策効果
.gitignoreワイルドカード化 + gitleaks CIキー漏洩インシデント発生件数:1件/半期 → 0件(6ヶ月継続中)
Worker Secret一元化(Phase 1完了時)Vendorキーローテーション工数:全PC手動更新×30分 → wrangler secret put×30秒
Pages Functions middleware導入検索エンジンインデックス化リスクのある社内docs:13サイト保護完了
バージョン棚卸し表不意のCI失敗:四半期あたり3〜4件 → 0〜1件

ミドリ: ローテーションが30分から30秒って、運用負荷が桁違いに減りますね。

タツヤ: そう。「セキュリティ強化=運用負担増」じゃなくて、むしろセキュリティを強くするほど運用が楽になるっていう構造に持っていけるのが理想。

注意点・よくある失敗

ミドリ: 逆に「やっちゃダメ」なパターンってありますか?

タツヤ: 4つ警告しておくね。

ミドリ: 派手に動かしたい衝動を抑えるのが大事ですね。

タツヤ: 本番運用ってそういう地味さの積み重ねだよ。

まとめ

タツヤ: 今日のポイントを整理しよう。

ミドリ: Cloudflareの便利さの裏側に、これだけの運用ルールが必要だってことがよくわかりました。

タツヤ: 逆に言えば、ここを押さえれば本番運用は格段に安定する。今日話したことの多くはCloudflare固有じゃなくて、サーバーレス全般に通じる原則でもあるから、AWS LambdaやVercelに移行する時も持っていける知識だよ。

次のステップ

ミドリ: 明日から動くなら、何からやればいいですか?

タツヤ: 3つだけまず手を動かしてみてほしい。

  1. .gitignoreの見直し.env*ワイルドカード+!許可リスト方式に書き換える(全プロジェクト横展開)
  2. gitleaks GitHub Action導入 — 1リポジトリで試してから順次展開
  3. Worker Secret棚卸しwrangler secret listで各Workerの登録状況を表にまとめる

ミドリ: 全部リスクが低くて、半日で着手できそうな範囲ですね。

タツヤ: そう。Shimanto AI Solutionsでは、Cloudflare WorkersとPagesを使った本番システムの設計・運用支援、セキュリティ監査、Worker Proxyパターンへの移行支援などを行っているよ。.env系の追跡漏れチェック、Secret一元化のロードマップ作成、Pages Functions middleware導入のサポートなど、現場で踏みやすい落とし穴を一緒に潰す形で関わっていける。気軽に相談してほしい。


関連記事

シマント AI のサービス | 脱エンジニアの部屋 | AI戦略マップ 2025–2045