cloudflare

Activity

Stars and mentions · Last 30 days, Oct 6 – Nov 4

GitHub stars29,326
Mentions525
06701.3K2K2.7KOct 6Oct 20Nov 4
UTC days · repositories cloudflare owns58 repositories

Top repositories

cloudflare’s repositories ranked by pull request authors, as on trending.

Loading

Contributes to

Repositories outside cloudflare’s account where cloudflare opened or reviewed pull requests in the past 365 days.

Who noticed cloudflare

Everyone who starred or forked cloudflare’s repositories since the archive began in 2011.

Notable accounts

Ordered by the stars their own repositories have received.

Biggest fans

Accounts that starred the most of these repositories.

Breakout weeks

Each repository’s best week of new stars, with the release published around it.

Mentions

530posts
Nov 514 posts
  1. Blind RSA in circl ignored the reader you passed it --- title: Blind RSA in circl ignored the reader you passed it published: true description: tags: go, cryptography, opensource --- Cloudflare's circl has a partially blind RSA signer. Its Blind method takes an io.Reader. The blinding factor used that reader. The salt did not. It read from crypto/rand directly. That breaks the contract. Two calls with the same deterministic reader should give the same blinded message. They did not, because half the randomness came from somewhere else. The sibling code in brsa.go did it right and used the supplied reader throughout. The fix is one line in blindsign/blindrsa/partiallyblindrsa/pbrsa.go: _, err := io.ReadFull(random, salt) It used to read from rand.Reader. Now it reads from random, the reader the caller passed in. The test pins it down. TestBlindUsesSuppliedReader calls Blind twice with fresh deterministic readers that emit the same byte sequence. Both blinded messages must be identical. It fails before the fix and passes after. Callers passing crypto/rand see no behavior change. Merged as cloudflare/circl#711: circl#711

  2. 15年物のPHP通販に Cloudflare の security-audit-skill を当てたら、SQLi/XSS 以外の穴が出た きっかけ 客から引き継いで保守している通販サイトがある。PHP 5 時代に書かれたもので、プログラムは PHP ファイルで 335 本。これを PHP 8 に上げる仕事をやっていて、移行そのものは終わった。 PHP 8 化のついでに、セキュリティの見直しもやった。SQL は全部 pg_query_params に置き換えて(数えたら 752 箇所)、XSS は出力側に htmlspecialchars、管理画面と会員画面には CSRF トークン。自分で grep して、別のモデル(Astra)にも全コードを読ませてセッション周り(固定化、ログイン後の ID 再発行、Cookie 属性)を拾ってもらい、それも直した。 で、「もう残ってないだろ」と思ったところに、Cloudflare が自社の脆弱性発見基盤の出発点になったスキルを公開した。 cloudflare/security-audit-skill 星が 4 万を超えていて、どうせみんな「実際どうなの」が知りたいはず。自分もそうだった。なので、直し終わったつもりのコードにこれを当てて、何が出るか試した。 入れ方 Claude Code に入れる。Node が要る。 npx skills add cloudflare/security-audit-skill --skill security-audit --global --agent claude-code -y 最後の --agent claude-code -y を付けないと「どのエージェントに入れますか」の対話で止まる。自分は一回それで止まった。 入ったら、対象のフォルダで Claude Code を開いて一言。 security audit ./htdocs ./lib ./batch ドキュメント置き場は外した。全部食わせると関係ない物まで読んで時間を使う。 走らせている間 モデルは Claude Opus 5.5(Claude Code 2.1.232)。プランは Max。 最初の 12 分くらいは画面が「Baked for 12m」みたいな表示のまま動かないように見える。これは止まっているのではなくて、経過時間が増えていれば裏で考えている。その後「⏸ manual mode」と出て本当に止まることがあって、これはサブエージェントが「ファイル作っていいか」「このコマンド打っていいか」と聞いている。ファイル作成と読み取りは yes、ビルドやテストの実行は no にした。手元に PHP と PostgreSQL を動かすサンドボックスを用意していなかったからで、スキル自身も「サンドボックスが無ければ実行しない」と言ってくる。 途中で一度、見つけた物を 7 件まとめて「続けますか、それとも先に直しますか」と聞いてきた。「続けろ」で最後までやらせた。 所要 29 分 51 秒。 結果 スキルの報告では確認済み 8 件(重 2、中 3、低 3)、要追加確認 1 件。重 2 件は自分でもコードを追って確認した。 先に全体の評価を書くと、SQL の文字列連結は 0 件、exec/eval/unserialize/動的 include は 0 件、客が書いた文字を管理画面に出す所は全件エスケープ済み、という判定だった。つまり自分と Astra がやった侵入系の潰し込みは、第三者が見ても「済んでいる」で一致した。ここは正直ほっとした。 問題は残りの方で、重の 2 件はどっちも「金額」だった。 重 1:追加注文の合計が客の言い値になる 注文した後に商品を追加できる画面がある。その画面から鯖に送られるデータの中に「追加分の小計」が入っていて、鯖はそれを信じて、伝票の合計を「元の合計 + 送られてきた小計」で書き換えていた。 明細(どの商品を何個)は鯖が DB の単価で入れる。だから商品は正しい値段で積まれるのに、請求額だけ客が送った数字になる。ブラウザの開発者ツール(F12)で送信前にその数字を書き換えれば、5 万円分の商品を追加しながら「追加分の小計 1 円」として伝票合計に加算できる経路が、ソース上で確認できた。チェックは「0 でないこと」だけなので負数も通る。つまり元の合計を減らす方向にも使える。 笑ったのは、同じ機能のスマホ版は明細から合計を計算し直していて、正解がすぐ隣にあったこと。PC 版だけ古いままだった。 重 2:明細を送らなければ請求が下がる 注文変更の画面は明細を 1 行目から順に送る作りで、鯖は「送られてこなかった行」を消しも直しもしない。3 行ある注文で 1 行目だけ残して送ると、商品は 3 つとも残るのに合計は 1 行分になる。こっちは PC・スマホ両方。 要するにどっちも、レジを通した後に自分でレシートの金額だけ書き換える万引きだ。侵入でも漏えいでもない。DB の中身は 1 バイトも抜けない。でも集金額はその数字になる。 さらに面白かったのは、このサイトの管理画面に「合計と明細が合わない伝票」を出すレポートが既にあったこと。作った人はこの不整合がまずいと分かっていた。検知はしていた、止めてはいなかった。スキルはそこまで指摘してきた。 中・低 追加注文の本人確認がメールアドレス一致だけで、確認コードの照合が無い 入庫処理 1 本だけ CSRF トークンを検証していない(更新系 46 本中、これ 1 本) アフィリエイトのログインに失敗回数制限が無い(会員側にはある) 伝票番号だけでキャンセル済みかどうかが分かる、オープンリダイレクト、分類コードが無検証でパスに入る 「46 本中 1 本だけ」みたいな数え方をしてくるのが、grep で自分がやる時と違う。網羅してから外れ値を出している。 要追加確認 1 件 古い会員ページが 11 本残っていて、.htaccess の RewriteRule 1 行だけで塞いである。スキルは Apache の設定が読める範囲に無かったので「効いているか判定できない」と正直に言ってきた。こっちで 11 本のうち 3 本に外から POST して、3 本とも 301 が返るのを確認した。残りも同じ RewriteRule の対象だが、全部は叩いていない。「分からない物を分からないと言う」のは監査ツールとしては正しい態度だと思う。 確認済み 8 件の中に、読んで「違う」と判断した物は無かった。 3 者の答え合わせ 前提として、自分の grep と Astra は修正前のコードをそれぞれ別の観点で見ていて、Cloudflare のスキルはその修正が入った後のコードを見ている。 指摘 Cloudflare のスキル(修正後) Astra(修正前・セッション観点) 自分の grep(修正前・SQL 観点) SQL インジェクション 連結 0 件 その観点では未確認 3 行拾って修正済み セッション固定化・Cookie 属性 「直っている」側 拾って修正済み その観点では未確認 追加注文の合計が客の言い値 拾った その観点では未確認 その観点では未確認 明細を送らなければ請求が下がる 拾った その観点では未確認 その観点では未確認 入庫の CSRF 抜け 拾った その観点では未確認 その観点では未確認 アフィリ認証の回数制限 拾った その観点では未確認 その観点では未確認 確認コード未照合 拾った その観点では未確認 知っていたが低と判断 道具の性能差というより、何を疑っているかの差だった。自分は SQL の組み立てを、Astra はセッションを見ていた。今回の監査は、誰が金額を決められるかを見た。 直した 重 2 件の修正は同じ 1 行で、明細を更新した後に DB から合計を取り直してそれだけを書く。スマホ版が既にやっていることを残り 3 本に入れるだけ。中と低も含めて 12 本、当日のうちに本番に上げた。 テストは客にやってもらった。普通の操作(追加注文、数量変更、全キャンセル)で合計が正しいことと、開発者ツールで小計を書き換えて送っても合計が書き換え前と同じになること、明細を途中で切って送っても残した商品が合計に入ること。どちらも OK が出た。 アフィリエイトのパスワードのハッシュ化だけは見送った。各ページが自分でも平文で比べていて、段階移行を間違えるとログインが壊れる。回数制限だけ入れた。 かかった物 対象:PHP 335 本 時間:29 分 51 秒 枠:Max プランの週の利用量が 32% → 42%。1 回で 10 ポイント増 トークン:ccusage の集計で、入力 270、出力 17 万、キャッシュ書き込み 50 万、キャッシュ読み 3,530 万、合計 3,600 万。ccusage の単価表で API 換算すると 27 ドル 毎日回す物ではない。PHP 8 化みたいな区切りで 1 回、が現実的。 限界 スキルがレポートの先頭に書いてきた、今回の実行の限界をそのまま書いておく。 コードを一度も実行していない。「確認済み」は、ソース上で入口から書き込み先まで追えたという意味 今回は委譲なしで進めたので、見つけた人と確認した人が同じ。重い物は別の目で見ろと言ってきた(README 上は発見役と別のエージェントが反証する設計。今回の実行ではそこが省かれた、と run_status に incomplete で残っている) なので重 2 件は、自分が該当行を読んで確かめてから直した。読めば 1 分で分かる内容だった。 まとめ 侵入系を潰し終わったコードでも、「客を信じている所」は残る。しかも 15 年誰も気付かない Cloudflare のスキルは、その線引きを最初にやるから拾える 入れるのは 1 コマンド、使うのは一言、30 分、週枠 10 ポイント 出てきた物は読んで裏を取ってから直す。読めば分かる 次の通販改修では、金額を誰が決めるかと、送られなかった明細がどう扱われるかを先に見る。 おまけ。自分も開発者ツールで数字を書き換える手口は思いつかなかった。15 年やっていても甘い。

  3. 週刊Cloudflare - 2026/11/01週 こんにちは、あさひです 🙋‍♂️ 今週の Cloudflare のアップデートをまとめていきます! この記事の主旨 この記事では、前週に Cloudflare のサービスにどんな変更があったかをざっくりと理解してもらい、サービスに興味を持ってもらうことを目的としています。そのため、変更点を網羅することを優先します。 2026/10/25 ~ 2026/10/31 の変更 Wrangler 4.153.0 マイナーアップデート wrangler dev に --infer-origin-from-routes が入りました routes がある設定では、local dev が先頭の route から origin を推定します。Worker が見る request.url、Host、Origin は route の host になり、client が繋いでいる先は localhost のままです。CORS で http://localhost:* を許可していると、local だけ拒否されることがあります 推定はこれまでどおり既定です。--infer-origin-from-routes=false で切ると、routes を外さずに local の origin を残せます。--host、--local-upstream、dev.host を明示したときは、flag よりそちらが優先されます。remote mode では、これまでどおり route の host が preview session を決めます deploy、versions upload、preview の --secrets-file で、dotenv の $VAR と ${VAR} を展開するようになりました process の環境変数と、ファイル内で先に出てきた値を使います。process の値が同名の key より優先され、空文字を明示したときも process の方が優先されます secrets file は信頼できる入力として扱ってください。X=${GITHUB_TOKEN} のように、process にある値を upload できます。CI では、信頼できない変更がファイルの中身を書き換えられないようにします 未設定の参照は空文字になります。PASSWORD=pa$word は、word が未設定だと pa になります。$ を文字のままにするときは PASSWORD=pa\$word と書きます。JSON の secrets file と、pipe した bulk secret は、これまでどおり展開しません パッチアップデート 新しい project を作るときと、compatibility date を書いていないときの既定が 2026-10-23 になりました Windows で、設定 path に空白があっても wrangler login --use-keyring が keyring を使えるようになりました C:\Users\John Smith のような path では、これまで資格情報が平文の file に保存されていました。いまは暗号化して保存します local mode の kv key list が1000 件で切れずに全 key を返すようになりました --prefix の有無は問いません。これまで警告なく先頭 1000 件だけで、一覧が完結したように見えていました 依存関係を更新 @cloudflare/workers-types を ^5.20261023.1 に、workerd を 1.20261023.1 に更新 Undici を 7.30.1 に更新し、不正な HTTP response で開発ツールが落ちないようにしました(GHSA-xjj6-f33q-929c) Zod を 4.6.6 に更新し、大きすぎる collection の検証でメモリを使いすぎる問題に対応しました(GHSA-mrhx-c7xc-m3w5) miniflare@5.20261023.0-beta 4.154.0 マイナーアップデート Miniflare v5 beta の dev registry を既定で有効にし、path の解決を一箇所にまとめました 解決順は CLOUDFLARE_DEV_REGISTRY_PATH、constructor の unsafeDevRegistryPath、<Cloudflare の設定 directory>/dev-registry/v5 です。既定の場所は古い Wrangler の directory とは別で、Miniflare の major version ごとに分かれます Vite plugin v2 beta はこの既定に従います。現行の Wrangler と getPlatformProxy() は既存の session と揃えるため、これまでどおり Wrangler 側の既定 path を使います。Vite v2 と Wrangler を混ぜるときは、両方の process に同じ CLOUDFLARE_DEV_REGISTRY_PATH を置いてください enableDevRegistry: false で登録と発見を切れます。shared storage は解決した既定 path をそのまま使います。storage の owner を選ぶには registry が必要なので、unsafeEnableSharedStorage: true と enableDevRegistry: false の組み合わせは拒否されます Miniflare は MINIFLARE_REGISTRY_PATH を読まなくなりました。代わりに CLOUDFLARE_DEV_REGISTRY_PATH を使います。連携側が registry の file を読むときは resolveDevRegistryPath() を使えます Wrangler は runtime の設定、起動時の binding 表示、Flagship の active session の確認に、解決後の path を使います。CLOUDFLARE_DEV_REGISTRY_PATH で path を上書きしても、Flagship の確認は同じ path を見るので避けられません。Wrangler 側で registry を明示的に無効にしている場合は、無効のままです パッチアップデート local development で、binding の error が code と名前を残すようになりました remote binding と、Node.js 側の binding proxy(getPlatformProxy() と Miniflare#getBindings())の error が、code や numericCode のような機械可読の property を保持します。これまで property は落ち、error の名前は message に畳まれていました。たとえば "ArtifactsError: Repository not found" のようになり、local では文面でしか分岐できませんでした Node.js から呼ぶ側は、ArtifactsError のような独自の error 名も残ります。Worker の中で受け取る remote binding の error は property を残しますが、名前はこれまでどおり Error です 依存関係を更新 miniflare@5.20261023.1-beta 4.155.0 マイナーアップデート local development で、remote の Analytics Engine binding を使えるようになりました dataset に remote: true を付けると、wrangler dev と Vite plugin からの writeDataPoint() が、実際の dataset へ送られます。付けないときは、これまでどおり local では書き込みを破棄します サンプル{ "analytics_engine_datasets": [ { "binding": "EVENTS", "dataset": "my-dataset", "remote": true } ] } パッチアップデート tail の出力で、端末の制御文字を見える文字として出すようになりました pretty な tail と remote preview の出力が、log の文字列、exception、event の metadata にある制御文字を escape します。Unicode、tab、複数行の log はそのまま読めます。JSON 出力は元の構造を保ちます Flagship の flag を更新するとき、metadata を消さないようになりました wrangler flagship flags の enable、set、rollout、split、rules が、既存 flag の metadata を残します。wrangler flagship flags pull は、metadata を local の flag store へコピーします .assetsignore の文字コードを、asset manifest を作る前に確認するようになりました UTF-8 でない、または NUL 文字を含むときは、upload と local development が、理由を書いた error で止まります。pattern を知らせずに失うことはありません。standalone の Miniflare と、Wrangler、Vite、Vitest の開発連携にも同じ確認があります。BOM 付きの UTF-8 と、意図して空の file はそのまま使えます 依存関係を更新 miniflare@5.20261023.2-beta 4.156.0 マイナーアップデート 実験的な wrangler d1 insights を、account 内の全 D1 database に対して実行できるようになりました database 名は省略できます。省略すると、account 内の全 database を横断した上位の query を返します。各結果に databaseId が付きます パッチアップデート 非対話の autoconfig が、project root に静的 file と Pages の functions directory が両方あるときは止まるようになりました framework を検出できない project に functions があると、autoconfig は Cloudflare Pages project かどうかを尋ねます。CI や coding agent のような非対話の実行は、Pages ではないとみなして静的 site として続けていました。静的 file が project root にあると、root を static assets として deploy し、Pages Functions(その project の API route)は deploy されません この場合は、Pages Functions をまだ自動では移行できない、という error で止まります。Pages Functions なら、静的 file を subdirectory へ移して wrangler pages deploy するか、Functions を Worker へ移す案内が出ます。Pages Functions ではない functions なら、静的 file を subdirectory へ移すか、設定 file を置く案内です。静的 file がすでに subdirectory にある project、Pages または framework として検出された project、対話で質問に答えた実行は、対象外です autoconfig が project root を static assets の directory にするとき、公開を避ける .assetsignore を書くようになりました 検出した output directory は相対 path の "." で、project path は絶対 path でした。比較が一致せず、.assetsignore が書かれていませんでした。root に index.html がある静的 project(wrangler setup のあとや、設定なしの wrangler deploy など)では、.env、.dev.vars、.git、node_modules が公開 asset として upload されることがありました 生成した設定が使う assets directory を解決してから project root と比較します。Nuxt や SvelteKit のように、framework が独自の build output を持つ場合は、使わない root の .assetsignore を置きません 生成する .assetsignore は、既存の .wrangler、.dev.vars*、.env* に加え、Pages の upload が飛ばすものも覆います。root の /_worker.js、/_routes.json、/functions(blog/functions/index.html のような入れ子は upload されます)、任意の深さの node_modules、.git、.DS_Store、そして .npmrc です。既存の .assetsignore が .env のような狭い pattern だけのときは、.env* を足して .env.production なども飛ばします assets だけの project の root に Pages の functions または _worker.js があると、autoconfig はそれらを deploy しないと警告します。assets から除外され、server の code としても実行されないためです 実験的な wrangler d1 insights が GraphQL Analytics API に拒否されたとき、対処の分かる error で止まるようになりました これまで空の [] を出していました。rate limit では、再試行までどれだけ待つかを出します。認可の error では、API token に必要な Account Analytics: Read を示します テストと container の検証で、自分自身を名指しした Durable Object binding を解決するようになりました Worker 自身の名前を明示した Durable Object binding が、@cloudflare/vitest-plugin では test runner を指すようになりました。cloudflare.config.ts と Wrangler の設定 file の両方です。名前付きの Wrangler environment は、その environment の Worker 名を使い、base Worker への binding は外部のままです container の検証もこの自己参照を受け付けます。local と外部の binding は、実際の deployment 名で区別します 設定の変換でも、明示した binding の宛先は残ります。--name や CI の名前の上書きで deploy しても、意図した Durable Object の宛先を保ちます 依存関係を更新 miniflare@5.20261023.3-beta Workers spanContext()、setStatus()、updateName() Workers の custom span に、OpenTelemetry の span API から 3 つの method が入りました。trace の ID を log や応答に載せて、Workers Observability や export した trace と突き合わせられます。 span.spanContext() は、その span の traceId、spanId、traceFlags を返します。ID は Workers Observability と、export した trace の ID と一致します span.setStatus(status) は、span の status を "unset"、"ok"、"error" にします。error には message を付けられます。status は OpenTelemetry の宛先へ export した trace に含まれます。Workers Observability の dashboard には、まだ出ません span.updateName(name) は、span を開始したあとに名前を変えられます。request がどの route に一致したかが分かってから付け直す、といった使い方です。tracing.getActiveSpan() が返す、その invocation の root span も名前を変えられます developers.cloudflare.com URL path prefix からの Static Assets 配信 Workers Static Assets が、directory の構造を変えずに、URL の path prefix から asset を配信できるようになりました。blog の成果物を /blog/ 以下に出す、といった配置です。 Wrangler では assets.base_path、cloudflare.config.ts では worker.assets.basePath を設定します。Wrangler は 4.148.0 以降、cloudflare.config.ts を使うときは cf 1.0.0-beta.13 以降が必要です。Wrangler では assets.directory が配信する directory を選びます。cloudflare.config.ts では、build の設定が asset を供給します。 サンプル{ "assets": { "directory": "./dist", "base_path": "/blog" } } developers.cloudflare.com build と build log の 90 日保持 Workers Builds が build とその build log を、build が終わってから 90 日残すようになりました。Worker の Deployments tab の View build history か、Workers Builds API から見られます。 90 日を過ぎると、その build と log は消えます。その build が作った version と deployment は、Worker の version history に残るので、そこへ roll back できます。log を長く残すときは、Get build logs API で download します。 developers.cloudflare.com BYOIP 宛 subrequest での Workers context の維持 Cloudflare が proxy している BYOIP の hostname へ Worker が subrequest を送るとき、Workers の context を保つようになりました。この request は外部の Internet 向けではなく、Cloudflare から Cloudflare への traffic として扱われます。 これまで、一部の BYOIP 宛 subrequest では Workers の context と、元の client の信号が落ちることがありました。security rule の判定がずれたり、Bot Management が元の client ではなく Cloudflare 内部の接続を評価したり、本来は通る request が 403 になったりします。 BYOIP 宛の subrequest は、CF-Worker header と cf.worker.upstream_zone を保ちます。同じ zone への subrequest でも、zone をまたぐ subrequest でも、security rule を同じ扱いで評価できます。宛先 zone の Bot Management の field は、影響を受ける BYOIP の subrequest について、元の end user の接続を表します。bot score、verified bot、JA3 / JA4 の fingerprint は、Cloudflare の service 同士の接続を表さなくなります。 Cloudflare が proxy する BYOIP の宛先は、通常の Workers subrequest の扱いに揃いました。別の zone の hostname へ subrequest を送ると、CF-Connecting-IP は security 上の理由で Workers の client IP 2a06:98c0:3600::103 になります。同じ zone への subrequest では、これまでどおり CF-Connecting-IP に client の IP が入ります。 developers.cloudflare.com Durable Objects code update の既定 defer Durable Objects の code 更新を、既定で遅らせるようになりました。進行中の処理や、開いている接続を、deploy の直後に切られにくくなります。 Durable Object は request をまたいで動き続け、長い agent、寿命の長い WebSocket、多数の client の状態の調整に使えます。各 Durable Object は世界に 1 つなので、同時に動かせる code は 1 version だけです。これまで code を deploy すると、その変更はすぐ適用され、進行中の処理が中断し、開いている接続が切れることがありました。 いまは、動いている Durable Object が現在の code のまま、hibernate するまで最大 5 分(300 秒)動けます。進行中の処理が終わる時間を取れます。Hibernation API を使う WebSocket は、hibernate したあとに新しい code を採用しても、接続を保ちます。 待ち時間は durable_objects の code_update_strategy で変えられます。max_delay の単位は秒で、0 から 86400(24 時間)まで、ミリ秒の精度です。省略すると 300 秒です。1 回の deploy だけ変えるときは、wrangler versions deploy に --durable-objects-code-update-mode を付け、immediate か deferred を渡します。障害対応で今すぐ反映したいときや、deploy の直後に tooling が Durable Object を呼んで新しい code が必要なときに使います。 deferred は best effort で、すべての処理が終わる保証はありません。max_delay の前でも、Cloudflare の runtime 更新などで object が reset されることがあります。 サンプル{ "durable_objects": { "bindings": [ { "name": "CHAT_ROOM", "class_name": "ChatRoom" } ], "code_update_strategy": { "mode": "deferred", "max_delay": 60 } } } developers.cloudflare.com Workflows jurisdiction による実行場所の固定 Workflow を jurisdiction に固定できるようになりました。instance の実行とデータの保存が、その jurisdiction の中だけになります。GDPR や FedRAMP のような、データの置き場所の要件に合わせるときに使います。 workflows binding か、exports で宣言した Workflow に jurisdiction を付けます。指定できる値は eu(欧州連合)、us(米国)、fedramp(FedRAMP-Moderate の data center)です。 jurisdiction は Workflow を作ったときに決まり、あとから変えられません。既存の Workflow と違う jurisdiction を設定すると、wrangler deploy と wrangler versions upload は Worker を upload する前に止まります。Workflow の jurisdiction には Wrangler 4.152.0 以降が必要です。 サンプル{ "workflows": [ { "name": "my-workflow", "binding": "MY_WORKFLOW", "class_name": "MyWorkflow", "jurisdiction": "eu" } ] } developers.cloudflare.com Workers VPC binding ごとの virtual network VPC Network の binding で、使う virtual network を指定できるようになりました。同じ private IP を持つ、別々の backend へ、1 つの Worker から届きます。 network_id に cf1:vnet:<UUID> を指定します。UUID はその virtual network の ID です。account の既定の virtual network を使うときは、これまでどおり cf1:network です。binding ごとに別の virtual network を選べます。 サンプル{ "vpc_networks": [ { "binding": "PRODUCTION", "network_id": "cf1:vnet:<PRODUCTION_VNET_UUID>", "remote": true }, { "binding": "STAGING", "network_id": "cf1:vnet:<STAGING_VNET_UUID>", "remote": true } ] } developers.cloudflare.com D1 query result の 50 MB 上限 2026 年 11 月 4 日(水)から、D1 は query の結果を最大 50 MB に制限します。50 MB を超える結果を返す query は失敗し、次の error を返します。 D1 query result exceeded the maximum response size limit of 50 MB 50 MB は D1 全体の request の 99.999 パーセンタイルまでを含むため、ほとんどの利用には影響しません。REST API の import と export はこの上限の対象外で、これまでどおり動きます。 大きな結果を返している query は、適用日の前に結果を小さくしてください。SELECT * ではなく必要な列だけを選び、大きな結果は LIMIT と OFFSET でページに分けます。 developers.cloudflare.com Agents 中断した実行の再開と承認待ち @cloudflare/computer v0.7.0 から、agent の workspace で動かしている code を、deploy や crash のあとに再開でき、外部の操作を待つために止められます。tool の呼び出しを承認してから続ける、といった流れです。 WorkerJavaScriptBackend か、個々の run に onInterrupt: "replay" を付けます。回復が始まると、JavaScript は先頭からもう一度実行され、完了済みの呼び出しには記録した結果を再利用します。すでに記録した出力はもう一度表示しません。中断の前に結果が記録されていなかった呼び出しは、その操作がすでに成功していても、回復時に繰り返されることがあります。二度実行してはいけない操作は unsafeToRepeat() で扱います。 回復は次に workspace.runtime を呼んだときに始まります。traffic を待たずに回復するときは、Durable Object の alarm から workspace.runtime.recover() を呼びます。 承認を待つときは、application の関数を custom module として出します。関数の中で ctx.interrupt() を呼ぶと、application が承認の結果を渡すまで run が止まります。待ち時間は timeout に含まれません。待ちは deploy をまたぎます。保留中の呼び出しは workspace.runtime.pending() で取り、respond() で解決します。保留がすべて解決すると、run は続きます。 developers.cloudflare.com 公開 URL のない dev server の preview agent が作った site を、公開 Internet に出さずに確認できるようになりました。preview.internal のような hostname への request は Internet ではなく agent へ届き、sandbox の dev server、workspace の folder、別の Worker を agent の browser から開けます。Agents SDK v0.31.0 以降が必要です。 host の fetch は Durable Object の中で動き、その binding と state を使えます。service binding で別の Worker へ送ることもできます。target の schema を付けると、model が port や folder を選んで URL を開きます。選んだ値は turn、browser の再起動、redeploy をまたいで残ります。harness には依存せず、Pi Durable、AI SDK、TanStack AI には adapter があります。それ以外は browser.session() で繋ぎます。browserTool は model の code を専用の Dynamic Worker で動かすようになり、CodemodeRuntime の export は不要です。すでに使っている場合は、v0.31.0 の upgrade notes を参照してください。 developers.cloudflare.com Workspace 上のファイルとしての Agent Skills Agents SDK の Agent Skills が、file になりました。SKILL.md を置いた folder を、agent の Computer Workspace の /workspace/.agents/skills/ に書くと、次の turn から agent が使えます。build も deploy も要りません。agent 自身が skill を書くこともできます。 読み込み元は 3 つです。Computer の Workspace、R2 bucket、code です。Think は各 turn の前にすべての source を列挙し直します。各 SKILL.md は Agent Skills の仕様と照合されます。仕様の agent 向け案内のとおり、規則を外れた skill も warning を log に残したうえで読み込まれます。書く前に確認するときは parseSkill() を使います。 skill の script は Workspace の file で、model は Workspace の exec tool で実行します。activate_skill と read_skill_resource は Think、Pi Durable、AI SDK で同じ tool として使えます。会話の途中で skill が変わったとき、Think は cache した system prompt を組み立て直さず、新しい catalog を reminder として送ります。上書きした getSystemPrompt() は context block より上、system prompt の先頭に残ります。 agents/skills を直接使っている code には、破壊的変更です。agents:skills の Vite import、agents/skills/compile、SkillRegistry、Think の skillWorkspace はなくなりました。runner() は agents/skills/runner へ移りました。Agents SDK v0.32.0 と Think v0.23.0 以降を入れてください。 サンプルimport { Think, skills } from "@cloudflare/think"; export class MyAgent extends Think<Env> { getSkills() { return [ skills.workspace(this.workspace), // /workspace/.agents/skills skills.r2(this.env.SKILLS_BUCKET, { prefix: "skills/" }), ]; } } developers.cloudflare.com Browser Run 既定ブラウザの Chrome 152 Browser Run の既定が Chrome 152 になりました。session は現在の web platform の機能、描画の修正、security patch を受けます。code の変更は要りません。 あわせて、次も変わっています。 Quick Actions の既定 User-Agent が Chrome 119 ではなく Chrome 152 を名乗ります。Browser Run の User-Agent を allowlist や照合に使っている場合は、rule を更新してください。userAgent parameter で独自の値を指定する使い方は残ります Quick Actions の request が通常の Chrome からの request に近くなり、site に拒否されにくくなりました 大きな response header を返す page が正しく読み込まれます 信頼しない root 証明書を、trust store から外しました /links が絶対 URL を返します。link だけを辿る crawl は相対 link も辿ります /markdown と、crawl の Markdown が、code block の言語を残します timeout した Quick Actions が揃った error を返します。exception を投げた Outbound Worker は、browser へ HTTP error を返します developers.cloudflare.com Access generic SAML 経由の Google Workspace SCIM Google Workspace の custom SCIM profile(beta)から、user の更新と deprovision を Cloudflare One へ送れるようになりました。使えるのは、Google 側の beta に入っている organization です。 Cloudflare One では、generic SAML の identity provider を使います。専用の Google Workspace 連携は SCIM に対応していません。Google で SAML app を作り、その app の autoprovisioning に Cloudflare の SCIM endpoint と secret を入れます。Cloudflare One で Enable user deprovisioning を有効にすると、Google が user を deprovision したときに、有効な Access session を取り消します。Google 側で設定した deprovision の遅延は、そのまま適用されます。既存の Google Workspace OIDC client への対応は、Google が OIDC と SCIM の組み合わせに対応してから追加されます。 group の所属は別に設定します。Google の SAML app に groups attribute を送らせると、認証のときに、選んだ Google group が user に含まれます。SCIM の provisioning scope で group を選んでも、所属は同期されません。Google の SCIM provider では、この対応がまだ追加中です。 Cloudflare One の user は、先に Access で認証するか、Cloudflare One Client で device を登録しないと、SCIM の更新が適用されません。 developers.cloudflare.com MCP tool-call analytics の response_status MCP の tool-call analytics で、応答の status を指定できるようになりました。account、portal、server の timeseries endpoint に、response_status query parameter が付きます。 success、error、cached のいずれか 1 つを数えるか、all ですべての status を含めます。既定は success です。これまで件数には失敗と cache された呼び出しも含まれていました。以前と同じ全 status の件数が要るときは、response_status=all を明示します。 応答には、選んだ response_status が含まれます。空文字を含む不正な値は 400 です。 developers.cloudflare.com Cloudflare One Access 画面の HTML と Liquid template Cloudflare Access の、利用者に見える画面を、HTML と Liquid の template で差し替えられるようになりました。対象は login、identity-denied、forbidden、command-line の承認画面です。画面ごとの見た目と、利用者への案内を自分で書けます。Liquid の変数で、手順を足したり、拒否された理由を示したりできます。 template は Cloudflare One の dashboard で作り、editor で検証してから、application に割り当てるか、account の既定にします。 developers.cloudflare.com SCIM population viewer identity provider が Cloudflare One へ provisioning した user と group を、dashboard で確認できるようになりました。API を叩いたり、support へ問い合わせたりせずに、Cloudflare が受け取った resource を見られます。 Zero Trust > Integrations > Identity providers で、SCIM resources の user 数か group 数を選ぶと viewer が開きます。 user は username、email、name、resource ID で、group は name か resource ID で検索できる resource の attribute を、整形した表示か JSON で見られる group の member を見られ、Cloudflare と IdP の resource ID を provisioning log と突き合わせられる 編集権限があれば、Cloudflare の SCIM population から resource を 1 件ずつ削除できる viewer が見せるのは、provisioning 済みの resource であり、provisioning の event 履歴ではありません。失敗した request は SCIM の provisioning log で調べます。既存の SCIM deprovision と policy の挙動は変わっていません。 developers.cloudflare.com Data Localization Suite データ所在地の設定をまとめた Overview dashboard に、Data Localization Suite の Overview が入りました。account のデータの所在地に関する設定を、読み取り専用で一画面にまとめたものです。Regional Services、Customer Metadata Boundary、Geo Key Manager を、複数の画面や API を行き来せずに確認できます。traffic をどこで処理し、log と private key をどこに置くか、を見る画面です。 Application Security > Data Localization Suite から開くか、dashboard の検索で "DLS" と入れます。product ごとの tab があります。 Regional Services: region 化した hostname(region か zone で絞り込めます)、region 化した IP binding、region 化した Spectrum application と、それぞれの region Customer Metadata Boundary: 有効か、region(US か EU)、region 外からの access を許すか Geo Key Manager: geo policy の付いた custom 証明書の一覧。hostname、zone、region、status 各項目から、その設定を変える dashboard の画面へ移れます。各 tab から、その product のドキュメントへも移れます。 developers.cloudflare.com Cloudflare WAN の regional Gateway HTTP inspection Cloudflare WAN の traffic を、Regional Services の Gateway 処理 region で検査できるようになりました。選んだ region の外にある data center に入った HTTPS を、暗号化のまま選んだ region へ渡し、そこで TLS を復号して HTTP を検査します。機密性の高い traffic を、どの場所で処理するかを選べます。 入口は IPsec tunnel、GRE tunnel、Direct CNI です。unified routing が必要です。処理 region が適用されるのは Gateway の HTTP traffic で、Gateway の DNS と network policy の処理には適用されません。 処理 region の設定は account team に連絡して行います。 developers.cloudflare.com DNS Advanced Nameserver の nameservers.type DNS settings の endpoint が、Advanced Nameserver を nameservers.type: "cloudflare.advanced" で表すようになりました。適用は段階的で、7 日ほどかかる見込みです。これまでこの設定は "foundation_dns": true でした。 account へ適用が届く前は、Advanced Nameserver でも endpoint が nameservers.type: "cloudflare.standard" を返し続けます。資格のある account では、適用の途中でも PATCH で nameservers.type: "cloudflare.advanced" を受け付けます。対象は zone の /zones/{zone_id}/dns_settings と、account 既定の /accounts/{account_id}/dns_settings で、変更は同時に進みます。 foundation_dns の boolean は zone と account 既定の設定で、非推奨の互換 alias として残ります。新しい書き込みには nameservers.type: "cloudflare.advanced" を使ってください。移行のあいだは、foundation_dns の読み書きを続けられます。1 つの PATCH に両方を入れたときは、値が一致している必要があります。食い違う値は拒否されます。nameservers.type を閉じた enum として扱っている client は、"cloudflare.advanced" を認識するよう更新してください。 この API の変更は Foundation DNS の subscription も、zone で Advanced Nameserver が有効かどうかも変えません。zone の移行も要りません。将来の API 変更で、endpoint から foundation_dns parameter は消えます。nameservers.type へ移したあとは、client から boolean の処理を外してください。 developers.cloudflare.com 新しい DNS records 画面の既定化 5 月に案内した新しい DNS records の画面が、Enterprise を含むすべてのプランで既定になりました。classic の DNS records 画面は廃止され、dashboard からは開けません。 公開当初の新しい画面では、既存の DNS record の type を変えられませんでした。その後の追加で、DNS records の画面から type を変えられます。たとえば A から CNAME です。dashboard はこの変更に batch DNS records の endpoint を使います。 developers.cloudflare.com SSL/TLS Keyless SSL の ECDSA 証明書での TLS 1.3 Keyless SSL が ECDSA 証明書で TLS 1.3 を使えるようになりました。zone で TLS 1.3 が有効なとき、Keyless の ECDSA 証明書を出す hostname へ接続した visitor は TLS 1.3 で接続します。private key は自分の key server に置いたままです。 RSA 証明書を使う Keyless SSL の接続は、これまでどおり最大 TLS 1.2 です。 developers.cloudflare.com Analytics SQL API introspection の include_attributes SQL API の introspection endpoint に、include_attributes query parameter が入りました。true にすると、dataset の attributes JSON 列にある key と、その型を、account の直近 7 日のデータから見つけます。見つかった key は dataset の応答の attributes field に入ります。 一部の dataset は追加の key-value を attributes JSON 列に置き、attributes['http.method'] のような key の完全一致で問い合わせます。これまでの発見方法である include_custom_attributes は名前を custom_attributes field で返していました。新しい名前は、返る名前が attributes 列を通じて触る key だと分かりやすくするためのものです。 include_custom_attributes と custom_attributes は非推奨ですが、まだ動きます。連携は include_attributes と attributes へ移してください。 developers.cloudflare.com Audit Logs Audit Logs v2 の account analytics Audit Logs v2 に、account 単位の audit log の analytics が dashboard で出るようになりました。選んだ期間について、Actions over time の chart が成功と失敗を分けて示します。 summary card は Total actions、Unique actors、Products impacted、Failure rate です。card を選ぶと、action の種類、認証方法、上位の actor、上位の product、失敗した action を調べられます。値を filter として含めたり外したりすると、analytics と audit log の table の両方が更新されます。 account の analytics は、organization 単位の audit log にはありません。 developers.cloudflare.com China Network 中国本土向け Challenge Page Cloudflare China Network 経由で出る Challenge Page が、中国本土で host している Turnstile widget を使うようになりました。本土の visitor は、widget が国境を越えた request を出さずに済むため、challenge を速く、安定して完了できます。 China Network の zone が Challenge Page を出すとき、この変更は自動で適用されます。作業は要りません。Cloudflare の global network から配信される visitor は、これまでどおり global の Challenge Page です。 developers.cloudflare.com WAF WAF 緊急リリース(2026-10-25) Cloudflare Managed Ruleset に、Brotli の脆弱性 CVE-2026-111497 に関する rule が 2 件追加されます。どちらも既定の action は Log で、有効です。一致した request を見られるので、影響を確認してから Block へ切り替えられます。 Brotli の rule: request body が Brotli だと宣言している request(Content-Encoding: br)に一致します。正当な client が Brotli で request body を送る場合も含まれます。Accept-Encoding: br には一致せず、Cloudflare の Brotli 応答圧縮の設定にも影響しません。既定の action は Log です Next.js の rule: Next.js の image optimization endpoint(/_next/image)への request に一致します。正当な image optimization の traffic も含まれます。既定の action は Log で、request は止めません。action を Block にすると、最適化した画像が読み込まれず、この endpoint を使う application の画像配信が乱れることがあります ruleset の既定のままなら、どちらも一致した request を log するだけで、止めません。analytics で一致を確認し、必要ならどちらかの rule に Block を設定できます。Log のまま有効にしても、見えるようになるだけで、一致した request は止まりません。 ruleset 単位の action override で、これらの rule に Block を適用していると、既定が Log でも、配置された時点で一致した request が止まることがあります。偽陽性で、正当な traffic を止めることがあります。配置の前に、既存の ruleset override を確認してください。traffic を止めずに評価するときは、rule ごとの override を Log にし、一致を見てから Block へ切り替えます。 developers.cloudflare.com WAF リリース(2026-10-28) PHP の out-of-memory(OOM)の脆弱性 CVE-2026-109618、CVE-2026-109619、CVE-2026-115560 について、rule の式が更新されました。Security Researcher からの報告に基づく、PHP の Capture the Flag に伴う反復的な改善です。 Cloudflare Managed Ruleset と Cloudflare Free Ruleset の、PHP Remote Code Execution Attack (CVE-2026-109618, CVE-2026-109619, CVE-2026-115560) #6 が対象です。action はどちらも Block のままです。 developers.cloudflare.com WAF 緊急リリース(2026-10-30) Cloudflare Managed Ruleset に、Nuxt Image と IPX の脆弱性向けの rule が 1 件追加されました。既定は Disabled です。検出は部分的なので、advisory に従い、最新 version へ修正してください。 Nuxt Image / IPX - Explicit GIF or Animation Modifier Exposure developers.cloudflare.com WAF リリース(2026-10-30) PHP の out-of-memory(OOM)の脆弱性 CVE-2026-109618、CVE-2026-109619、CVE-2026-115560 について、rule の式が更新されました。Security Researcher からの報告に基づく、PHP の Capture the Flag に伴う反復的な改善です。action はいずれも Block のままです。 Cloudflare Managed Ruleset と Cloudflare Free Ruleset の PHP Remote Code Execution Attack (CVE-2026-109618, CVE-2026-109619) #3 Cloudflare Managed Ruleset と Cloudflare Free Ruleset の PHP Remote Code Execution Attack (CVE-2026-109618, CVE-2026-109619) #4 Cloudflare Response Ruleset の Default block for High confidence Signatures developers.cloudflare.com

    1 like
  4. Path Normalization at the Tunnel Edge: Why a Route Matches on localhost but Not Through a Tunnel Your /admin route works on localhost:8080. Through the tunnel, //admin returns something different, /public/../admin behaves differently again, and a rule you assumed was guarding the path never seems to fire. Nothing is wrong with your app. Several components are each parsing the same path with different rules, and a tunnel adds one more parser to the chain. That chain is about to change. Starting 2027-01-25, new cloudflared releases will turn on URL normalization by default, which Cloudflare says may change which ingress rule matches a request. This guide explains what changes, what doesn't, how path-confusion auth bypasses work, and how to test your own routing before the default flips. > Quick summary > > - cloudflared 2026.10.0 and later can normalize the URL path before matching ingress rules. > - It is off by default today and on by default in new releases from 2027-01-25. > - Cloudflare scopes the change to tunnels with ingress rules that require Cloudflare Access validation; other configurations are unaffected. > - The documented transformations are dot-segment removal, backslash-to-slash conversion and merging repeated slashes. The path forwarded to your origin is not modified. > - Percent-encoding (such as %2F) and trailing slashes are not in the documented list, so test them instead of assuming. What Cloudflare announced On October 22, 2026, Cloudflare published a changelog entry titled "Cloudflare Tunnel supports URL normalization." The key facts: - Availability: cloudflared version 2026.10.0 and later. GitHub lists 2026.10.0 as released on October 5, 2026; the newest release I could find as of November 5, 2026 is 2026.10.6. - Default today: off. The flag is inverted, so the documented default of --disable-path-normalization is true. - Default from 2027-01-25: on, for new releases. - Scope: Cloudflare says it improves security for tunnels with rules that require Access validation, and that configurations without such rules are unaffected. - Origin path: unchanged. Normalization applies to the path used for matching, not the path forwarded. To enable it now: cloudflared tunnel --disable-path-normalization=false run <TUNNEL_NAME_OR_UUID> or set TUNNEL_DISABLE_PATH_NORMALIZATION=false on each cloudflared replica and restart it. One operational detail deserves attention. Standalone cloudflared binaries running as a service auto-update every 24 hours by default (autoupdate-freq). Package-manager installs, Windows and interactive runs don't auto-update. If your tunnel auto-updates, or you deploy a floating latest image tag, the new default will arrive without a deliberate upgrade. Either set the flag explicitly now or pin your version until you've tested. What gets normalized, and what doesn't Cloudflare's configuration docs describe the behavior with three examples. Incoming paths are shown as cloudflared receives them: | Incoming path | Used for ingress matching | Sent to origin | | --- | --- | --- | | /hello/../path | /path | /hello/../path | | /hello//path | /hello/path | /hello//path | | /hello\path | /hello/path | /hello\path | In words: dot segments (. and ..) are removed following RFC 3986 section 5.2.4, backslashes become forward slashes, and repeated slashes are merged. Notice what the documentation does not list: percent-decoding, trailing-slash handling, semicolon path parameters, and case folding. This matters because RFC 3986 draws a hard line here. A percent-encoded unreserved character such as %61 (the letter "a") is equivalent to its literal form, so normalizers may decode it. A percent-encoded reserved character is not equivalent: %2F is not the same as /, and a standards-compliant normalizer must not collapse them. My takeaway: treat anything outside Cloudflare's three documented transformations as "not normalized" until your own test shows otherwise. I verified this against Cloudflare's documentation, not by instrumenting cloudflared, so the test matrix below is the way to confirm behavior on your version. Why localhost and a tunnel disagree Hitting localhost:8080/admin involves one path parser: your framework's. A tunneled request passes through at least three: 1. The Cloudflare edge, which has its own URL normalization feature. Per Cloudflare's docs, incoming-URL normalization is on by default, and certain baseline steps (merging adjacent slashes, for example) are always applied and cannot be disabled. 2. cloudflared, which matches the path against your ingress rules. 3. Your origin, whose framework, reverse proxy or static-file handler applies its own decoding and dot-segment logic. There is a fourth, easily overlooked participant: your test client. Browsers resolve .. segments before sending a request, and the WHATWG URL parser treats backslashes as slashes for http and https URLs. So clicking around in a browser almost never exercises these inputs. Even curl collapses dot segments unless you pass --path-as-is. When you "tested" a path and it worked, the client may have rewritten it into a safe one first. Any disagreement between parsers is a potential bypass. If layer two thinks the request is for /public/... and layer three thinks it's for /admin, a rule written for one is evaluated against the other. The path-confusion auth bypass, step by step Consider a tunnel that protects /admin with Access validation at cloudflared, defense in depth behind your Access policy: tunnel: <TUNNEL_UUID> credentials-file: /etc/cloudflared/<TUNNEL_UUID>.json ingress: - hostname: app.example.com path: ^/admin(/|$) service: http://localhost:8080 originRequest: access: required: true teamName: <your-team-name> audTag: - <access-application-aud-tag> - hostname: app.example.com service: http://localhost:8080 - service: http_status:404 Ingress rules are evaluated first to last, and path is a regular expression parsed with Go's regexp/syntax package. Now send GET /public/../admin/users. - Normalization off: the matcher sees /public/../admin/users. The first rule's regex requires the path to start with /admin, so it doesn't match. The request falls through to the second rule, which has no Access requirement, and is forwarded with its original path. - Your origin collapses the dot segment, routes the request to /admin/users, and serves it. The JWT check that was supposed to guard /admin never ran. - Normalization on: the matcher sees /admin/users, selects the first rule, and requires a valid Access JWT. The origin still receives the original path. The pattern is generic: a security decision made on one interpretation of a path, with the action taken on another. This is exactly what Orange Tsai's 2018 Black Hat and DEF CON research, "Breaking Parser Logic," catalogued. His slides showed that /foo;name=orange/bar/ is read as /foo;name=orange/bar/ by Apache, Nginx and IIS, but as /foo/bar/ by Tomcat and Jetty, and used that gap to bypass proxy-level ACLs. It keeps happening, including this year: - Heimdall (CVE-2026-42274): before version 0.17.14, the identity-aware proxy matched rules on the raw path while downstream components normalized dot segments, so a request like /user/../admin could be authorized as one path and processed as another. - Quarkus (CVE-2026-50559): path-based authorization policies could be bypassed with percent-encoded semicolons (%3B), slashes (%2F) or backslashes (%5C), because the security layer and the routing or static-file layer decoded them differently. - @opennextjs/cloudflare (CVE-2026-3125): a backslash in /cdn-cgi\image/ slipped past Cloudflare's edge interception, then a Worker's URL class converted it to a forward slash, reaching a vulnerable handler. Fixed in 1.17.1. - fast-uri (CVE-2026-6321): the library decoded percent-encoded separators and dot segments before removing dot segments, so distinct URIs could collapse onto one normalized path. Fixed in 3.1.1. Different stacks, same root cause: two parsers, one path. The four inputs to test Dot segments (/../ and /./) This is the classic traversal-style confusion, and the one the new cloudflared setting directly addresses. Test plain .. and the encoded forms %2e%2e and %2E%2E. RFC 3986 says percent-encoded unreserved characters are equivalent to literals, and the WHATWG URL parser treats %2e as a dot in path segments. Many origins decode %2e before removing dots, as the Quarkus advisory shows. Cloudflare documents dot-segment removal but doesn't say whether encoded dots are decoded first, so this row needs a real test. Double slashes (//) Frameworks differ on whether //admin equals /admin. Some merge, some treat the empty segment as significant, and some reject it. Cloudflare's cloudflared normalization merges them for matching only. A pattern like ^/admin won't match //admin without normalization, and an origin that does merge it will happily serve /admin. Trailing slashes (/admin vs /admin/) The documented normalization doesn't mention trailing slashes, so assume they are untouched. That makes regex anchoring decisive: ^/admin$ matches /admin but not /admin/, while many frameworks (Express by default, for instance) route both to the same handler. Write protected-path patterns as ^/admin(/|$), or better, protect the whole host and carve out public exceptions. Encoded slashes (%2F) and friends %2F is where parsers split most. Some origins decode it into a real separator before routing, some keep it inside a single segment, and some reject it outright (many servers expose a setting for this). Because RFC 3986 treats it as non-equivalent to /, cloudflared's documented normalization has no obvious reason to decode it, and a standards-following matcher would treat /admin%2Fusers as a single opaque segment. Your origin might not. The same goes for %5C (backslash) and %3B (semicolon). The Quarkus case shows how a bypass can hide in exactly that gap. A test matrix you can run today Assume the config above, with the origin returning 200 on /admin/* only when an Access JWT is present. For each request, the pass condition is that it is denied the same way plain /admin/users is. | Request path | Matched as (off) | Matched as (on) | Documented? | | --- | --- | --- | --- | | /admin/users | /admin/users | /admin/users | Baseline | | /public/../admin/users | raw | /admin/users | Yes | | //admin/users | raw | /admin/users | Yes | | /admin//users | raw | /admin/users | Yes | | \admin\users | raw | /admin/users | Yes | | /admin/ | raw | raw | Trailing slash not listed | | /%61dmin/users | raw | Not documented | No, test it | | /admin%2Fusers | raw | Not documented | No, test it | | /public/%2e%2e/admin/users | raw | Not documented | No, test it | | /admin;x=1/users | raw | Not documented | No, test it | Two cautions when reading results. First, Cloudflare's own edge normalization is a separate layer. Some rows may look identical with cloudflared normalization on and off because the edge already rewrote them; that's fine, but you want to know which layer is doing the work. Second, cloudflared tunnel ingress rule <URL> shows which rule a URL matches, but Cloudflare's docs don't say whether it honors the normalization flag, so confirm with live traffic as well. Here's a small Python harness that sends paths exactly as written, which curl and browsers won't always do: import http.client, ssl HOST = "app.example.com" PATHS = [ "/admin/users", "/public/../admin/users", "//admin/users", "/admin//users", "\\admin\\users", "/admin/", "/%61dmin/users", "/admin%2Fusers", "/public/%2e%2e/admin/users", "/admin;x=1/users", ] ctx = ssl.create_default_context() for path in PATHS: conn = http.client.HTTPSConnection(HOST, context=ctx, timeout=10) conn.request("GET", path) print(f"{path!r:34} -> {conn.getresponse().status}") conn.close() Run it twice: once with the tunnel on current defaults, once with --disable-path-normalization=false. Any path that returns something other than the baseline denial is a finding. For one-off checks, curl --path-as-is stops curl from collapsing dot segments before sending. Rollout checklist before 2027-01-25 1. Find out if you're in scope. Search your configs for access: blocks with required: true. No such rules, and Cloudflare says you're unaffected. 2. Check where the access block sits. An open issue in Cloudflare's docs repository reports that on cloudflared 2026.7.1 a top-level originRequest.access block wasn't enforced, while the same block under an individual ingress rule was. This is a user report, not confirmed documentation, so verify on your version rather than trusting either reading. 3. Run the matrix in staging with normalization off, then on. Record every difference. 4. Audit regex anchors. Replace unanchored or $-terminated patterns for protected paths with ones that handle trailing slashes. 5. Turn it on early, explicitly. Setting the flag now means the January change is a no-op for you. After the cutover, check the release notes to confirm the flag is still honored. 6. Control auto-updates (--no-autoupdate, version pinning) until the matrix passes on the new version. 7. Fix the origin too. Normalization at the tunnel is defense in depth. Make your application reject ambiguous paths (encoded slashes, backslashes, dot segments) rather than depending on the layer in front of it. 8. Log raw request targets so a probing attempt shows up as %2e%2e or \ in your logs, not as a cleaned path. FAQ Does this change the path my application receives? No. Cloudflare states the URL path sent to your origin stays the same. Only the path used to select an ingress rule is normalized. Will my tunnel break on 2027-01-25? Only if you have rules requiring Access validation and your matching relies on un-normalized paths. Cloudflare says configurations without such rules are unaffected, but it recommends testing path-based routing before the date. Is normalizing at the tunnel enough to stop path-confusion bypasses? It closes the gap for the transformations it performs. It does not make your origin's parsing consistent with the tunnel's, and Cloudflare doesn't document decoding of reserved characters like %2F. Treat it as one layer. Does the same risk exist with other tunnels and reverse proxies? Yes. Any component that makes a routing or authorization decision on a path it hasn't normalized the way the next component will is exposed. Run the matrix against whatever sits at your edge. Webhook signature verification behind tunnels is a separate failure mode, covered in the earlier post on signatures. Sources - Cloudflare Tunnel supports URL normalization (changelog, Oct 22, 2026) - Cloudflare Tunnel configuration file: how traffic is matched - Cloudflare Tunnel run parameters: disable-path-normalization - Cloudflare Tunnel origin parameters: Access settings - Cloudflare URL normalization (Rules docs) - cloudflared releases on GitHub - cloudflare-docs issue 32006: access block placement - RFC 3986: Uniform Resource Identifier (URI): Generic Syntax - Orange Tsai, "Breaking Parser Logic" (Black Hat USA 2018 slides) - CVE-2026-42274 (Heimdall) - CVE-2026-50559 (Quarkus) advisory - CVE-2026-3125 (@opennextjs/cloudflare) - CVE-2026-6321 (fast-uri) --- Originally published at instatunnel.my

  5. 初めてのCI/CD。pushしただけで公開まで流れる理由を、図で理解した(GitHub Actions + Cloudflare Workers) 1. はじめに CI/CDという言葉は知っていましたが、なかなか勉強するまでには至りませんでした。それが仕事で使うことになり、とうとう勉強を始めることにしました。 とはいえ、どこから手をつければいいのか分かりません。調べると記事はたくさんあり、手順も丁寧に書かれています。ただ、どれも「こう書けば動く」が中心で、なぜpushしただけで最後まで流れるのかは、読んでもいまいち分かりませんでした。 幸い、今はAIという優秀なパートナーがいます。そこで今回は、AI(Claude Code)に助けてもらいながら、自分の環境で1つずつ検証していきました。 まず作ったのは、GitHubにpushすると、GitHub ActionsがCloudflare Workersへ自動で公開してくれるしくみです。画面で操作したのは3つ、書いたファイルも3つだけで、pushすると公開まで勝手に進んでいきます。実際に動かしてみても、やはり疑問は「何で流れるの?」、これだけでした。 この記事では、手順に加えて、なぜ最後まで自動で流れるのかを図で説明します。同じところでもやもやしている人の参考になればうれしいです。 なお、記事の内容はすべて自分の環境で動かして確かめていますが、CI/CDは初心者です。間違いなどがあれば、コメントで教えていただけるとうれしいです。 2. 作ったもの GitHubのmainブランチにpushすると、約30秒で https://test.tobaru.net のページが更新されるしくみです。ページはHTMLファイル1つだけの、いちばん小さなものにしました。 ひとことで言うと、GitHubに置いたファイルを、GitHubのロボット(Actions)がCloudflareまで運んでくれます。運ぶときにCloudflareのドアを開けるので、ロボットに合鍵(APIトークン)を預けてあります。 !04_overview.png 自分で用意したサーバーはありません。DNSのレコードとhttpsの証明書も、手では作っていません。 3. 環境 | 項目 | 内容 | |---|---| | GitHub | Freeプラン、Privateリポジトリ | | Cloudflare | Freeプラン | | ドメイン | Cloudflare Registrarで取得したtobaru.net(DNSもCloudflare) | | デプロイの道具 | cloudflare/wrangler-action@v4、wrangler 4 | | 作業したPC | Windows 11、VS Code、Git | 自分のPCにNode.jsやwranglerは入れていません。wranglerはGitHub Actionsの中でだけ動きます。 ドメインのDNSがCloudflareにあることが、後で出てくる「1行でDNSと証明書ができる」の条件になります(5章の(5))。 4. 手順 画面で操作したのは(1)〜(3)の3つだけです。(4)はファイルを書くだけ、(5)はpushするだけです。 (1) GitHubにリポジトリを作る - 名前はtobaru-net-test、Privateにしました - README・.gitignore・ライセンスは付けず、空のリポジトリにします(ローカルで作ったファイルをそのままpushするため) (2) CloudflareでAPIトークンを作る 1. 右上の人のアイコン → プロフィール → APIトークン → トークンを作成する 2. テンプレート「Cloudflare Workers を編集する」を使う 3. 範囲を絞る - アカウントリソース: 自分のアカウントだけ - ゾーンリソース: 特定のゾーン → tobaru.netだけ 4. 作成する。トークンは一度しか表示されないので、そのまま(3)に貼る 範囲を絞っておくと、万一トークンが漏れても、できることは「Workersの更新」と「tobaru.netの設定」だけです。 (3) GitHubのSecretsに登録する リポジトリの Settings → Secrets and variables → Actions → New repository secret で、2つ登録します。 | Name | 値 | |---|---| | CLOUDFLARE_API_TOKEN | (2)で作ったトークン | | CLOUDFLARE_ACCOUNT_ID | CloudflareのアカウントID | (4) ファイルを3つ書く tobaru-net-test/ ├─ public/ │ └─ index.html … 見せるページそのもの ├─ wrangler.jsonc … Cloudflareへの注文書(何を・どこで出すか) └─ .github/ └─ workflows/ └─ deploy.yml … Actionsへの指示書(いつ・何をするか) public/index.htmlは、ふつうのHTMLです(中身は省略)。 wrangler.jsoncです。ふつうのJSONはコメントが書けませんが、jsonc(JSON with Comments)は//で書けます。 { // Cloudflare側でのWorkerの名前(名札)。 // この名前のWorkerがなければ新しく作り、あれば中身を置き換える "name": "tobaru-net-test", // Workersの動き方の基準日 "compatibility_date": "2026-10-01", // 静的なファイル(HTML・画像など)の置き場所。この中身がそのままサイトに並ぶ "assets": { "directory": "./public" }, // どのURLで出すか。custom_domain: true でDNSレコードと証明書も作ってくれる "routes": [ { "pattern": "test.tobaru.net", "custom_domain": true } ], // xxx.workers.dev というCloudflareのURLでは公開しない "workers_dev": false } .github/workflows/deploy.ymlです。 name: Deploy # いつ動くか(イベントの条件) on: push: branches: [main] # mainブランチにpushされたとき workflow_dispatch: # Actionsタブの「Run workflow」ボタンで手動でも動かせる jobs: deploy: runs-on: ubuntu-latest # GitHubが数十秒だけ貸してくれるLinux permissions: contents: read steps: # 1. このcommitのファイル一式をランナーに取り出す - uses: actions/checkout@v5 # 2. wranglerをインストールして動かす(commandを書かなければ wrangler deploy) - name: Deploy to Cloudflare Workers uses: cloudflare/wrangler-action@v4 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} # 金庫の合鍵 accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} # どのアカウントに送るか wranglerVersion: "4" (5) pushする git init -b main git add -A git commit -m "最初のページとデプロイ設定" git remote add origin https://github.com/<ユーザー名>/tobaru-net-test.git git push -u origin main pushしてから約30秒で、https://test.tobaru.netが開けるようになりました。10秒おきに確かめると、最初の3回はつながらず、4回目で200が返ってきました。 5. なぜ最後まで自動で流れるのか 答えを先に書くと、何かがずっと監視しているわけではありません。「イベントが起きたら、こう動く」という小さな決まりが、前の結果を受け取ってつながっているだけです。 pushしてから公開されるまでを、5つの段階に分けました。 !05_sequence.png 画像をクリックすると、大きく表示されます。 ここからは、私が分からなかったところを順に説明します。 (1) GitHubはフォルダを監視しているの? 監視はしていません。pushやプルリクエストなどのイベントが起きたときにだけ、GitHubがそのcommitの.github/workflows/を開きます。中のYAMLを全部読んで、on:の条件に合うものだけを動かします。 それが分かる例です。 | こんなとき | デプロイは動く? | |---|---| | README.mdだけ変えてpush | 動く。deploy.ymlは変わっていないが、「mainへのpush」というイベントが起きたから | | deploy.ymlを書き換えたが、pushしていない | 動かない。GitHubは自分のPCの中を見ていない | | deploy.ymlを書き換えてpush | 書き換えた後の内容で動く。そのcommitのファイルを読むから | | 別のブランチにpush | 動かない。on:がbranches: [main]だから | | プルリクエストをmainにマージ | 動く。マージするとmainにcommitが入り、mainへのpushと同じになるから | 「受付の人がマニュアルをずっと見張っている」のではありません。「呼び鈴が鳴ったらマニュアル棚を開いて、今回の用件に当てはまるページを探す」イメージです。マニュアル棚が.github/workflows/、ページの見出しがon:です。 (2) ファイルを指定していないのに、なぜwrangler.jsoncが使われるの? deploy.ymlのデプロイの手順には、「どのファイルを送るか」を書いていません。それでも動くのは、書かなかったときの決まり(既定値)が2段階あるからです。 1. wrangler-actionの決まり: commandを書かなければdeployを実行する(action.ymlに「Defaults to "deploy"」とあります)。workingDirectoryも書かなければ、リポジトリのいちばん上で実行する 2. wranglerの決まり: wrangler deployは、実行したフォルダでwrangler.jsonc / wrangler.json / wrangler.tomlを探して読む しかも、wrangler.jsoncそのものは送られません。「何を・どこに・どんな名前で」の指示として読まれ、送られるのはassets.directoryに書いたpublic/の中身です。 決まった名前・決まった場所に置けば何も書かなくても動き、変えたいときだけ書く。この考え方を「設定より規約(Convention over Configuration)」と呼ぶそうです。(1)の.github/workflows/も同じです。 (3) Cloudflareで何も作っていないのに、なぜWorkerができるの? wrangler.jsoncの"name": "tobaru-net-test"で、「この名前のWorkerを、この中身にして」と頼んでいるからです。Cloudflareは、その名前のWorkerがなければ新しく作り、あれば中身を置き換えます。上の図のaltのところです。 どのアカウントに作るかはaccountId、入っていいかはapiTokenで伝えています。どちらもSecretsから渡しているので、deploy.ymlには値そのものを書いていません。 (4) Secretsは何のため? Secretsは合鍵を入れておく金庫です。deploy.ymlには${{ secrets.CLOUDFLARE_API_TOKEN }}と、「金庫のこの引き出しを使って」とだけ書きます。合鍵そのものはどのファイルにも書かないので、コードを人に見せても漏れません。 (5) 1行でDNSと証明書までできるの? できました。routesに"custom_domain": trueと書くと、「このホスト名をまるごとこのWorkerにする」意味になり、CloudflareがDNSのレコードとhttpsの証明書を作ってくれます。 ただし、次の2つがそろっているからできることです。 - tobaru.netのDNSが、同じCloudflareアカウントにある(Cloudflareが自分でレコードを書き込める) - APIトークンにtobaru.netを触る権限がある(4章の(2)でゾーンを絞って許可した) DNSが別の会社にあるドメインでは、この1行だけでは済みません。 custom_domainを付けずに"pattern": "test.tobaru.net/api/*"のように書くと、「このURLに来たアクセスだけWorkerが受け取る」という別の使い方になります。この場合、DNSのレコードは自分で作ります。 6. 警告が出たところ 最初は、他の記事と同じくcloudflare/wrangler-action@v3を使っていました。デプロイは成功しましたが、Actionsの結果のAnnotationsに、警告1件とお知らせ1件が出ました。 !01_annotations.png (1) Node.js 20 is deprecated(警告) > Node.js 20 is deprecated. The following actions target Node.js 20 but are being forced to run on Node.js 24: cloudflare/wrangler-action@v3. wrangler-action@v3はNode.js 20向けに作られていて、GitHub側がNode.js 20を廃止していく途中なので、Node.js 24で動かしているという知らせです(GitHubのお知らせ)。今は動いていますが、将来動かなくなるおそれがあります。 調べるとv4が出ていて、action.ymlにusing: "node24"とありました。入力の項目(apiToken・accountIdなど)は同じなので、@v3を@v4に変えるだけで警告が消えました。 この修正もdeploy.ymlを1文字直してpushしただけです。しくみそのものの設定を変えた場合も、ページを変えた場合と同じ流れで反映されます(5章の(1)の表の3行目)。 (2) ubuntu-latestがUbuntu 26に移る(お知らせ) > The ubuntu-latest label will migrate to Ubuntu 26 beginning October 19, 2026. runs-on: ubuntu-latestの中身が、順に新しいUbuntuに切り替わるという案内です(runner-imagesのIssue)。今回のワークフローはwranglerを動かすだけなので、そのままにしています。 (3) nameを変えるときの注意 つまずいたわけではありませんが、しくみから考えると注意が必要だと分かったところです。 nameを変えてpushすると、Cloudflareは名前が変わったとは受け取りません。「その名前のWorkerがないので新しく作る」と受け取ります(5章の(3))。古いWorkerは残ったままになるので、変えるときは古いほうの片付けも必要です。 7. Cloudflareの画面で確かめる Cloudflareの画面では何も作っていませんが、pushしたあとに開くと、次の2つができていました。 (1) Workersの一覧 「Workers & Pages」を開くと、wrangler.jsoncのnameで付けたtobaru-net-testがあり、routesで頼んだtest.tobaru.netがつながっていました。 !02_workers.png 「デプロイメントを表示」を開くと、pushした回数ぶんの履歴が並んでいます。GitHubのcommitと1回ずつ対応しています。 (2) DNSのレコード tobaru.netのDNSのページには、自動で作られたtestのレコードがありました。自分では1つも作っていないのに、本当にレコードが出ていてびっくりしました。 !03_dns.png | 見えているもの | 意味 | |---|---| | 左端の鍵のアイコン | 自動で作られたレコードなので、ここからは編集できない | | タイプ「Worker」 | AやCNAMEではなく、「このWorkerに届けて」という特別なレコード | | コンテンツ「tobaru-net-test」 | wrangler.jsoncのname。名札でつながっている | | 「プロキシ済み」 | アクセスはいったんCloudflareが受け取る | 外のDNS(1.1.1.1)にtest.tobaru.netを聞いてみると、返ってくるのはCloudflareのIPアドレス(104.21.x.xなど)でした。「プロキシ済み」なので、サーバーの場所ではなくCloudflareのアドレスが返ります。応答のヘッダーにはServer: cloudflareとCF-RAY: …-NRTがあり、東京の拠点から返っていることも分かりました。 8. まとめ 最初は、なぜ自動で最後まで流れるのかが、いまいち理解できませんでした。でも、監視という大掛かりな仕組みではありませんでした。イベントが起きたときの動きが決まっていて、その決まりどおりに動くと、流れるように最後までつながる。それが分かったとき、美しいなーと思いました。DNSの画面に、自分では作っていないレコードが出ていたときも感動しました。 今回分かったことを並べると、次の3つです。 - GitHubは監視していない。イベントが起きたときに.github/workflows/を読み、on:に合うものだけを動かす - 書いていないことは既定値で動く。wrangler-actionはdeployを実行し、wranglerは決まった名前のwrangler.jsoncを探す - CloudflareはnameでWorkerを見分け、なければ作り、あれば置き換える。custom_domain: trueならDNSと証明書も作る 次は、プルリクエストを作ったらチェックだけを動かし、mainにマージしたらデプロイする形(CIとCDの分離)を試す予定です。チェックも、pull_requestというイベントに決まりを足すだけで作れるはずです。

    1 like
  6. > You personally want to believe that LLM usage is somehow essentially solely responsible for the bad product It's been confirmed (at least, in the HN thread) by a representative of Jepsen that Orkes were using adversarial LLM review. And given that even very basic test setups failed instantly, it's hard to believe that this LLM review was coupled with human review. So what I believe is that they likely adopted a form of agentic coding that had no routine human review in the process. And that, I suspect, is how they ended up in the situation of believing their software was well-tested and conformant when it wasn't. More on that in a bit. > history has shown bad products were made by human programmers all the time I've spent an enormous amount of time trying to teach the teams I work with techniques for creating software that isn't bad, including solid and thorough testing and review processes. The all-in LLM approach requires discarding those very techniques in favor of just trusting that enough layers of adversarial agents will get it right. My own direct experience is that this doesn't work. And earlier this year I posted a long piece which, among other things, included a review of some studies into LLM-assisted coding which found that it's not the case that software is just as bad as before: it's the case that with LLMs, software is getting worse than before, quickly. The CircleCI report cited there, for example, is based on a huge sample of CI runs to show both the rate of failures and the time to resolution of failures, and both of those numbers went strongly in the wrong direction. CircleCI has continued to update their data, and though there's been a small rebound the state of software quality, as far as they're able to measure it, still appears to be significantly down from what it was prior to the advent of LLMs. > If you read a positive report that said LLM usage went fine, would you be just as similarly convinced that LLM usage is actually good? My experience is that such reports tend to be one-off anecdotes and also tend to struggle under scrutiny. For example: also noted in that long piece I linked above was Cloudflare's big announcement earlier this year that they had used LLMs to build their own clone of Next.js from the ground up using an agentic coding/review/fix loop. This was, at the time, held up as a triumph and a great success story for LLM coding. Except if you read what I wrote about that, and follow some of the links I provided, you'll see that on release it was unable to run a basic default "Hello, world" Next.js app, and that it was riddled with security issues because, it turned out, their LLM coding agents had simply failed to copy over and use the full original Next.js test suite. Which created the false impression that Cloudflare had a well-tested and verified implementation when in fact they did not. That's the same sort of situation Orkes found themselves in, and yet at the time I don't recall prominent LLM coding advocates accusing Cloudflare (as they are accusing Orkes, now, in various places) of being dilettantes who simply didn't know how to use LLMs properly. > People will read what they want into their report, and invent any number of scenarios that justify it. I pointed out a basic tension with LLM usage: the selling point of LLM coding is that it generates so much software so quickly that no human can keep up with it, so you essentially have to remove human review from the loop. But LLM-only loops with no human review are not reliable enough to actually get away with doing this. ParticleDB is the latest example of this problem, but far from the only one. To the extent that anyone has a "chip on their shoulder" in this thread, it seems to be you, since you're becoming extremely defensive and hostile over what is pretty basic and reasonable criticism of the effectiveness of LLM coding, rooted in the actual observable evidence in front of us.

    14 points
  7. Cloudflare AgentsがPi 1.4に対応したので、Agentの中断・復旧・永続化を検証する > 中断された Agent は、最後のチェックポイントから正しく再開しました。副作用が2回起きたのは、そのせいです。 この記事の結論 Cloudflare Agents SDK が Pi 1.4 に対応したので、Durable Object 上の Agent を実際に停止させ、会話履歴・タスク・ツール実行がどこまで復旧するかを測りました。復旧機構は期待どおり動きましたが、Agent が復旧することと、外部の副作用が重複しないことは別です。 - Pi 1.4 で追加された Hibernation と Parked Tasks は、実際の Durable Object で確認できた - replay: "safe" は冪等性を検査しない。非冪等なツールに付けると、同じ副作用が2回起きる - Storage Error Recovery は進行中の呼び出しを救わず、次の操作から Pi を開き直す仕組みだった - agents@0.30.0 には、別タスクが動いているとリトライ待機中のタスクを起こせない既知の不具合がある。使うなら 0.30.1 以降にする | 観測したもの | 結果 | | --- | ---: | | 再起動後の会話履歴 | 読み戻せた | | 完了済みタスク | 二重実行されなかった | | safe な冪等ツール | 再実行されたが副作用は1回 | | safe と宣言した非冪等ツール | 副作用が2回 | | unsafe ツール | 自動再実行されず、完了もしない | | Hibernation | 待機中に別インスタンスで再開 | 検証コードはこちらです: github.com/tanahiro2010/slides/tree/main/slides/s… そして、この記事で一番伝えたいことを先に書いておきます。 replay: "safe" は、ツールを安全にする機能ではありません。 Pi に対して「このツールは途中で止まったら再実行してよい」と宣言するだけです。副作用が本当に冪等か、同じリクエストを2回受けても外部 API が重複処理しないかは、ツールの実装側が保証しなければなりません。 > この記事は「Cloudflare AgentsがPi 1.4に対応したので、Agentの中断・復旧・永続化を検証する」の登壇内容に、発表で話しきれなかった検証手順・実測値・失敗した測定を足したものです。対象は Cloudflare Agents と Pi Durable を使って、外部 API やデータベースを操作する人です。 --- 1. 「Pi 1.4 対応」の4項目を分解する 2026年10月22日、Cloudflare は Agents SDK の Pi 1.4 対応を発表しました。Changelog には次の4項目が並んでいます。 - Hibernation - Parked Tasks - Storage Error Recovery - Model Stream Retry 最初は、4つとも Pi 1.4 の新機能だと思いました。しかし、配布物と変更履歴を追うと、出どころは2層に分かれていました。 | Changelog の項目 | 実際の変更元 | | --- | --- | | Hibernation | Pi 1.4 の sleeping タスクを Agents SDK が配線 | | Parked Tasks | Pi 1.4 の機能を Agents SDK が公開 | | Storage Error Recovery | Agents SDK 側の修正。Pi のバージョンとは別 | | Model Stream Retry | Agents SDK 側の修正。Pi 1.3 でも成立 | 特に Model Stream Retry の PR には、Pi 1.3.0 と Pi main の両方で判定を確認したと書かれていました。つまり、Storage Error Recovery と Model Stream Retry を Pi 1.3 と 1.4 で比較するのは軸が違います。比較すべきなのは agents@0.29.0 と agents@0.30.x です。 ベンダーの Changelog は、利用者にとって分かりやすい単位で変更をまとめています。一方、障害が起きたときに調べるには、どのパッケージのどの層が責任を持つかを分ける必要があります。 2. Pi、PiHarness、Lifecycleの役割 今回の構成には3つの層があります。 | 層 | 担当 | | --- | --- | | Pi Durable | Agent の実行ループ、タスク、チェックポイント、replay | | PiHarness | Pi に Durable Object の SQLite と起動処理を接続 | | Lifecycle | Durable Object 上で長時間の仕事を実行し、必要なときに起こす | Pi Durable は Cloudflare のパッケージではありません。@earendil-works/pi-durable として提供され、Agents SDK 側の PiHarness が Durable Object に組み込みます。 ここで重要なのは、保存と再開が別の機能だということです。 - 会話履歴やタスクの保存先は、pi_ で始まる SQLite テーブル - 眠っているタスクを再開するのは、Lifecycle の Wake ジョブと Durable Object Alarm 保存できていても、起こす仕組みがなければ再開しません。逆に、再開できてもチェックポイントがなければ、どこから続けるか分かりません。今回の検証では、この2つを混ぜないようにしました。 Pi Durable の設計記事には、次のようにあります。 > every step of a run is a task that stores a checkpoint before it moves on. このチェックポイントが、再起動後に「完了済みのタスクをもう一度実行しない」ための基礎になります。 3. まず、観測そのものを永続化する 障害復旧を測るとき、最初に作るべきなのは実験対象ではなく記録機構です。Durable Object の書き込みは、呼び出した瞬間にすべてがディスクへ確定するわけではありません。 検証用のレコーダは、イベントを SQLite に書いたあと、通常は ctx.storage.sync() を呼ぶようにしました。直後に ctx.abort() を呼び、再起動後にイベントが残っているかを比較します。 | 記録の仕方 | 再起動 | 再起動後に残った行数 | | --- | --- | ---: | | sync() あり | した | 1 | | sync() なし | した | 0 | どちらもコンストラクタで発行したインスタンス ID が変わっていました。本当に同じインスタンスが動き続けたのではなく、再生成後のインスタンスが SQLite を読んでいます。 ctx.abort() は通常の throw とは違います。アプリケーションコードでは catch できず、Durable Object を即座にリセットします。障害注入に通常の例外を使うと、Agent の復旧ではなく「同じインスタンスのエラー処理」を測ってしまいます。 この土台を確認してから、以降のイベントはすべて sync() ありで記録しました。ログに残らなかった観測を、復旧の証拠には使っていません。 4. 会話履歴とタスクは再起動を越えて残る ツールを1回実行する Agent を動かし、処理が完了したあとに ctx.abort() で Durable Object を落としました。再起動後に transcript とイベントを読みます。 | 観測 | 再起動前 | 再起動後 | | --- | ---: | ---: | | インスタンス識別子 | — | 変化した | | transcript のエントリ数 | 5 | 5 | | ツール実行開始 | 1 | 1 | | 副作用 | 1 | 1 | 完了済みのタスクは二重実行されませんでした。同じ operationId を使った submit も、1回目は accepted: true、2回目は accepted: false になりました。異なる ID を使うと accepted: true になるため、submit 全体が壊れたわけではありません。 ここまでは、Pi Durable がチェックポイントと submit の識別子を使って、完了済みの仕事を保護している結果です。 5. クライアントが切れても、Agentは動き続ける 次に、モデルのリトライで処理時間を伸ばし、途中で WebSocket を切断しました。クライアントがいなくなったあとも実行が続くかを確認します。 結果は、最終的に done になりました。モデル呼び出しは「失敗2回、成功1回」の3回です。切断前後でインスタンス IDは変わっていません。 これは「落ちて復旧した」のではありません。Agent の実行がクライアント接続に依存していなかったということです。 再接続後は snapshot から現在の状態を組み立て直せました。ただし、切断中に流れたイベントをカーソルで途中から再生できるわけではありません。イベントストリームは状態を追うためのものではありますが、履歴ストリームの代わりではありません。 6. Parked Tasksはクラッシュループを止める 同じ場所で何度も ctx.abort() を発生させ、restartLimit を2に下げて測りました。 | 落とし方 | 障害発火 | インスタンス生成 | pending() | 次の Alarm | | --- | ---: | ---: | --- | --- | | ctx.abort() | 2 | 2 | parked | なし | | 通常の例外 | 1 | 0 | 空 | なし | 通常の例外は Durable Object をリセットしないため、再起動回数には入りません。Parked に到達するには、実際に再起動される障害が必要です。 Parked になったタスクは、放置しても Durable Object を起こし続けません。手動では2つの抜け方があります。 | 操作 | 戻り値 | wait() | | --- | --- | --- | | session.retry() | true | done | | session.abort() | ok | unanswered / aborted | retry() はやり直す操作、abort() は放棄する操作です。クラッシュループを止めるだけでなく、運用者が判断して再開か放棄かを選べる設計になっています。 なお、ツールが api.output() で進捗を8回出しても、再起動カウントは戻りませんでした。Pi が進捗として扱うのは、ツールの出力ではなく、タスクが次の状態をコミットしたことです。まだ完了していない1つのツールが出力を吐き続けても、クラッシュループの上限はリセットされません。 7. Storage Error Recoveryは「次の操作」から効く Storage Error Recovery は、ストレージエラーが起きたあとに Pi を開き直せる機能です。ここでは Storage を Proxy で包み、次の2種類のエラーを注入しました。 - commit() などから任意のエラーを返す - 読み取りから StorageRequestError を返す どちらも、現在進行中の呼び出しはエラーになりました。しかし、次の submit() は成功し、最終的に done になりました。致命的な Storage エラーでは factory の実行回数が1から2に増えましたが、Durable Object 自体のインスタンス IDは変わっていません。 つまり、これは Durable Object のクラッシュ復旧ではありません。PiHarness が壊れた Pi をキャッシュから外し、次の操作または Wake ジョブの次のステップで Pi を開き直しています。 進行中の呼び出しは救われません。 仕様にも、すでに実行中の呼び出しは SessionFailed で失敗するとあります。factory は何度実行されても正しく初期化できるように書く必要があります。 8. replay: "safe" は副作用を守らない ここが今回の中心です。4種類のツールを用意し、実行開始・副作用の確定・終了を別々に記録しました。副作用確定直後に Durable Object を落とし、再起動後の挙動を比較します。 | ツール | replay | 冪等性 | 実行開始 | 副作用 | 完了 | | --- | --- | --- | ---: | ---: | ---: | | write_record | safe | あり | 2 | 1 | 1 | | bump_unsafe | 未指定 | なし | 1 | 1 | 0 | | bump_safe_unguarded | safe | なし | 2 | 2 | 1 | | bump_safe_guarded | safe | あり | 2 | 1 | 1 | write_record は同じ値を書き込むので、2回実行されても結果は変わりません。bump_safe_unguarded はカウンターを1増やすだけで、実行されるたびに結果が変わります。このツールに safe を付けると、Pi は宣言を信じて再実行し、副作用が2回確定しました。 一方、bump_unsafe は自動再実行されません。副作用確定後に落としたケースでは、副作用は1回だけ残りましたが、ツールは完了していません。Agent は中断されたツール結果をモデルへ渡して run を決着させます。 中断されたツールの transcript は、専用のエントリになるわけではありません。通常と同じ pi.tool-result ですが、内容が次のように変わります。 <harness> [error] Tool bump_unsafe was interrupted and may have partially run </harness> このメッセージは、モデルに「部分的に実行されたかもしれない」と伝える平文です。その後にモデルが同じツールを呼び直すかどうかは、モデルの判断に委ねられます。今回の実モデルによる3試行では呼び直しませんでしたが、1モデル・1プロンプト・3試行だけなので一般化はしません。 bump_safe_guarded には冪等キーを入れました。再実行自体は起きていますが、2回目はキーを見て副作用をスキップし、確定回数は1回に収まりました。 ここから分かることは明確です。safe の判断を間違えても Pi は警告しません。 外部 API、決済、メール送信、データベース更新などをツールに持たせるなら、safe の設定とは別に冪等キーや重複排除を実装する必要があります。 9. Model Stream Retryはエラー文言で判定される Model Stream Retry は、途切れたモデルストリームを一時的なエラーとして扱い、ターン全体を再試行します。配布物を読むと、判定はエラー本文の正規表現でした。 同じ回数だけ失敗させ、エラー文言だけを変えました。 | エラー文言 | 結果 | モデル呼び出し | | --- | --- | ---: | | The stream ended before the model finished its answer. | unanswered | 1 | | The stream ended without the model finishing its answer. | done | 3 | | Model stream stalled: no data received for 60s. | unanswered | 1 | | Model stream timed out: no data received for 60s. | done | 3 | | insufficient_quota: the account has no remaining credit. | unanswered | 1 | 再試行対象の文言では「失敗2回、成功1回」の3回になりました。つまり、再試行は部分的なストリームの続きではなく、モデルリクエスト全体の作り直しです。 baseDelayMs を100秒に設定しても、待機時間の上限は約60秒でした。既定の maxAgentDelayMs が60,000ミリ秒だからです。設定値を大きくしたからといって、無制限に待つわけではありません。 10. Hibernationは、待機中に別インスタンスで再開する Hibernation の測定では、最初に alarm の時刻を比べました。しかし、ローカルの workerd では onWake の有無にかかわらず、alarm の秒数が一致しました。これは測定対象を間違えています。 Hibernation の本質は、alarm の時刻ではなく、待機中に Durable Object がメモリから退避できるかです。そこで実デプロイし、コンストラクタでインスタンス IDを記録して、リトライがどのインスタンスで走るかを見ました。 onWake を渡した場合は、次のようになりました。 model_error@old-instance → instance_started@new-instance → model_request@new-instance onWake を渡さない場合は、次のようになりました。 model_error@same-instance → model_request@same-instance | onWake | リトライを走らせたインスタンス | | --- | --- | | 渡す | 別インスタンス | | 渡さない | 同じインスタンス | リトライ待機中は、測定のために一度もリクエストを送りませんでした。途中で触るとオブジェクトが起きてしまい、退避を観測できないからです。 処理中の work と、リトライ待ちの sleep は別の状態です。work がある間は heartbeat Alarm が30秒ごとに再設定されます。一方、sleep 中は最初に起きるタスクの時刻が次の Alarm になります。この2つを混ぜると、Hibernation の測定結果を誤読します。 11. agents@0.30.0 の不具合を再現する 公式が案内した agents@0.30.0 で検証を始めようとしました。しかし、このバージョンには、別タスクが動いているとリトライ待機中のタスクが起きない既知の不具合があります。 1つの Harness で、次の2本を並行させました。 - セッションA: 60秒走るツールを実行する - セッションB: 再試行対象のエラーを返し、12秒の backoff に入る | | B単独 | 並行する work あり | | --- | ---: | ---: | | agents@0.30.1 | 12秒 | 12秒 | | agents@0.30.0 | 12秒 | 57秒 | 0.30.0 では、B のリトライが A の完了まで止まっていました。0.30.1 では、A が動いていても B は予定どおり12秒後に再開します。 原因は配布物の差分からも確認できます。0.30.0 は Pi が active かどうかだけを見ていました。0.30.1 では、sleep の期限 wake.at を次の待機期限に使い、期限に達したら pi.resume() を呼びます。 これは煽るための話ではありません。PiHarness が Beta と案内されていることの、具体的な例です。Pi 1.4 の Hibernation を検証するなら、agents@0.30.1 以降を使う必要があります。 12. Pi 1.4に上げると、1.3には戻せない Pi 1.4 は session storage の SQLite schema を更新するため、Pi 1.3 へロールバックできません。 実際に Pi 1.4 で1ターン実行したあとの schema version は 2 でした。配布物のマイグレーション定義を比べると、次の差があります。 | バージョン | マイグレーション | | --- | --- | | pi-durable@1.3.0 | version 1 のみ | | pi-durable@1.4.0 | version 1 と version 2 sleepingStatus | Pi 1.3 のマイグレーションランナーは、対応していない新しい schema version を見つけるとエラーにします。追加された sleepingStatus は、Pi 1.4 の Hibernation を支える sleeping タスク状態そのものです。 つまり、Durable Object を退避できるようになったことと、ロールバックできなくなったことは、別々の変更ではありません。同じ schema 変更の表と裏です。 Pi 1.4 と1.3を比較するときは、同じ Durable Object を使い回してはいけません。実験用の名前を分け、マイグレーションを一方向のものとして扱います。 13. 測定をやり直して分かったこと この検証では、最初の測定結果をそのまま記事にしませんでした。対照を組み直した結果、3つの主張を修正または保留にしています。 WebSocketのイベント取りこぼし 最初は、遅いイベントリスナーがイベントを取りこぼすように見えました。しかし、WebSocket を8秒で閉じていたことが原因でした。ドレイン時間を30秒に延ばすと、遅いリスナーでもイベント数は一致しました。 進捗の定義 自前カウンターの更新を Pi の進捗だと思い込んでいました。api.output() を8回呼ぶ対照に変えても、未完了のツールは Parked になりました。ツールの出力ではなく、タスクが状態をコミットしたかどうかが進捗です。 19分のモデルストリーム Worker 内の faux Provider で約19.2分のストリームを流したところ、モデル呼び出し1回のまま完走しました。ただし、これは外部モデル API へのHTTP接続を19分維持した測定ではありません。公式の「15分を越えるリクエストは切断されることがある」という制約を否定する材料にはならないため、結論は保留にしました。 障害復旧の検証では、「結果が出た」だけでは不十分です。そもそも障害が起きていなかった、観測側が先に接続を閉じた、という可能性を対照で潰す必要があります。 14. まとめ Cloudflare Agents と Pi 1.4 の復旧機構は、実際に動きました。会話履歴は再起動後に読み戻せ、完了済みタスクは二重実行されず、クラッシュループは Parked Tasks で止まり、リトライ待機中の Agent は Hibernation によって別インスタンスで再開できました。 しかし、Pi が守っているのは Agent の実行状態です。あなたの外部副作用まで自動的に exactly-once にしてくれるわけではありません。 実装時には、次の点を守る必要があります。 - 外部操作には冪等キーや重複排除を入れる - replay: "safe" は、2回実行しても安全だと自分で証明できるツールだけに付ける - unsafe にしても、確定済みの副作用が消えるわけではない - factory は Storage Error Recovery で何度も呼ばれる前提で書く - Hibernation を使うなら agents@0.30.1 以降を使う - Pi 1.4 に上げた Durable Object は、Pi 1.3 に戻せない 最後に、検証から持ち帰る教訓を1つに絞るならこれです。 「復旧した」と「重複せずに完了した」は、別の主張です。 参考 - Cloudflare Changelog — Agents SDK now supports the latest Pi 1.4 release — Pi 1.4 対応として発表された4項目 - Cloudflare Docs — Pi — PiHarness、Recovery、Hibernation の公式ドキュメント - Cloudflare Docs — Pi Extensions — ツールの replay 設定と復旧時の扱い - agents CHANGELOG — 0.30.0 / 0.30.1 の変更 - cloudflare/agents#2683 — onWake と並行タスクに関する既知の不具合 - pi-durable CHANGELOG 1.4.0 — Pi 1.4 の sleeping タスクと schema migration - Pi Durable の設計記事 — チェックポイントとタスク実行の設計

  8. *Looks even further* Oh they also explicitly violated the BSD3 licence of boringtun by copy-pasting original source without attribution, and even explicitly deleted copyright notices in files to hide it. github.com/firezone/sanstun/blob/main/src/handsha… github.com/cloudflare/boringtun/blob/master/borin… Cool cool. Fuck this guy.

    7 likes
  9. Reddit · · r/BestGitHubRepos · u/company_url_finder · cloudflare/skills

    Cloudflare Skills: Skills for teaching agents how to build on Cloudflare (3.2k stars) What it does All text below is quoted directly from the project README. >Skills for teaching agents how to build on Cloudflare. Skills: - cloudflare: Helps agents discover which Cloudflare products fit their needs, then find the right skills and docs - agents-sdk: Building stateful AI agents with state, scheduling, RPC, MCP servers, email, and streaming chat - durable-objects: Stateful coordination (chat rooms, games, booking), RPC, SQLite, alarms, WebSockets - wrangler: Deploying and managing Workers, KV, R2, D1, Vectorize, Queues, Workflows - workers-best-practices: Writing, reviewing, or configuring production Workers - web-perf: Auditing Core Web Vitals (FCP, LCP, TBT, CLS), render-blocking resources, network chains >npx skills add https://github.com/cloudflare/skills Repository cloudflare/skills Repository stats |Metric|Value| |:-|:-| |Stars|3,200| |Forks|328| |Last release|None| |Languages|Shell 88.3%, Python 11.7%| |Contributors|46| |AI contributor accounts (Claude / Codex / Cursor / Copilot)|@claude (4 commits), @cursoragent (1 commits)| |AI co-authored commits|Claude: 4| Main contributor |Field|Value| |:-|:-| |Name|Brendan Irvine-Broque| |GitHub|@irvinebroque| |Commits to this repo|20 (top contributor)| |Bio|sr. director of product, workers @cloudflare| |Company / Location|San Francisco, CA| |Followers|281| ────────────────────────────── 📌 How many great GitHub repos have you discovered and completely forgotten about? Don't let another useful tool disappear into your Reddit feed. Get the community's best repos, organized by category, plus key takeaways from real developer discussions, delivered to your inbox twice a week. 📬 Never lose track of great repos again. Subscribe free.

    5 points
  10. If you are a vibe coder like me. Use this cloudflare auditing skill to audit your web application cloudflare/security-audit-skill #AI #vibecoding You do not know what you do not know. Knowing security and this recipe will help you harden your app.

    6 likes · 1 repost

Achievements

Awards earned by the repositories cloudflare owns. Select an award to embed its badge.

Gold
121st place finishes
Silver
112nd place finishes
Bronze
113rd place finishes
Awarded repositories
11Owned by cloudflare now

Repositories

Ordered by gold, then silver, then bronze.

  1. pingoraBest: 1st by stars, 11,440 stars513
  2. workerdBest: 1st by stars, 3,231 stars500
  3. security-audit-skillBest: 1st by stars, 3,617 stars131
  4. cloudflare-osBest: 1st by stars, 7 stars101
  5. moltworkerBest: 2nd by stars, 656 stars022
  6. computerBest: 2nd by stars, 16 stars020
  7. hellogopherBest: 2nd by stars, 352 stars011
  8. miniflareBest: 2nd by stars, 419 stars010
  9. vinextBest: 2nd by stars, 600 stars010
  10. flanBest: 3rd by stars, 586 stars002
  11. gokeyBest: 3rd by stars, 412 stars001

Every award

Top-three finishes of these repositories in completed trending days and weeks, newest first.

  1. security-audit-skill
  2. security-audit-skill
  3. security-audit-skill
  4. security-audit-skill
  5. security-audit-skill
  6. computer
  7. cloudflare-os
  8. cloudflare-os

A repository earns an award for finishing first, second or third in a completed trending day or week, ranked by new stars, new forks or pull request authors. Awards follow a repository through renames and transfers and count toward its current owner. Weeks run Monday to Sunday, UTC.