
turnstile-spin
热门在项目中端到端配置 Cloudflare Turnstile。扫描代码库,通过 Cloudflare API 创建 Widget,将其嵌入到相应的表单中,在客户现有的后端服务中接入标准的服务端 siteverify 验证逻辑,校验无误后持久化保存该 Skill。当用户提出添加 Turnstile、配置验证码(CAPTCHA)、保护表单防 Bot 攻击或修复 Turnstile 集成时加载此 Skill。对标 developers.cloudflare.com/turnstile/spin。
在项目中端到端配置 Cloudflare Turnstile。扫描代码库,通过 Cloudflare API 创建 Widget,将其嵌入到相应的表单中,在客户现有的后端服务中接入标准的服务端 siteverify 验证逻辑,校验无误后持久化保存该 Skill。当用户提出添加 Turnstile、配置验证码(CAPTCHA)、保护表单防 Bot 攻击或修复 Turnstile 集成时加载此 Skill。对标 developers.cloudflare.com/turnstile/spin。
Turnstile Spin Skill
把“配置 Turnstile”这一 Prompt 转化为真正可用的端到端集成方案:创建 Widget、在所有选定的插入点嵌入前端代码片段、在客户现有的后端接入标准的服务端 siteverify,并在最终汇报成功前完成真实的校验测试。
你就是 Agent。请通过调用 scripts/ 目录下的脚本并根据其 JSON 输出进行分支处理,来运行以下向导。脚本负责确定性的逻辑(API 调用、重试/错误处理);你的职责是流程编排、读取代码库、确认交互以及进行前端与后端的代码修改。
标准官方指南位于 developers.cloudflare.com/turnstile/spin。若文档页面与本文件内容有冲突,请以官方文档页面为准。
何时加载此 Skill
当用户的 Prompt 中包含以下任意内容时加载:
- “Turnstile”、“CAPTCHA”、“人机验证”、“Bot 防护”
- “siteverify”、“cf-turnstile-response”
- “保护这个表单”、“防 Bot 注册”、“防垃圾注册”
- 具体的注册、登录或联系表单,并结合了“Cloudflare”或“Bot”等关键词
除非同时提到了 Turnstile,否则不要在处理无关的 Cloudflare 任务(如 Workers、Pages、R2 等)时加载此 Skill。
对话流程
用户发送了 Prompt。你正处于多步骤对话中。尽最大可能自动检测,仅在必要时提问,且在执行每个不可逆步骤前必须先进行确认。每一个带编号的节点代表 Agent 发送的一条消息。标记有 [等待用户回应] 的项目需要用户给予答复。
-
简要确认。 用一句话回复:“我将为你端到端运行 Turnstile 配置流程。包含:检查鉴权、扫描代码库、创建 Widget、嵌入对应表单、接入服务端 siteverify、执行校验。是否继续?”[等待用户回应] 此时先不要展示具体计划,鉴权 + 扫描优先。
-
CLI 检查。 Spin 的辅助脚本会使用
curl请求api.cloudflare.com,并使用npx wrangler whoami进行账号枚举。第 8 步创建 Widget 时,若子命令可用(Wrangler 4.109+),优先使用wrangler turnstile widget create;否则降级使用内置的 curl 脚本。无需持久化安装 CLI。 -
鉴权 + 权限探针(第一个不可逆操作)。 运行
scripts/auth-probe.sh。根据返回的status进行分支处理:ok:继续执行第 4 步。脚本已自动选择账号(单账号 Token,或与$CLOUDFLARE_ACCOUNT_ID匹配的账号)。missing_token或missing_scope:提示用户在 https://dash.cloudflare.com/profile/api-tokens 页面创建 Token → 选择 Custom token → 权限设置为Account.Turnstile:Edit→ 在 Account Resources 中包含目标账号。不要引导用户去执行wrangler login,除非 wrangler 的 OAuth scope 中包含了Account.Turnstile:Edit(不同 wrangler 版本有所差异)。按从优到劣提供三种提交 Token 的方式:- 环境变量导出 + 重新启动(Token 绝不进入聊天记录):
export CLOUDFLARE_API_TOKEN=<token>,然后在该 Terminal 中重启 Agent。 - 保存到本地文件(Token 保存在仅当前用户有权访问的文件中,不进入聊天记录):
umask 077 && printf '%s' '<token>' > ~/.cf-turnstile-token,随后通过TOKEN=$(cat ~/.cf-turnstile-token)读取。 - 直接在聊天框粘贴(速度最快,但 Token 会存入对话日志;如果日志后续会被共享,用户应在事后轮换 Token)。
若用户选择方案 3(直接粘贴),你可以利用等待时间预先执行第 5、6、7 步(域名扫描、代码库扫描、插入计划)。方案 1 和 2 会重启 Session,因此在这两种情况下不要预先获取状态。鉴权建立完成后,重新运行auth-probe.sh,再继续执行第 8 步。
- 环境变量导出 + 重新启动(Token 绝不进入聊天记录):
multiple_accounts:Token 包含多个账号,且未设置$CLOUDFLARE_ACCOUNT_ID。展示带编号的accounts账号列表。[等待用户回应] 随后导出CLOUDFLARE_ACCOUNT_ID=<所选账号>并重新运行auth-probe.sh。account_mismatch:已设置$CLOUDFLARE_ACCOUNT_ID但不属于该 Token 覆盖的账号。展示accounts列表,请用户unset CLOUDFLARE_ACCOUNT_ID或将其设置为列表中的有效 ID。
-
账号选择。 若
auth-probe.sh在经历multiple_accounts往返后返回ok,则此步骤已完成。否则脚本已静默选择了唯一的账号,直接进入第 5 步。 -
域名配置。 始终包含
localhost和127.0.0.1。对于生产环境域名,扫描package.json中的homepage、wrangler.toml、README.md、AGENTS.md以及 git remote。确认:“我将为localhost、127.0.0.1以及<domain>注册 Widget。是否确认?”[等待用户回应] 如果未检索到生产环境域名,请向用户询问。 -
代码库扫描。 静默检测三项内容:
- 前端框架(Next.js、Astro、SvelteKit、Hugo、原生 HTML 等)→ 决定 Widget 嵌入代码片段。
- 后端 Handler 位置(Express 路由、Next.js API 路由、Rails Controller、Workers fetch handler、Pages Function 等)→ 决定 siteverify 代码片段。
- 已有的 CAPTCHA(reCAPTCHA / hCaptcha)→ 将第 7 步切换为迁移模式。
-
插入计划。 展示带有
[recommended](推荐)/[skip by default](默认跳过)标记的候选表单列表;请用户确认(回复序号、“全部”、“推荐”或指定列表)。[等待用户回应] 若检测到已有的 CAPTCHA,则展示迁移计划(参见“从其他 CAPTCHA 迁移”)。 -
Widget 创建。 当 wrangler CLI 的
turnstile widget子命令可用时优先使用:npx wrangler turnstile widget create "<name>" \ --domain <d1> --domain <d2> ... --mode managed --json从 stdout 的 JSON 中解析出
sitekey和secret。若 wrangler 不存在、版本低于 turnstile 子命令(提示unknown command)或执行失败,降级使用scripts/widget-create.sh --account-id <id> --name <name> --domains <list> --mode managed,该脚本将直接通过curl调用 Cloudflare API。告知用户 sitekey。将 secret 捕获到 Shell 变量WIDGET_SECRET中;切勿将其写入磁盘,除非在第 9 步将其写入用户自己的环境变量 / Secret 存储中。 -
接入集成代码。 明确代码变更约定:“我将在每个选定的表单中嵌入 Widget,并在你现有的提交 Handler 中插入标准的 siteverify 调用,以
success === true作为关卡校验。Handler 现有的核心逻辑保持不变。Secret 将以TURNSTILE_SECRET的形式保存在环境变量中。”询问“确认”还是“查看 Diff”。[等待用户回应] 若用户选择“查看”,打印 Unified Diff 并再次确认。切勿自行提出其他额外的行为变动(如邮件发送、自定义后端等)。标准的服务端 siteverify(Node / fetch 惯用法;根据检测到的后端进行适配):
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 remoteip: clientIp, // X-Forwarded-For / req.ip 等 }), }); const result = await r.json(); if (!result.success) { return reject(403, 'forbidden'); // 平台对应的拒绝逻辑 } // 现有 Handler 逻辑在此处继续运行,保持不变将 Secret 写入用户的密钥存储中(Node/Rails/Python 使用
.env,Workers 使用wrangler secret put TURNSTILE_SECRET,Vercel / Fly / Render 等使用对应平台的 Secret 管理器)。切勿硬编码在代码中。 -
校验测试。 运行
scripts/validate.sh。逐项汇报通过的检查项。若有任何检查失败,暴露错误并停止后续流程。[若有失败,等待用户回应] -
持久化保存 Skill。 询问:“是否将 Spin Skill 保存至
.claude/skills/turnstile-spin/SKILL.md,以便在后续任务中复用?”默认确认。[等待用户回应] 随后运行scripts/persist-skill.sh --path <agent-specific-path>。 -
最终汇报。 输出结构化的总结报告:创建了什么、校验了什么、下一步建议。
严禁执行事项
- 切勿将 Turnstile Secret 写入磁盘,除非是作为用户自身环境变量 / Secret 存储的一部分。
- 切勿跳过校验步骤。
- 未展示 Diff 前切勿直接覆盖文件。
- 切勿在浏览器前端直接调用 siteverify。永远遵循:浏览器 → 用户后端 → siteverify。
- 切勿部署任何额外的基础设施(Workers、代理、Sidecar 等)。客户现有的后端直接调用 siteverify 即可。
- 未经询问切勿使用
sudo或全局安装 npm 包。 - 未经要求切勿在向导范围外提议其他功能(如自定义 Workers、自定义域名、高级 WAF 规则等)。
严格范围边界:切勿向用户询问
Spin 的作用是在用户现有的表单 Handler 执行前,通过标准 siteverify 校验 Turnstile Token。其他所有事项均超出本 Skill 范围:
- 邮件 / 短信 / 消息通知发送。 保持现有提交 Handler 原封不动(仅增加
success === true校验门禁)。不要提议 Resend、Mailchannels、SMTP 或 mailto。 - 添加新的后端服务。 如果表单目前没有后端 Handler(纯静态站点、仅 mailto 的联系表单),直接说明情况并退出。Spin 需要一个服务端位置来放置 siteverify。
- 数据库 / 支付 / OAuth / 表单持久化。 超出范围。
- 前端框架迁移、重构或样式修改。 仅修改必要的代码。
- reCAPTCHA v3 分值阈值。 Turnstile 仅返回
success: true/false。 - 仅 Pre-clearance(预通关)模式配置。 若
clearance_level !== no_clearance,则 siteverify 是可选的,Spin 并不适用。请引导用户并退出。
恢复流程:尊重现有的 Widget 配置
当用户拥有 Cloudflare Dashboard 访问权限时,Dashboard 内的 Fix with Spin 横幅是一个一键恢复入口:它会展示针对已有 Widget 调优后的 Agent Prompt。下方本 Skill 的恢复流程则是用户在编辑器环境中操作时的对应等价流程。
如果用户告知你他们已经配置好了 Turnstile Widget,希望在不轮换 sitekey 的前提下接入 siteverify(例如“我有 sitekey 但 siteverify 一直没调通”、“基于我已有的 Widget <sitekey> 配置 Spin”):
- 跳过第 8 步(创建 Widget)。sitekey 已经存在,直接从用户处获取。
- 通过
scripts/fetch-secret.sh --account-id <id> --sitekey <key>获取 Widget 元数据。根据status分支处理:ok:从响应中读取secret、clearance_level和domains。确认domains包含用户的生产环境主机名;如果不包含,在继续前先指出缺失之处。missing_read_scope:提示用户向 Token 添加Account.Turnstile:Read权限,或降级为请用户手动粘贴 Secret。在手动粘贴路径下,你将无法获取clearance_level或domains,需向用户二次确认这两项。
- 检查响应(或用户回答)中的
clearance_level:no_clearance:标准接入流程(第 9 步)。- 其他任何值:询问用户是否想在 Pre-clearance 之上再加一层 siteverify,或者按照范围边界规则退出。
- 从第 9 步(接入集成代码)继续。Site key 保持不变,已有 Widget 在整个过程中持续正常工作。
- 切勿为了获取新的 Secret 而重新创建 Widget,这会导致所有已部署该 sitekey 的地方全部失效。
前端修改约定
接入已有表单时(第 9 步),代码修改约定为:加门禁,不替换。 用户现有的提交 Handler 保持原有的所有行为,Spin 仅在其之前增加一层校验步骤。
前端(嵌入 Widget 并提交至用户现有的 Endpoint):
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<form action="/signup" method="POST">
<!-- 现有 input 输入框保持不变 -->
<div class="cf-turnstile" data-sitekey="<SITEKEY>" data-action="turnstile-spin-v2"></div>
<button type="submit">Sign up<





