本ファイルは cloudflare/skills(Apache-2.0)を基に改変しています。詳細は リポジトリルートの ATTRIBUTION.md を参照。
Turnstile Spin
「Turnstileを設定して」という依頼を、動作するend-to-end連携へ変える。成果物はwidget、選定した全挿入箇所のfrontend、既存backendでの正式なserver-side siteverify、成功報告前の実検証である。
agentはscripts/配下のscriptを実行し、JSON出力のstatusで分岐する。API calls、retry、error handlingなどの決定的処理はscriptが持つ。agentはorchestration、codebaseの読解、推奨案の選定、frontend/backend編集を担う。
Turnstile連携の中核手順はdevelopers.cloudflare.com/turnstile/spinを参照する。ただし、repository所有権、AIDDの編集原本→sync、秘密値・Account IDの非開示、package manager選択はこのrepository固有の安全overlayであり、upstream文書を直接取得して上書きしない。製品仕様で不一致があれば公式docsを優先し、local overlayと統合して編集原本へ反映する。
Token種別はCloudflare公式のAccount API Token互換表を正本とする。現行の互換表ではTurnstileはAccount API Token非対応である。Turnstile widget管理には、対象AccountだけへAccount.Turnstile:Editを付けたuser-owned API tokenを例外的に使う。Workers/D1/R2のCI/CD用account-owned tokenとは分離し、相互に使い回さない。
このSkillをloadする条件
依頼に次のいずれかが含まれるときにloadする。
- 「Turnstile」「CAPTCHA」「bot対策」「bot protection」
- "siteverify", "cf-turnstile-response"
- 「このformを保護」「bot signupを止める」「spam signup」
- 特定のsignup、login、contact formと「Cloudflare」または「bot」の組み合わせ
Turnstileに言及しないWorkers、Pages、R2などのCloudflare作業ではloadしない。
成果物先行フロー
Turnstile設定の依頼は、repository調査、安全かつ正式な挿入箇所の選定、範囲内の実装、検証を行う権限を含む。多段の質問票にはしない。Evidence → Decide → Draft → Validate → Diffで進め、最初にread-only調査を行い、推奨構成を1つ選び、連携を作り、検証済み結果とunified diffを示す。credentials/Account identity、推論不能なproduction hostname、破壊的なCAPTCHA移行など、利用者だけが決められる境界でのみ停止する。
-
すぐ開始する。 作る成果を短く伝え、事前確認の往復を挟まずauth確認とread-only scanへ進む。
-
CLI確認。 最初にlockfileを調べ、既存package managerを使う。pnpm-lock.yaml/pnpm-workspace.yamlならpnpm exec wrangler、package-lock.jsonならnpx --no-install wrangler、yarn.lockならyarn exec wranglerを使う。このrepositoryの例はpnpmである。helperはapi.cloudflare.comへのcurlとproject-localなwrangler whoamiでAccountを列挙する。Step 8ではWrangler 4.109+のwrangler turnstile widget createを優先し、利用できなければ同梱curl scriptへfallbackする。global installは不要であり、勧めない。
-
Authとscope probe(最初の不可逆操作より前)。 scripts/auth-probe.shを実行し、statusで分岐する。
-
Account選択。 multiple_accounts後にokなら選択済み。単一Accountの場合はscriptが内部で選ぶ。既存resourceが個人Accountにありチーム運用へ変えたい場合は停止し、移行を別taskとして計画する。チーム側へ重複widget/resourceを作らない。選択済み番号はsession内のCLOUDFLARE_ACCOUNT_INDEXだけで扱い、完全なAccount IDを保存しない。
-
Domain。 localhostと127.0.0.1を必ず含める。productionはpackage.jsonのhomepage、wrangler.toml、README.md、AGENTS.md、git remoteを調べ、根拠が最も強いhostnameを選ぶ。明確なhostnameが1つなら確認質問しない。見つからなければlocalhost連携を先に作成・検証し、production widget作成前に不足hostnameを1件だけ尋ねる。
-
Codebase scan。 次の3点を質問せず検出する。
- Frontend framework(Next.js、Astro、SvelteKit、Hugo、vanilla等)→ widget embed snippetを決める
- Backend handlerの場所(Express route、Next.js API route、Rails controller、Workers fetch handler、Pages Function等)→ siteverify snippetを決める
- 既存CAPTCHA(reCAPTCHA / hCaptcha)→ Step 7をmigration modeへ切り替える
-
挿入箇所の決定。 public exposure、abuse impact、既存submit path、backend verificationの可否、実装riskで候補を採点する。利用者へ選択を丸投げせず、[recommended]を全て選び、[skip by default]は除外する。理由は最終報告に残す。既存CAPTCHAがあれば具体的なmigration diffを先に作り、破壊的置換またはEnterprise設定だけ承認を求める(「他CAPTCHAからの移行」参照)。
-
Widget作成。 既存package managerでturnstile widget subcommandを利用できる場合はWrangler CLIを優先する。このrepositoryのpnpm例:
pnpm exec wrangler turnstile widget create "<name>" \
--domain <d1> --domain <d2> ... --mode managed --json
stdout JSONからsitekeyとsecretをparseする。Wranglerがproject-localにない、subcommandより古い(unknown command)、または失敗した場合は、Cloudflare APIへ直接curlするscripts/widget-create.sh --account-index <number> --name <name> --domains <list> --mode managedへfallbackする。scriptはAccount IDをprocess内で解決し、出力・保存しない。global installはしない。sitekeyは報告してよい。secretはshell変数WIDGET_SECRETへ保持し、Step 9で利用者自身のenv/secret storeへ入れる場合を除いてdiskへ書かない。
-
連携を配線する。 選定した各formへ正式なcontractを適用する。widgetを埋め込み、既存submit handler内へsuccess === trueでgateするsiteverify callを追加する。handlerの既存logicは変えず、secretはTURNSTILE_SECRETへ置く。明示的な設定依頼は、この限定的で可逆なcode editを許可する。先に検証し、その後unified diffを示す。mail deliveryやcustom backendなどの別機能を提案しない。
Canonical server-side siteverify (Node / fetch idiom; adapt to the detected backend):
const r = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
secret: process.env.TURNSTILE_SECRET,
response: token, // cf-turnstile-response from the request
remoteip: clientIp, // X-Forwarded-For / req.ip / etc.
}),
});
const result = await r.json();
if (!result.success) {
return reject(403, 'forbidden'); // platform-appropriate equivalent
}
// existing handler logic runs here, unchanged
secretは利用者のsecret storeへ入れる(Node/Rails/Pythonなら.env、Workersならwrangler secret put TURNSTILE_SECRET、Vercel/Fly/Render等はplatformのsecret manager)。inlineしない。
-
検証。 scripts/validate.shを実行し、各checkのPASSを報告する。1件でもFAILならerrorを示して停止する。[失敗時は利用者を待つ]
-
repository所有権を迂回せずSkillを保存・更新する。 最初に適用されるAGENTS.mdを読み、保存先を分類する。
- AIDD管理repository:
.claude/skillsと.agents/skillsは生成されるruntime配置。直接write/fetchしない。AGENTS.mdが指定する編集原本(このbundleではaidd-agent-kit/skills/turnstile-spin/)を更新し、repositoryのsyncとverify commandを使う。upstreamを取得する必要があればtemporary directoryへ取得し、diffをreviewして承認済み変更だけを編集原本へ適用する。persist-skill.shのtargetに管理runtimeを指定しない。
- 別のauthoring ruleがあるrepository: そのruleに従う。runtime discovery pathを編集原本と仮定しない。
- authoring ruleがない非管理repository: repository-local runtime pathを提案する。Claude Codeは
.claude/skills/turnstile-spin/SKILL.md、Codexは.agents/skills/turnstile-spin/SKILL.md。後続task用に保存するか尋ね、既定はyes。[利用者を待つ] この場合だけscripts/persist-skill.sh --path <selected-client-path>を実行する。
-
最終報告。 「作成・変更したもの」「選んだformと理由」「検証結果」「残る利用者操作」「次にすること」を日本語で構造化して示す。sitekeyは必要時に示してよい。Token、widget secret、raw Account IDはchatと最終報告へ出さない。Accountはnameだけを示す。
禁止事項
- 利用者自身のenv/secret store以外へTurnstile secretを書かない。
- 検証を省略しない。
- 関係ない挙動を上書きしない。変更を限定し、検証し、最終報告でdiffを示す。
- browserからsiteverifyを呼ばない。常にbrowser → 利用者のbackend → siteverify。
- 追加infrastructure(Workers、proxy、sidecar)をdeployしない。既存backendから直接siteverifyを呼ぶ。
sudoやglobal package installを使わない。既存package managerのproject-local commandまたはcurl fallbackを使う。
- 依頼されていないcustom Workers、custom domains、advanced WAF rulesを提案しない。
- raw Account IDをchat、質問、最終報告へ出さない。候補はname、番号、masked IDだけを表示する。
- TurnstileにAccount API Tokenを使わない。Turnstile専用user-owned tokenをCI/CD用account-owned tokenと共有しない。
- 管理runtime directoryへこのSkillをpersist/downloadしない。
AGENTS.mdに従って編集原本を更新し、syncとverifyを行う。
対象外のことを利用者へ尋ねない
Spinが行うのは、利用者の既存form handlerが動く前に正式なsiteverifyでTurnstile tokenを検証すること。次は対象外。
- Email / SMS / notification delivery。 既存submit handlerは
success === trueでgateする以外変更しない。Resend、Mailchannels、SMTP、mailtoを提案しない。
- 新しいbackendの追加。 現在backend handlerがないpure-static siteやmailto-only formなら、その事実を伝えて終了する。Spinにはsiteverifyを置くserver-side処理が必要。
- Database / payment / OAuth / form persistence。 対象外。
- Frontend framework migration、refactoring、styling。 必要箇所だけを編集する。
- reCAPTCHA v3 score thresholds。 Turnstileは
success: true/falseを返す。
- Pre-clearance-only設定。
clearance_level !== no_clearanceならsiteverifyはoptionalでSpin対象外。該当docsへ案内して終了する。
復旧フロー: 既存widget設定を尊重する
利用者がCloudflare dashboardへaccessできる場合、dashboard内のFix with Spin bannerが既存widget用のone-click recoveryである。以下はeditorから進める場合の同等フロー。
既存Turnstile widgetのsitekeyをrotateせずsiteverifyだけ接続したい場合:
- Step 8(widget作成)をskipする。既存sitekeyを利用者から受け取る。
- 選択済み候補番号を使い
scripts/fetch-secret.sh --account-index <number> --sitekey <key>でmetadataを取得し、statusで分岐する。scriptは完全なAccount IDをprocess内だけで解決する。
ok: responseのsecret、clearance_level、domainsを内部で読む。domainsにproduction hostnameがなければ、先へ進む前に不足を示す。
missing_read_scope: tokenへAccount.Turnstile:Readを追加してもらう。追加できない場合、secretをchatへ貼らせず、利用者自身に対象platformのsecret storeへTURNSTILE_SECRETとして登録してもらう。この経路ではclearance_levelとdomainsを取得できないため、dashboard上の表示だけを確認してもらう。
- responseまたはdashboard確認から
clearance_levelを判定する。
no_clearance: 標準の接続(Step 9)へ進む。
- それ以外: pre-clearanceにsiteverifyを重ねるか確認するか、対象範囲に従って終了する。
- Step 9から続行する。sitekeyは変えず、既存widgetを動作させたままにする。
- 新しいsecretを得る目的でwidgetを再作成しない。deploy済みの全sitekeyを壊す。
Frontend編集contract
既存formを接続するときのcontractは、置換せずgateすること。既存submit handlerの処理は維持し、その前にvalidationだけを追加する。
Frontend(widgetを埋め込み、既存endpointへsubmit):
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<form action="/signup" method="POST">
<!-- existing inputs unchanged -->
<div class="cf-turnstile" data-sitekey="<SITEKEY>" data-action="turnstile-spin-v2"></div>
<button type="submit">Sign up</button>
</form>
Backendは既存handler内でStep 9の正式なsiteverify fetchを使う。req.body['cf-turnstile-response']からtokenを読み、success === trueでgateし、残りのhandlerは変えない。既存handlerがstubなら、success gate付きstubのまま残す。stubの置換はSpinの仕事ではない。
他CAPTCHAからの移行
Step 6のscanで既存reCAPTCHA/hCaptchaも探す。見つかったらStep 7をmigration planへ切り替える。
検出signal:
- reCAPTCHA:
https://www.google.com/recaptcha/api.js, class="g-recaptcha", data-sitekey="6L...", backend POST to /recaptcha/api/siteverify
- hCaptcha:
https://js.hcaptcha.com/1/api.js, class="h-captcha", backend POST to https://hcaptcha.com/siteverify
置換内容:
- script tagを
https://challenges.cloudflare.com/turnstile/v0/api.js(async defer)へ置換する。
class="g-recaptcha" / class="h-captcha"のdivをclass="cf-turnstile"へ置換し、data-sitekeyを新Turnstile sitekeyへ更新し、data-action="turnstile-spin-v2"を追加する。
- Token fieldを
g-recaptcha-responseからcf-turnstile-responseへ変更する。
- Backend siteverify URLを
https://challenges.cloudflare.com/turnstile/v0/siteverifyへ向ける。RECAPTCHA_SECRET / HCAPTCHA_SECRET env varsを廃止し、TURNSTILE_SECRETを追加する。
利用者へ明示するedge cases:
- reCAPTCHA v3 score thresholds。 Turnstileにscoreはない。移行後は
success === falseでrejectすることを明示する。
- reCAPTCHA Enterprise。 自動移行せず、developers.cloudflare.com/turnstile/migration/recaptcha/へ案内する。
- Custom
action= values。 grecaptcha.executeへ渡していたcustom actionをwidgetのdata-actionとして維持する。custom actionがない場合だけturnstile-spin-v2を使う。
Edge cases
| 状況 | 対応 |
|---|
project-localなwrangler whoamiが失敗 | lockfileで既存package managerを再確認する。このrepositoryならpnpm exec wrangler whoami。global installは勧めず、subcommandがなければbundled curl scriptへfallbackする。 |
| Cloudflare Accountが複数 | 既存resource所有先を優先し、新規自社appは複数memberのチーム用Accountを既定にする。候補は番号・name・masked IDだけを表示し、選択番号をCLOUDFLARE_ACCOUNT_INDEXへ渡す。 |
| Cloudflare Pages project | Pages Functionまたはframework相当箇所へsiteverifyを置く。Pages Pluginも利用できる。 |
| Cloudflare Workers backend | Workerのrequest handler内でStep 9の正式なfetchを使う。challenges.cloudflare.comへのfetchはNodeと同様に動く。 |
EXPECTED_HOSTNAME mismatch | widget domainsはPATCHでなくPUTで更新する(PATCHは10405 Method not allowed): curl -X PUT .../widgets/$SITEKEY -d '{"name":"...","mode":"managed","domains":[...]}' |
| flow途中でToken失効 | 停止し、scripts/auth-probe.shを再実行して新しいcredentialsをownerに用意してもらう。 |
Validationがinvalid-input-secret | secretがbackendへ届いていない。対象env/secret managerのTURNSTILE_SECRETを再確認する。Workersならwrangler secret listで正しいscriptへのbinding名を確認する。 |
Validationがinvalid-input-response | dummy probe tokenでは想定結果で、secretが有効であることを示す。validate.shは成功として扱う。 |
Telemetry marker
このSkillが書く全cf-turnstile divにはdata-action="turnstile-spin-v2"を含める。これはAccount-levelのaggregate telemetryであり、per-userではない。Cloudflareはactivation測定に使う。利用者がattributeを削除しても連携は動作し、analytics segmentationだけが失われる。
V1 agent flowが作ったturnstile-spin-v1付きの古いwidgetがproductionに存在する場合、既存markerを維持し、retagしない。