ブログ一覧

Bot Fight Mode を無料プランで使うと GitHub Actions が 403 になる

GitHub Actions で 8 サイトのトップページを 1 日 2 回 GET する死活チェックを組み、初回実行でいきなり 2 サイトが 403 でした。2026 年 7 月 18 日のログです(このブログ以外のドメインは伏せています)。

OK  https://site-a.example/  200 3147ms
OK  https://site-b.example/  200 3680ms
NG  https://site-c.example/  403 (リトライ後)
OK  https://site-d.example/  200 1585ms
OK  https://site-e.example/  200 3622ms
NG  https://shinobiworks.com/  403 (リトライ後)
OK  https://site-f.example/  200 3460ms
OK  https://site-g.example/  200 2252ms

8 サイトは全部 Cloudflare Workers 上の同じ構成で、同じスクリプトが同じ User-Agent(emdash-uptime-check/1.0)で叩いています。ブラウザで開けば 200。403 になった 2 つだけに共通していたのは、Cloudflare の Bot Fight Mode を有効にしていたことでした。

ダッシュボードで Bot Fight Mode を切り、同じコミットのまま再実行したのが 11 分後です。

OK  https://site-c.example/  200 839ms
OK  https://shinobiworks.com/  200 4063ms

全 8 サイトが 200 になりました。コードは 1 行も変えていません。

判定されたのは「ブラウザではないこと」

GitHub Actions のランナーは Azure のデータセンターの IP で、Node.js の fetch から自前の User-Agent を名乗って接続します。ブラウザではない、データセンターから来る、TLS の指紋がブラウザのそれではない。Bot Fight Mode が「自動化された通信」と判定する材料が揃っています。

これは監視スクリプトに限りません。同じ条件に当てはまる、自分の側の非ブラウザ通信を数えてみると次のとおりでした。

  • GitHub Actions からの死活チェックと、OG 画像の文言ドリフト検知(本番の HTML を取りに行く)
  • サンドボックスから CMS の API(/_emdash/api/*)を叩く CLI。記事の投入・更新・タグ付けの経路
  • Slack のイベント API からサイトへ届く POST(Slack から記事を投稿するプラグイン)
  • 動作確認の curl

Bot Fight Mode を入れると、これらが全部「敵」の側に回ります。

無料プランの Bot Fight Mode には例外が作れない

「監視の UA や IP だけ通せばよい」と考えて、WAF のカスタムルールで除外できないか調べました。Cloudflare のドキュメント(Bot Fight Mode のページ)に、はっきり書いてありました。

Bot Fight Mode does not run on the Ruleset Engine — it operates in a separate evaluation pipeline where Skip, Bypass, and Allow actions have no effect.

WAF のカスタムルールは Ruleset Engine 上で動き、Bot Fight Mode はその外側にあります。Skip も Allow も届きません。先回りできるのは IP Access Rules だけで、これは「IP Access Rule が先に一致すれば Bot Fight Mode は動かない」という形です。GitHub Actions のランナーの IP は広い範囲で変わるので、この方法は取れませんでした。

検索して出てくる「WAF のカスタムルールで Skip する」という手順は、Super Bot Fight Mode のものです。こちらは Pro 以上のプランに含まれていて、ドキュメントにも次のようにあります。

Custom rules are executed before Super Bot Fight Mode. To configure exceptions to Super Bot Fight Mode, create a custom rule with the Skip action.

つまり、例外を作りたければ Pro に上げる必要があります。Pro はゾーンごとの課金で、年払いなら月 20 ドル、月払いなら 25 ドルです。私は 8 ゾーン持っているので、Bot Fight Mode に例外を作る費用は月 160〜200 ドルになります。

無料プランの Bot Fight Mode でできるのは、有効か無効かの 2 択だけです。

8 月にもう一度考えて、入れないことにした

7 月に切ってから、8 月の下旬に D1 の読み取りと Workers の起動数を見直す作業があり、その中で「スキャナの通信を Worker の手前で落とせないか」と Bot Fight Mode を再検討しました。判断の材料はこうでした。

守りたいものは、今は圧迫されていない。

  • Workers の起動: 日平均 34,063 回(8 月 17〜19 日の 3 日間)。無料枠 10 万回/日の 34%
  • D1 の読み取り: 1 日 152 万行。無料枠 500 万行の 30%
  • CPU 上限による 503 は、原因だったテンプレート側の集計を取り除いて以降ゼロ

Bot Fight Mode で減らせる量も、思ったより小さい。 スキャナは毎回違う URL を試すので、エッジキャッシュには当たりません。一方でそのアクセスは 404 を返すだけの軽いリクエストで、起動数の 34% の中に既に含まれています。切り分けの過程で「404 が主因」と一度は診断したのですが、実際の主因は通常ページのタクソノミー集計で、404 を減らしても数字は動きませんでした。

失うものは確実に発生する。 上に挙げた非ブラウザ通信が、どれも例外なしで判定の対象になります。しかも CLI や Slack の経路は使うときにしか通らないので、死活チェックのように毎日同じ経路で鳴って気づける失敗ではありません。「CLI が 403」「Slack から投稿できない」が別々の日にぽつぽつ起きて、そのたびに Bot Fight Mode のせいかどうかを切り分けることになります。無料プランの Security Events は保持が短く、Bot Fight Mode の判定理由は出ません。

得るものが今はゼロで、失うかもしれないものの検証手段が無い。入れない、という結論になりました。

再訪する条件は数字で置いてあります。日次の Worker 起動が 7 万回に近づくか、閲覧者に見える 5xx が再び増えたとき。そのときも第一手は Bot Fight Mode ではなく Workers Paid(月 5 ドルで 1,000 万回/月)で、副作用がありません。

この構成で気をつけること

  • 無料プランの Bot Fight Mode は WAF カスタムルールの Skip / Allow が効かない。例外を作れるのは Pro 以上の Super Bot Fight Mode
  • IP Access Rules だけは先回りできるが、GitHub Actions のように IP が動く相手には使えない
  • 有効にする前に、自分の非ブラウザ通信を数える。監視、CLI、Webhook の受信、確認の curl。全部が判定の対象になる
  • 「入れて様子を見る」は、死活チェックのような毎日同じ経路で鳴るものにしか効かない。たまにしか通らない経路の 403 は、原因の切り分けから始まる
  • 守りたい枠が圧迫されていないなら、入れる理由が無い。圧迫されたときの第一手は課金の方が確実で、副作用が無い

7 月の 403 は 11 分で直りましたが、それは監視の初回実行で、しかも自分がその場で見ていたからです。同じことが CLI で起きていたら、もっと時間がかかっていたと思います。

関連

この記事をシェア