生成时间: 2026-07-21T00:52:19+08:00
Floway 部署到 Cloudflare Workers 后 Codex 403、Placement 522 与滥用举报事件报告¶
1. 问题描述¶
本次事件原本要将本地项目 /home/chesszyh/Applications/Floway 部署到 Cloudflare Workers,并通过自定义域名提供私有 LLM API 网关。部署后,Floway 的网页和控制面一度可用,但 codex provider 无法拉取 ChatGPT/Codex 模型列表:同一份 OAuth 凭据在本机直连请求成功,从 Worker 发出时却收到 OpenAI 返回的 403 HTML challenge 页面。
为了验证是否是 Cloudflare Worker 所在地区不受 OpenAI 支持,先后尝试美国、德国和日本的 targeted placement。三次尝试都没有进入 Floway 应用,而是在 Cloudflare 边缘直接返回 522。移除 placement、重新部署和回滚代码版本均未恢复原 Worker 服务,只能新建 floway-recovery Worker 恢复入口。
新 Worker 恢复后,再次执行受管理员认证保护的 Codex 模型列表探测,仍得到相同的 OpenAI 403。约十几分钟后,Cloudflare 发来 Network abuse 举报通知,指向该 Worker 的规范 workers.dev 地址。最终决定停止本项目的 Cloudflare 运行面:移除自定义域名绑定,关闭两个 Floway Worker 的 workers.dev 入口和 cron,但保留 Worker 代码及 D1、R2、KV 数据。
2. 环境和范围¶
- 项目:Floway,LLM API gateway。
- 本地仓库:
/home/chesszyh/Applications/Floway。 - 技术栈:Hono、TypeScript、pnpm、Vue/Vite、Cloudflare Workers、D1、R2、KV、Durable Objects、Images。
- Cloudflare Worker:
floway、floway-recovery。 - 生产入口:报告中统一记作
<FLOWAY_DOMAIN>。 - Cloudflare 账户、数据库、KV、R2 标识:已脱敏。
- Wrangler:本次执行时为
4.81.0。 - 事件日期:2026-07-20 至 2026-07-21,时区
Asia/Shanghai。 - 本次只操作 Floway 对应的 Worker、域名和定时任务;未触碰账户内其他项目。
本报告不会记录以下敏感内容:Cloudflare OAuth token、ADMIN_KEY、Codex OAuth token、完整 auth.json、会话 token、API key、Cloudflare account ID、D1/KV/R2 资源 ID,以及滥用报告的直接面板链接和报告编号。
3. 现象与复现¶
3.1 Worker 内 Codex /models 返回 403¶
Floway 控制面的模型刷新操作返回 HTTP 500,内部错误保留了真实上游状态:
Codex /models fetch failed: 403 <html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style global>body{font-family:Arial,Helvetica,sans-serif}...
对照请求表明:
- 使用相同 OAuth 凭据从本机请求,上游模型列表返回 HTTP 200。
- 从默认 Cloudflare Worker 请求,上游返回 HTTP 403 和 challenge HTML。
- 因此凭据格式和 OAuth token 本身不是首要差异;运行时出口路径才是关键变量。
3.2 Targeted placement 后整个入口返回 522¶
依次为 Worker 设置以下 placement:
{ "region": "aws:us-east-1" }
{ "region": "aws:eu-central-1" }
{ "region": "aws:ap-northeast-1" }
三个地区均出现一致结果:自定义域名持续返回 Cloudflare 522,连静态首页都无法进入,说明失败发生在 Floway 请求处理之前。删除 placement 字段、重新发布以及回滚到原先正常的 Worker 版本后仍持续 522。
3.3 滥用通知¶
恢复 Worker 上线并再次执行 Codex 模型列表探测后,Cloudflare 发出自动邮件:
Cloudflare received an Network abuse regarding:
floway-recovery[.]<account-subdomain>[.]workers[.]dev
Reported URLs:
hxxps://floway-recovery[.]<account-subdomain>[.]workers[.]dev
通知中的 Logs or other evidence of abuse 为空,因此仅凭邮件无法确定举报方,也无法证明第三方实际访问过该 URL。
3.4 最终停用后的外部信号¶
floway workers.dev: enabled=false, previews_enabled=false
floway cron schedules: []
floway-recovery workers.dev: enabled=false, previews_enabled=false
floway-recovery schedules: []
Floway custom domains: []
停用后 <FLOWAY_DOMAIN> 已无法建立正常 TLS/HTTP 连接,floway-recovery.<account-subdomain>.workers.dev 返回 404。
4. 调查时间线¶
- 已执行
pnpm i的 Floway 工作区被配置为 Cloudflare Workers 生产部署,绑定 D1、R2、KV、Images 和 Durable Objects,并使用自定义域名。 - 完成远端 D1 migration 检查;61 个 migration 均已应用,没有待执行 migration。
- 初始 Worker 网页返回 200,但
codexprovider 模型列表请求返回 OpenAI 403 challenge HTML;相同凭据本机请求返回 200。 - 记录初始可回滚版本
93c0f27b-6eca-4d41-a9d3-edffbdd34550。 - 部署美国东部 placement,版本
2131cd3b-75fd-49ff-a756-83fd586e2ea1;入口持续 522。 - 部署德国 placement,版本
ff1524c0-9c73-4911-894b-5231b829f75c;入口持续 522。 - 部署日本 placement,版本
694ae277-3242-4da5-bffb-9b45df5cac8e;入口持续 522。 - 删除 placement 配置并重新部署,版本
55298329-646d-4938-acc4-333ff5d65117;522 未消失。 - 回滚到
93c0f27b-6eca-4d41-a9d3-edffbdd34550;522 仍未消失,证明问题不是当前代码包本身。 - 检查 Wrangler 实现,发现
placement.mode = "off"会在上传前转换成undefined,即“不发送 placement 字段”,无法强制清除远端状态。 - 通过 Worker settings API 尝试
{ "placement": { "mode": "off" } },API 返回invalid placement mode: off;改发{ "placement": null }后 API 接受,版本元数据中的 placement 也为空,但原 Worker 仍为 522。 - 删除并重建原自定义域名绑定,另建临时探针域名,两个域名均持续 522,进一步把故障边界收敛到原
flowayWorker 服务。 - 新建干净服务
floway-recovery并绑定相同存储及域名,发布版本f61e9f36-18b9-4988-8d0a-2f0bb33a7466;复制ADMIN_KEY后产生 secret change 版本94b2fd8c-0727-49a0-b1f3-17a7ff2fc2c9。 - 新服务连续 20 次首页检查均返回 200。旧
floway的workers.dev与 cron 被关闭,避免双实例定时任务。 - 在新服务中执行真实反馈回路:管理员登录返回 200,Codex
list-models返回 500,内部上游错误仍是 OpenAI 403 challenge;随后注销临时会话。 - 约十几分钟后收到 Cloudflare
Network abuse自动通知。 - 核查 D1:生产库没有有效 API key,举报窗口后没有
usage_requests数据面用量记录,只有一个有效用户;没有证据表明第三方通过 Floway 发起模型请求。 - 用户决定停止本项目。最终移除
<FLOWAY_DOMAIN>的 Worker 绑定,清空floway和floway-recovery的 cron,关闭两个 Worker 的workers.dev和 preview 入口,保留存储和代码。
5. 根因¶
本事件包含三个相互关联但不能混为一谈的原因层次。
5.1 Codex 403:Worker 出口被 OpenAI 风控¶
最强证据是“同一凭据、本机 200、Worker 403”。403 响应不是 JSON API 错误,而是 HTML challenge 页面,说明 OpenAI 前置反滥用/机器人系统拦截了 Cloudflare Worker 的出口请求。
这不等价于“新加坡不在 OpenAI 支持地区”。默认请求虽然观察到 SIN 边缘标识,但新加坡本身属于受支持地区;而 targeted placement 失败在 Floway 执行前,未能形成一次有效的跨地区上游对照实验。
Cloudflare 的 Workers 安全文档说明,Worker 出站 HTTP 请求由平台代理转发,并带有能够追溯到具体 Worker 的标识。这解释了为什么上游可以按 Worker 来源实施阻断或提交投诉:https://developers.cloudflare.com/workers/reference/security-model/。
5.2 Placement 522:远端服务状态未被普通部署/回滚清理¶
美国、德国、日本三次 placement 都让整个入口在应用执行前 522。代码回滚没有恢复,API 显示 placement 已清空后也没有恢复;新建相同代码和绑定的 floway-recovery 却立即恢复 200。因此最符合证据的解释是:targeted placement 使原 floway 服务进入了异常的 Cloudflare 调度/路由状态,该状态不属于普通 Worker 代码版本,无法通过 wrangler rollback 清除。
这部分是基于差分实验得出的工程判断,并非 Cloudflare 官方对该服务给出的根因确认。
5.3 Network abuse:网关行为可能被归类为代理服务¶
无法从空白 evidence 确认具体举报者,因此“由 OpenAI 自动投诉”只能作为高概率推断,不能写成已确认事实。但时间线高度相关:新 Worker 执行受认证保护的 OpenAI/Codex 出站探测后,十几分钟内收到 Network abuse 通知。
Floway 不是公开 VPN,也不是无认证开放代理;生产 D1 当时没有有效 API key,也没有数据面使用记录。然而它在功能上是把客户端 LLM API 请求转发到第三方上游的网关,可能被 Cloudflare 的规则或投诉方归类为“similar proxy service”。Cloudflare Self-Serve Subscription Agreement 中明确禁止使用服务提供 VPN 或类似代理服务:https://www.cloudflare.com/terms/。
Cloudflare 对 Workers 这类托管服务可直接限制内容,并允许账户所有者申请复审:https://www.cloudflare.com/trust-hub/abuse-approach/。
6. 已执行的变更¶
6.1 Cloudflare 远端状态¶
- 创建并多次发布
flowayplacement 测试版本。 - 创建恢复服务
floway-recovery。 - 将
ADMIN_KEY安全复制到恢复服务,过程中未打印 secret。 - 自定义域名一度从
floway转移到floway-recovery。 - 清空旧
flowaycron,并关闭其workers.dev。 - 最终清空
floway-recoverycron,并关闭其workers.dev和 preview。 - 最终删除 Floway 的自定义域名绑定。
- 删除临时探针域名。
- 未删除
floway、floway-recoveryWorker 代码。 - 未删除 D1、R2、KV、Images 或 Durable Object 数据。
- 未改动账户内其他 Worker 和其他项目资源。
6.2 本地文件¶
/home/chesszyh/Applications/Floway/wrangler.jsonc是 gitignored 的个人配置,当前 Worker 名为floway-recovery,仍声明自定义域名和 cron。若再次执行pnpm run deploy,会重新启用当前已停用的入口和定时任务。/home/chesszyh/Applications/Floway/wrangler.example.jsonc在本次部署准备中增加了自定义域名 route 占位配置;该修改仍未提交。.serena/是原有未跟踪内容,本次未修改。- 本次没有创建 Git commit 或 Pull Request。
7. 验证¶
7.1 恢复服务曾经可用¶
新建 floway-recovery、完成 secret 配置后执行连续检查:
20/20 requests: HTTP 200
同时验证真实控制路径:
login_http=200
models_http=500
error.message=Codex /models fetch failed: 403 <html>...
这证明恢复服务的 Floway 控制面正常,而 Codex 失败仍来自上游。
7.2 未发现第三方数据面滥用¶
D1 只读查询结果:
active_keys=0
usage_requests after incident window=[]
active_users=1
该结果不能证明 Cloudflare 没有观察到任何网络异常,但能证明没有 API key 可用于正常的 Floway 数据面调用,也没有 Floway 记录的模型请求用量。
7.3 最终停用确认¶
Cloudflare API 最终复查:
{
"floway": {
"workers_dev": false,
"previews_enabled": false,
"schedules": []
},
"floway-recovery": {
"workers_dev": false,
"previews_enabled": false,
"schedules": []
},
"floway_domains": []
}
外部访问复查:
<FLOWAY_DOMAIN>: unavailable
floway-recovery.<account-subdomain>.workers.dev: HTTP 404
8. 调试过程中遇到的问题¶
8.1 把“换国家”误当成可直接验证的单变量¶
Targeted placement 不只是改变出站地区;在本环境中它使整个自定义域名 522。由于请求未进入 Floway,三次测试并没有真正验证美国、德国或日本出口能否访问 OpenAI。
8.2 Wrangler 的 mode: off 语义具有误导性¶
Wrangler 4.81.0 的 parseConfigPlacement() 对 mode === "off" 返回 undefined。因此配置中写 off 或直接删除 placement,上传时都表现为“不发送字段”,无法可靠地把已有远端 placement 改成关闭状态。
Cloudflare settings API 又不接受 placement.mode=off:
invalid placement mode: off
发送 placement: null 能清空 API 元数据,但仍没有修复原 Worker 服务的 522。
8.3 代码版本回滚不是服务配置回滚¶
wrangler rollback <VERSION_ID> 成功把活动代码版本切回已知正常版本,但未清除 placement 相关的服务异常。以后看到“部署版本已回滚”不能直接推导“域名路由和调度状态也已回滚”。
8.4 删除并立即重建域名绑定没有修复¶
先尝试删除后立即重建自定义域名,随后又让域名实际消失十余秒再重建,两次均保持 522。临时新 hostname 指向同一 floway 服务同样 522,排除了单一 hostname 缓存作为主要原因。
8.5 曾把 1042 过早解释成滥用封禁¶
访问关闭后的 workers.dev 地址曾看到 error code: 1042。Cloudflare 官方错误表说明,1042 的含义是 Worker 尝试 fetch 同一 zone 的另一个 Worker,而不是“因滥用被封”。因此该信号不能单独证明处罚状态:https://developers.cloudflare.com/workers/observability/errors/。
8.6 本机代理会让网络观测变得混乱¶
本机 Clash/Fake-IP 环境会让 curl 显示 127.0.0.1 或 fdfe:dcba:... 形式的远端地址。诊断中使用 curl --noproxy "*" 尽量绕过显式代理,但 Fake-IP/TUN 仍可能影响地址显示。HTTP 状态、Cloudflare API 元数据和 D1 记录比 remote_ip 更可靠。
9. 复用说明与经验¶
- 对 OpenAI-compatible relay,必须分别验证模型发现和一次真实生成;“网页能开”不能证明上游可用。
- 收到 HTML 403 时先对比同凭据的本机直连请求。若本机 200、Worker 403,应优先怀疑平台出口风控,而不是重复刷新 OAuth token。
- Cloudflare placement 适合把 Worker 靠近后端,但不是通用的出口国家选择器。变更前必须准备独立恢复服务,不能只依赖代码版本回滚。
- 部署前记录活动版本、D1 migration 状态和可复制的回滚命令;涉及 migration 时还要保存 D1 Time Travel bookmark。
wrangler rollback只覆盖 Worker 版本;域名、cron、workers.dev、placement 和其他服务级设置应单独核验。- LLM API gateway 即使要求认证,也可能被云平台归类为代理服务。生产前应先检查平台条款和上游对云函数出口的政策。
- Cloudflare Worker 的出站请求可追溯到具体 Worker。不要通过反复换 Worker 名称来规避投诉或 mitigation,这可能放大账户风险。
- 若必须运行 Floway,优先考虑 Node 目标部署到 VPS,并使用 DNS-only 记录,避免把第三方 LLM 流量再次经过 Cloudflare Worker/CDN 代理层。
- 当前只是远端停用,不是删除。D1、R2、KV 和 Worker 代码仍保留,适合后续审计或迁移。
- 当前本地
wrangler.jsonc仍包含 route 和 cron,任何未来部署都可能重新启用服务;重新执行前应先明确审查意图和 Cloudflare 复审结果。
10. 附录:可复用命令¶
以下命令均使用占位符,不应把 token 或 secret 写入 shell history。
10.1 检查部署和 migration¶
pnpm wrangler deployments list
pnpm wrangler d1 migrations list <D1_DATABASE_NAME> --remote
10.2 检查 Worker 的 cron¶
curl -sS \
-H "Authorization: Bearer $CLOUDFLARE_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/<CF_ACCOUNT_ID>/workers/scripts/<WORKER_NAME>/schedules" \
| jq '{success, schedules: .result.schedules, errors}'
10.3 清空指定 Worker 的 cron¶
curl -sS -X PUT \
-H "Authorization: Bearer $CLOUDFLARE_TOKEN" \
-H "Content-Type: application/json" \
--data '[]' \
"https://api.cloudflare.com/client/v4/accounts/<CF_ACCOUNT_ID>/workers/scripts/<WORKER_NAME>/schedules"
10.4 检查和关闭 workers.dev¶
curl -sS \
-H "Authorization: Bearer $CLOUDFLARE_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/<CF_ACCOUNT_ID>/workers/scripts/<WORKER_NAME>/subdomain"
curl -sS -X POST \
-H "Authorization: Bearer $CLOUDFLARE_TOKEN" \
-H "Content-Type: application/json" \
--data '{"enabled":false,"previews_enabled":false}' \
"https://api.cloudflare.com/client/v4/accounts/<CF_ACCOUNT_ID>/workers/scripts/<WORKER_NAME>/subdomain"
10.5 只列出属于 Floway 的自定义域名¶
curl -sS \
-H "Authorization: Bearer $CLOUDFLARE_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/<CF_ACCOUNT_ID>/workers/domains" \
| jq '[.result[] | select(.service == "floway" or .service == "floway-recovery") | {id, hostname, service, enabled}]'
10.6 删除一个已经确认的自定义域名绑定¶
删除前必须先用上一条命令核对精确 ID,不能使用未解析变量、通配符或账户级批量删除。
curl -sS -X DELETE \
-H "Authorization: Bearer $CLOUDFLARE_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/<CF_ACCOUNT_ID>/workers/domains/<DOMAIN_BINDING_ID>"
10.7 检查是否存在 Floway 数据面使用¶
pnpm wrangler d1 execute <D1_DATABASE_NAME> --remote --json --command \
"SELECT hour, SUM(requests) AS requests, COUNT(DISTINCT key_id) AS keys
FROM usage_requests
WHERE hour >= '<UTC_START_TIME>'
GROUP BY hour
ORDER BY hour;"
10.8 绕过显式代理检查 HTTP 状态¶
curl --noproxy "*" -sS -o /dev/null \
-w '%{http_code} %{time_total}\n' \
--max-time 20 \
https://<FLOWAY_DOMAIN>/
10.9 重新启用前的最低检查清单¶
1. 确认 Cloudflare abuse report 的复审结果和明确政策边界。
2. 审查 wrangler.jsonc 中的 name、routes、triggers 和 placement。
3. 确认不会恢复 workers.dev 或重复 cron。
4. 检查 D1 migration diff 并准备回滚版本/Time Travel bookmark。
5. 使用不包含真实凭据的最小探针先验证入口。
6. 不要再次用 Cloudflare Worker 反复探测已返回 challenge 的 OpenAI 端点。