Cloudflare Workers権限制御入門——AIエージェント用トークンを最小化する
Cloudflare Workersの新しい細粒度権限制御を題材に、AIエージェントやGitHub Actionsへ渡すAPIトークンをWorker単位に絞り、Firestoreへ監査ログを残す実践手順を解説します。

はじめに
AIエージェントにデプロイや調査を任せ始めると、モデル性能より先に権限設計が効いてきます。
- ログ調査だけのはずが、Workerのコードまで読めてしまう
- GitHub Actionsのトークンが、全Workerやドメイン設定まで触れてしまう
- Preview用の修正エージェントが、本番Workerを変更できる状態になっている
- 「誰に、どのWorkerへ、どの権限を渡したか」がアプリ側に残っていない
2026年9月15日、Cloudflareは「Give every teammate and agent the right level of access to your Workers」を公開し、Workers向けの細粒度権限制御を発表しました。公式ブログでは、特定のWorkerだけにアクセスを絞り、4つのDeveloper Platformロールで操作範囲を分けられると説明されています。
本記事では、架空の予約管理SaaS「ReserveKit」を題材に、Next.js App Router + Firestore + GitHub Actions + TypeScriptで、AIエージェント用Cloudflare APIトークンをWorker単位に絞る運用を作ります。
参考にした一次情報は次の通りです。
- Give every teammate and agent the right level of access to your Workers
- Cloudflare Workers: Roles and permissions
- Cloudflare Workers: Routes
何が変わったのか
CloudflareのWorkers権限ドキュメントでは、権限はRoleとScopeを組み合わせるPermission Policyとして説明されています。ScopeはPlatform、Product、Resourceの3段階です。AIエージェントやCIに渡すなら、基本は「1つのWorkerだけ」を表すResourceスコープに寄せます。
| Role | できること | ReserveKitでの用途 |
|---|---|---|
| Metadata Read-Only | 設定、メトリクス、ログ、トレースを見られる。コードやデータは見ない | 障害調査エージェント |
| Content Read-Only | コードやD1行、R2オブジェクトなどを読める。変更はできない | レビュー専用エージェント |
| Editor | 内容と設定を更新できる。作成や削除はできない | 既存Workerのデプロイ |
| Admin | 作成、削除、アクセス付与を含む完全管理 | 人間の責任者 |
とくに実務で使いやすいのは、GitHub ActionsにResourceスコープのEditorを渡す構成です。既存Workerへの wrangler deploy には足りますが、Workerの作成や削除はできません。トークンが漏れたり、エージェントの指示がずれたりしても、影響範囲を1つのWorkerに閉じ込めやすくなります。
ハンズオン1: 権限台帳をFirestoreに残す
まず、Cloudflare Dashboardで作成したトークンの「用途」をFirestoreに記録します。トークン値そのものは保存しません。保存するのは、どのWorkerへ、誰が、どのRoleを使うのかという台帳です。
// src/lib/cloudflareAccessPolicy.ts
export type CloudflareRole =
| "Metadata Read-Only"
| "Content Read-Only"
| "Editor"
| "Admin";
export type WorkerAccessPolicy = {
workerName: string;
actor: "human" | "ci" | "agent";
actorName: string;
role: CloudflareRole;
scope: "Platform" | "Product" | "Resource";
purpose: string;
};
export function validateWorkerPolicy(policy: WorkerAccessPolicy) {
if (policy.actor !== "human" && policy.role === "Admin") {
return "CIやAIエージェントにはAdminを渡さない";
}
if (policy.actor === "agent" && policy.scope !== "Resource") {
return "AIエージェントは個別WorkerのResourceスコープに絞る";
}
return null;
}
Server Actionでは、管理者だけが台帳を追加できるようにします。db はFirebase Admin SDKで初期化済みのFirestoreクライアント、requireAdminUser は自社アプリ側の認可ヘルパーという前提です。
// src/app/admin/cloudflare-access/actions.ts
"use server";
import { FieldValue } from "firebase-admin/firestore";
import { db } from "@/lib/firebaseAdmin";
import {
type WorkerAccessPolicy,
validateWorkerPolicy,
} from "@/lib/cloudflareAccessPolicy";
import { requireAdminUser } from "@/lib/requireAdminUser";
export async function registerWorkerPolicy(input: WorkerAccessPolicy) {
const user = await requireAdminUser();
const error = validateWorkerPolicy(input);
if (error) {
throw new Error(error);
}
await db.collection("cloudflareWorkerAccessPolicies").add({
...input,
createdBy: user.uid,
createdAt: FieldValue.serverTimestamp(),
});
}
この台帳があると、PRレビュー時に「このエージェントは本当にこのWorkerだけ触れるのか」を確認できます。Cloudflare側の設定と、アプリ側の運用記録を分けて持つのがポイントです。
ハンズオン2: GitHub Actionsのトークンを分ける
Cloudflare docsでは、CI/CDやエージェントなどの自動化にはAPI tokensを使うと説明されています。また、Wranglerで細粒度権限を使う場合、wrangler login のOAuthフローではなく、アカウント所有のAPIトークンを CLOUDFLARE_API_TOKEN と CLOUDFLARE_ACCOUNT_ID で渡す流れが案内されています。
ReserveKitでは、次のようにEnvironmentごとにトークンを分けます。
| GitHub Environment | Worker | Role | Scope |
|---|---|---|---|
preview | reservekit-edge-preview | Editor | Resource |
production | reservekit-edge | Editor | Resource |
debug | reservekit-edge | Metadata Read-Only | Resource |
# .github/workflows/deploy-worker.yml
name: Deploy Cloudflare Worker
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: yarn
- run: yarn install --frozen-lockfile
- run: yarn build
- run: yarn wrangler deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
既存Workerへ wrangler deploy するにはWorkerに対するEditorが必要です。一方、存在しないWorkerを新規作成する場合はWorkers product scopeのAdminが必要です。つまり、AIエージェント用CIでは「既存の1 Workerにだけデプロイできる」状態を意図的に作れます。
ログ調査だけなら、Metadata Read-Onlyの別トークンで十分です。
export CLOUDFLARE_API_TOKEN="<METADATA_READ_ONLY_TOKEN>"
export CLOUDFLARE_ACCOUNT_ID="<ACCOUNT_ID>"
yarn wrangler tail reservekit-edge
ハンズオン3: Routes変更を別承認にする
Workerのコード更新と、RoutesやCustom Domainsの変更はリスクが違います。Cloudflare公式ブログでは、RouteやCustom Domainを追加・変更・削除するには、WorkerへのEditorに加えて対象zoneのWorkers Routes権限が必要だと説明されています。
通常のAIエージェント用CIにはRoutes権限を渡さず、PR側でも変更を見つけたら承認ラベルを求めます。
// src/lib/cloudflareRouteGuard.ts
export function requiresRouteApproval(
files: { filename: string; patch?: string }[],
labels: string[],
) {
const routeChanged = files.some((file) => {
const isWranglerConfig = ["wrangler.json", "wrangler.jsonc", "wrangler.toml"]
.includes(file.filename);
return isWranglerConfig && /routes|route|custom_domain|zone_name|zone_id/
.test(file.patch ?? "");
});
return routeChanged && !labels.includes("cloudflare-routes-approved");
}
このチェックはCloudflareの権限を代替するものではありません。レビュー体験を前倒しするためのガードです。最終的には、GitHub Actionsに渡すAPIトークンからRoutes権限を外しておくことが本丸です。
30タスクを回すときの分け方
AIエージェントが入ると、1つの大きな権限で全部を済ませたくなります。しかし、複数タスクを同時に回すときほど、トークンは作業単位で分けた方が安全です。
| タスク種別 | AI/CIに渡す権限 | バッチサイズ |
|---|---|---|
| ログ調査 | Resource + Metadata Read-Only | 3〜5件 |
| コードレビュー | Resource + Content Read-Only | 2〜4件 |
| Previewデプロイ | Preview Worker + Editor | 3件まで |
| Productionデプロイ | Production Worker + Editor | 1件ずつ |
| Route変更 | 通常CIには渡さない | 個別対応 |
作業を任せるほど、権限を強くするのではなく、狭く、短く、交換しやすくする。これがAIエージェント時代の基本になります。
まとめ
Cloudflare Workersの細粒度権限制御は、AIエージェントにデプロイや調査を任せる現場で使いやすいアップデートです。
- Metadata Read-Only、Content Read-Only、Editor、Adminを作業内容で使い分ける
- CIやAIエージェントには、原則として個別WorkerのResourceスコープを渡す
- WranglerではAPIトークンを
CLOUDFLARE_API_TOKENとCLOUDFLARE_ACCOUNT_IDで使う - RoutesやCustom Domainsの変更は、通常デプロイとは別の承認対象にする
- Firestoreに権限台帳を残し、PRレビューで確認できるようにする
AIエージェントに任せる範囲が広がるほど、「何ができるか」より「どこまでしかできないか」が効いてきます。Worker単位の権限制御は、その境界線を現実的に引くための、地味ですが強い道具です。