Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

Arbitrary Stripe priceId accepted at three checkout endpoints → Pro entitlement bypass

オープン
#2,222 コメント 2 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
55/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
nextjs, typescript

調査の方向性

packages/utils/src/constants/plans.ts から始め、3つの checkout エントリーポイント、guest-checkout、subscribe、desktop root の各ルートを比較します。apps/web/app/api/v1/[...route]/route.ts にある既存のバリデーションを読み、userIsPro、isProSubscription、Stripe webhook の処理を追跡します。これらのパス全体で、許可されたプラン、上限付きの数量、ゲスト認証、権限チェックが一貫してカバーされれば作業は完了です。

索引モデルが issue の本文から書いたものです。

説明

bug
Summary

priceId and quantity are taken directly from client-supplied JSON and passed into
stripe.checkout.sessions.create with no allowlist, at three separate endpoints, despite
STRIPE_PLAN_IDS (packages/utils/src/constants/plans.ts) existing for exactly this purpose:

  • apps/web/app/api/settings/billing/guest-checkout/route.ts:9,26 no auth at all.
  • apps/web/app/api/settings/billing/subscribe/route.ts:14,68 authenticated, but no allowlist.
  • apps/web/app/api/desktop/[...route]/root.ts:702-791 (POST /api/desktop/subscribe, used by
    desktop + mobile) authenticated via withAuth, but priceId is only validated as
    z.string(), no enum against STRIPE_PLAN_IDS.

For contrast, apps/web/app/api/v1/[...route]/route.ts:4402-4409 does this correctly: it
derives the price itself from STRIPE_PLAN_IDS[environment][payload.interval] and never
trusts a client-supplied price id.

Why this grants full Pro

Entitlement is checked by status, not by price:

  • userIsPro (packages/utils/src/lib/stripe/subscriptions.ts) only checks
    stripeSubscriptionStatus / thirdPartyStripeSubscriptionId.
  • isProSubscription is a deny-list it excludes only SSO and signed-BAA subscriptions.
  • In the Stripe webhook (apps/web/app/api/webhooks/stripe/route.ts:110-124,601-610),
    checkout.session.completed special-cases only SSO and signed-BAA subscriptions via
    isSsoSubscription/isSignedBaaSubscription. Every other subscription i.e. any other live
    recurring price on the account — falls into the generic path that sets
    stripeSubscriptionStatus: subscription.status and inviteQuota from the line-item quantity,
    with no price check at all.

So completing checkout with any other live recurring price in Cap's Stripe account (a
retired/legacy tier, an internal test price, anything not SSO/BAA) grants full Pro status.
allow_promotion_codes: true widens this further, and on subscribe/route.ts and
guest-checkout/route.ts an unbounded quantity flows straight into users.inviteQuota.

Existing related work (none of it closes this)
  • PR #2141 (open, "Checkout conversion...guest checkout lockdown") fixes only
    guest-checkout/route.ts adds an allowedPriceIds() check against STRIPE_PLAN_IDS and
    clamps quantity to 1-100. subscribe/route.ts and the desktop /subscribe endpoint are
    untouched by that PR, and isProSubscription remains a deny-list.
  • PR #1929 (open) adds Vercel-Firewall rate limiting to guest-checkout only doesn't prevent
    a single request from buying Pro at an arbitrary price, and fails open on self-hosted deploys
    without a configured Firewall rule.
  • No existing issue covers this.
Fix
  1. Allowlist priceId against STRIPE_PLAN_IDS[env] at all three endpoints (not just
    guest-checkout), mirroring the pattern already used in v1/route.ts.
  2. Clamp quantity at subscribe/route.ts and the desktop endpoint the same way #2141 does
    for guest-checkout.
  3. Convert isProSubscription from a deny-list to an allow-list (only prices in
    STRIPE_PLAN_IDS grant Pro), so entitlement isn't dependent on catching every non-Pro price
    individually as it's created in Stripe.
  4. Require auth on guest-checkout or otherwise bound its blast radius (currently zero auth).
Caveat

Actual historical exposure depends on which legacy/test prices are live (non-archived) in Cap's
Stripe account right now someone with Stripe dashboard access should check before sizing
impact. The code-level flaw is independent of that and reproducible today with any second live
recurring price.

主要言語
Rust
スター
22.5k
フォーク
1.9k
平均マージ
6時間 33分
マージ済み PR(30日)
77

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

CapSoftware/Cap のほかの issue

CapSoftware/Cap の issue をすべて見る

似ている issue

Rust の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。