Community编程与开发github.com

atokim4/stripe-webhook-security

检查并修复 Stripe Webhook 端点。扫描未使用 `constructEvent` 验证签名、或对已解析请求体进行签名校验的处理器,并提供原始请求体、幂等性、重放攻击和密钥管理的完整修复方案。

stripe-webhook-security 是什么?

该技能用于分析和修复 Stripe Webhook 端点中的安全漏洞。它内置了 vibe-preflight 扫描器,能够自动检测两种常见问题:未调用 `constructEvent`/`constructEventAsync` 的未经验证的处理器(错误级别),以及针对已解析请求体进行签名校验的处理器(警告级别)。扫描支持 Next.js(路由处理器和 pages/api)、Express 和 Supabase Edge Functions 等主流平台。 除了自动扫描,该技能还提供了详细的人工审查指南,涵盖密钥管理(区分测试/生产环境)、重放攻击防护(默认 300 秒容忍度)、幂等性处理(防止重复发货/充值)、事件乱序处理以及信任边界(仅信任 webhook 中的数据而非查询参数)等关键安全点。报告阶段会列出扫描发现的问题,逐项评估修复状态,并建议使用 Stripe CLI 进行本地测试验证。

兼容平台✓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}。其他代理请使用此技能的文件夹路径,例如 .agents/skills/stripe-webhook-security 或 .claude/skills/stripe-webhook-security。

规则级别含义
stripe-webhook-unverified错误处理器处理了 Stripe 事件,但从未调用 constructEvent / constructEventAsync。任何知道该 URL 的人都可以 POST 伪造的 checkout.session.completed 事件。
stripe-webhook-parsed-body警告签名已校验,但校验的是已经解析过的请求体。Stripe 对原始字节进行签名,因此真实事件会以 400 错误失败,导致有人最终删除签名校验。

--only stripe 会通过 Stripe 事件名称、stripe-signature 标头的读取或与 Stripe 相关的 webhook 路径来查找处理器。如果报告为 0 errors 但用户确认存在 webhook,请自行找到处理器并按以下指南进行审查。

2. 修复签名验证

路由处理器(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:在 app.use(express.json()) 之前 使用 express.raw({ type: "application/json" }) 注册 webhook 路由,然后将 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.

相关技能