Community程式設計與開發github.com

atokim4/stripe-webhook-security

檢查並修復 Stripe Webhook 端點的安全性。掃描未使用 `constructEvent` 簽章驗證的處理器,並針對已解析主體進行簽章檢查的狀況,逐步指導原始主體、冪等性、重放攻擊與密鑰處理。

stripe-webhook-security 是什麼?

此技能專為開發者設計,能在開發或審查 Stripe Webhook 處理器時,自動掃描常見的安全性缺失並提供修復指引。它支援多種框架,包括 Next.js(App Router 與 pages/api)、Express 以及 Supabase Edge Functions。技能的核心是運行一個無依賴的 Node.js 腳本(vibe-preflight 掃描器),它會檢查兩項安全規則:一是處理器是否完全遺漏 `constructEvent` 驗證(錯誤),二是是否在簽章驗證時使用了已解析的主體(例如 `req.json()`),這會導致驗證失敗(警告)。掃描完成後,技能會提供一份詳細的檢查清單,包含密鑰管理、重放攻擊防護、冪等性處理、事件順序、信任來源驗證以及回應策略,並要求開發者手動審查這些無法自動偵測的項目。最終輸出掃描報告與修復後的摘要,協助開發者確保 Webhook 端點的安全,尤其適合在上線付款功能前使用。

相容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/atokim4/vibe-preflight/tree/HEAD/skills/stripe-webhook-security

Installed? Explore more 程式設計與開發 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

Stripe Webhook 安全性

此技能會執行 vibe-preflight 掃描器中的 Stripe 部分(腳本位於 scripts/preflight.mjs 中,與本檔案同目錄,Node 20+,無依賴,無網路需求)。它會在 Next.js 路由處理器、pages/api、Express 和 Supabase Edge Functions 中尋找 Webhook 處理器,並檢查程式碼中的兩項安全規則。本頁面的其餘部分是您需要透過閱讀處理器來進行的審查,因為模式掃描無法證明一切。

1. 執行掃描

從專案根目錄執行:

node ${CLAUDE_SKILL_DIR}/scripts/preflight.mjs . --only stripe --fail-on warning --format json

Claude Code 會自動填入 ${CLAUDE_SKILL_DIR}。其他 AI Agent:請使用此技能的資料夾路徑,例如 .agents/skills/stripe-webhook-security 或 .claude/skills/stripe-webhook-security。

規則等級說明
stripe-webhook-unverifiederror處理器處理了 Stripe 事件,但從未呼叫 constructEvent / constructEventAsync。任何知道此 URL 的人都可以 POST 一個假的 checkout.session.completed 事件。
stripe-webhook-parsed-bodywarning有檢查簽章,但檢查的是已經被解析過的主體。Stripe 簽署的是它發送的原始位元組,因此真實事件會失敗並回傳 400,最後開發者可能會刪除這個檢查。

--only stripe 會透過 Stripe 事件名稱、讀取 stripe-signature 標頭或檔案中包含 Stripe 的 Webhook 路徑來尋找處理器。如果掃描報告 0 errors,但使用者知道他們有 Webhook,請自行找到該處理器,並仍然執行下方的審查。

2. 修復驗證

Route 處理器 (Next.js App Router、Remix、Hono 及其他 fetch 風格的處理器):

export async function POST(request: Request) {
  const body = await request.text(); // 原始主體,絕不使用 request.json()
  const signature = request.headers.get("stripe-signature");
  if (!signature) return new Response("缺少簽章", { status: 400 });
  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(body, signature, process.env.STRIPE_WEBHOOK_SECRET!);
  } catch {
    return new Response("無效的簽章", { status: 400 });
  }
  // 處理事件
  return Response.json({ received: true });
}

Express:使用 express.raw({ type: "application/json" }) 註冊 Webhook 路由,務必在 app.use(express.json()) 之前,然後將 req.body(此時為 Buffer)傳遞給 constructEvent。

Next.js pages/api:設定 export const config = { api: { bodyParser: false } };,並在驗證前將原始請求讀取為 Buffer。

Supabase Edge Functions 及其他 Deno / Edge 執行環境: await stripe.webhooks.constructEventAsync(body, signature, secret, undefined, Stripe.createSubtleCryptoProvider())。

3. 透過閱讀處理器來審查其餘項目

掃描器不會檢查這些項目。請查看程式碼,並針對下列每一項告訴使用者您的發現。

  • 密鑰。 STRIPE_WEBHOOK_SECRET(whsec_...)應來自伺服器環境變數,絕不能來自 NEXT_PUBLIC_ / VITE_ 變數或原始碼。測試模式與正式模式有不同的密鑰,每個端點和 stripe listen CLI 也各自有不同的密鑰。
  • 重放攻擊。 constructEvent 會拒絕時間戳超過容許範圍(預設 300 秒)的簽章。請不要傳入容差參數,或保持較小的值。切勿捕獲錯誤並繼續執行。
  • 冪等性。 Stripe 可能多次傳遞同一個事件。請確保寫入操作是天然可重複的(例如使用 Stripe ID 作為索引鍵的 upsert 操作、設定狀態而非增加點數),或將 event.id 儲存在具有唯一性約束的資料表中,並跳過已處理過的 ID。特別注意任何會增加點數、餘額或庫存的操作。
  • 順序。 事件的送達順序可能與發生順序不同。對於訂閱事件,請從 API 獲取當前物件(stripe.subscriptions.retrieve)或比較時間戳,而不是信任事件的順序。
  • 信任。 使用者與計劃資訊應來自驗證過的事件(client_reference_id、metadata、結帳時儲存的使用者 ID),絕不能來自查詢字串或未簽章的第二個請求。成功頁面的重新導向無法證明任何事:請透過 Webhook 授予存取權限。
  • 回應。 一旦事件被儲存或處理完畢,應立即回傳 2xx。耗時的操作應放入佇列處理。僅在簽章錯誤時回傳 400,這樣 Stripe 的重試機制才有意義。
  • 事件列表。 端點應只訂閱程式碼實際處理的事件類型,並忽略其他類型的事件而不產生錯誤。

4. 報告

列出掃描器的發現結果,然後針對每個審查項目輸出一行:正常、已修復,或向使用者提問(例如「每次送達都增加點數:重複事件會再次增加點數嗎?」)。修復後再次執行掃描,並顯示最後一行輸出。若要進行真實測試,建議使用者使用 Stripe CLI 執行 stripe listen --forward-to localhost:3000/api/webhooks 和 stripe trigger checkout.session.completed。

可以被確認的誤報,可以透過在該行或上一行加上 // vibe-preflight-ignore <rule-id> 來忽略,並在註解中說明原因。

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

相關技能