fix(security): 伺服器端強制 Turnstile 驗證 + 新增 KV 批量清理工具 - #3
Merged
Merged
Conversation
公開建立短網址 API (/api/public-create) 先前只在前端做 Turnstile 驗證,攻擊者可繞過前端直接呼叫 API 灌入大量短網址。 安全性修復: - public-create 在伺服器端強制驗證 Turnstile(沿用既有的 verifyTurnstile),驗證在任何 KV 寫入之前執行,避免攻擊流量 污染資料庫或灌爆速率限制計數;缺少 token / 未設定 secret 一律 拒絕(fail-closed),DEV_MODE 可略過以利本機測試。 - 驗證失敗時發送 Discord 存取被拒通知。 KV 清理工具: - 新增 /admin/cleanup 頁面,逐頁載入 KV 內全部短網址,可依建立 時間、建立者 IP、目標網址/短碼、0 點擊篩選,全選後批量刪除。 - /admin DELETE 新增 silent 批量模式:直接刪除、不逐筆發 Discord 通知(改為一則彙總),並一併清除 disabled: 鍵,單次上限 100 筆。 - 管理後台新增「批量清理」入口。
There was a problem hiding this comment.
Pull request overview
This PR addresses abuse of the public short-link creation endpoint by enforcing server-side Cloudflare Turnstile verification, and adds an admin-facing bulk cleanup UI to safely review/filter/delete large volumes of KV-stored links.
Changes:
- Enforce Turnstile validation in
POST /api/public-createbefore any KV writes (incl. rate-limit counters), with fail-closed behavior and Discord notification on denial. - Add
/admin/cleanuppage that paginates through KVlink:keys, supports filtering and “select all filtered”, and performs batched deletions viaDELETE /adminusing a newsilentbulk mode. - Extend admin delete behavior to support
silentmode and add an “🧹 批量清理” entry point in the admin UI.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| functions/api/public-create.js | Adds server-side Turnstile verification before rate limiting / KV writes and notifies Discord on denial. |
| functions/admin/index.js | Adds silent bulk delete mode and UI link to the new cleanup page; introduces per-request deletion cap. |
| functions/admin/cleanup.js | New admin cleanup UI + JSON data endpoint to enumerate KV links and batch delete selected items. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+56
to
+66
| const cookie = request.headers.get('Cookie') || ''; | ||
| const sessionMatch = cookie.match(/admin_session=([^;]+)/); | ||
| if (!sessionMatch) { | ||
| return { ok: false, response: Response.redirect(new URL('/admin', request.url).href, 302) }; | ||
| } | ||
|
|
||
| const sessionKey = sessionMatch[1]; | ||
| const sessionData = await env.LINKS_KV.get(`session:${sessionKey}`, { type: 'json' }); | ||
| if (!sessionData || Date.now() >= sessionData.expiresAt) { | ||
| return { ok: false, response: Response.redirect(new URL('/admin', request.url).href, 302) }; | ||
| } |
Comment on lines
+193
to
+196
| // 單一請求最多刪除 100 筆,避免超出 Workers 子請求上限 | ||
| if (ids.length > 100) { | ||
| return createErrorResponse('Too many IDs. Maximum 100 per request.', 'TOO_MANY_IDS', 400); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景
有人繞過前端、直接呼叫公開建立 API 大量灌入短網址,導致 KV 內累積大量無用資料。
根因
functions/api/public-create.js只在前端跑 Cloudflare Turnstile,伺服器端完全沒驗證 — 前端把turnstileToken放進 body,但後端收到後直接忽略。攻擊者只要直接POST /api/public-create(校內 IP 即可過白名單)就能無限建立短網址。functions/lib/validation.js早已寫好verifyTurnstile(),且SPEC.md第 168 行也明文要求驗證 Turnstile,只是從未被實作。變更內容
🔒 安全性修復
public-create.js在伺服器端強制呼叫verifyTurnstile(turnstileToken, env.TURNSTILE_SECRET, ip)。DEV_MODE=true可略過(本機測試)。🧹 KV 批量清理工具
/admin/cleanup頁面:逐頁載入 KV 內全部短網址,可依建立時間區間 / 建立者 IP / 目標網址或短碼 / 0 點擊篩選,「全選符合篩選」後批量刪除(每批 100 筆並顯示進度)。adminDELETE 新增silent批量模式:直接刪除、不逐筆發 Discord 通知(改為一則彙總),並一併清除disabled:鍵,單次上限 100 筆。對既有資料的影響
無。此 PR 不會自動讀寫、遷移或修改任何現有 KV 資料:
[id].js完全未改,所有現有短網址照常運作。list+getWithMetadata),刪除只在管理員主動勾選後才發生。Production 必須設定環境變數
TURNSTILE_SECRET(後端驗證用,對應前端的TURNSTILE_SITE_KEY)。由於改為 fail-closed,若未設定,新建短網址功能會被擋下(但不影響既有短網址的轉址)。https://claude.ai/code/session_01Y6XCMnr6W2tWg5ynwq1Yst
Generated by Claude Code