Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

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

Đang mở
#2,222 2 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

@massmarketconsumer-arch đang làm issue này rồi.

Từ ngày 7/9/2026.

  • #2237 của @massmarketconsumer-arch — đang mở

Đánh giá

Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức phù hợp với người mới
55/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
nextjs, typescript

Hướng nghiên cứu

Bắt đầu với packages/utils/src/constants/plans.ts và so sánh ba điểm đầu vào checkout: các route guest-checkout, subscribe và desktop root. Đọc phần validation hiện có trong apps/web/app/api/v1/[...route]/route.ts và lần theo userIsPro, isProSubscription cũng như phần xử lý Stripe webhook. Công việc hoàn tất khi các plan được phép, số lượng bị giới hạn, xác thực khách và các kiểm tra entitlement được bao phủ nhất quán trên các path này.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

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.

Ngôn ngữ chính
Rust
Star
23k
Fork
2k
Merge trung bình
19 giờ 50 phút
Pull request đã merge (30 ngày)
72

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của CapSoftware/Cap

Tất cả issue của CapSoftware/Cap

Issue tương tự

Thêm issue về Rust

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.