Arbitrary Stripe priceId accepted at three checkout endpoints → Pro entitlement bypass
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
- Lĩnh vực
- authentication, backend-api-design, payments, security
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ả
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,26no auth at all.apps/web/app/api/settings/billing/subscribe/route.ts:14,68authenticated, but no allowlist.apps/web/app/api/desktop/[...route]/root.ts:702-791(POST /api/desktop/subscribe, used by
desktop + mobile) authenticated viawithAuth, butpriceIdis only validated as
z.string(), no enum againstSTRIPE_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.isProSubscriptionis 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.completedspecial-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.statusandinviteQuotafrom 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.tsadds anallowedPriceIds()check againstSTRIPE_PLAN_IDSand
clamps quantity to 1-100.subscribe/route.tsand the desktop/subscribeendpoint are
untouched by that PR, andisProSubscriptionremains a deny-list. - PR #1929 (open) adds Vercel-Firewall rate limiting to
guest-checkoutonly 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
- Allowlist
priceIdagainstSTRIPE_PLAN_IDS[env]at all three endpoints (not just
guest-checkout), mirroring the pattern already used inv1/route.ts. - Clamp
quantityatsubscribe/route.tsand the desktop endpoint the same way #2141 does
for guest-checkout. - Convert
isProSubscriptionfrom a deny-list to an allow-list (only prices in
STRIPE_PLAN_IDSgrant Pro), so entitlement isn't dependent on catching every non-Pro price
individually as it's created in Stripe. - Require auth on
guest-checkoutor 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
- Có Dockerfile hoặc tệp Docker Compose
- Không có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- 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.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của CapSoftware/Cap
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
CapSoftware/Cap#2384 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Dashboard pagination causes full page reloads on self-hosted CapCó thể đã có người làm @Dewin đã nhận 18 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 88/100
CapSoftware/Cap#2305 · 2 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
CapSoftware/Cap#1714 · 3 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
[Bug][Linux/Wayland] Window capture distorts aspect ratio, drops cursor/clicks, and cannot import .cap bundles via portalCó thể đã có người làm @EtherealBless đã nhận hôm nay. Đang mởbug
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 25/100
CapSoftware/Cap#2419 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 25/100
CapSoftware/Cap#2409 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của CapSoftware/Cap
Issue tương tự
-
enhancement
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
zcashlabs/thus-spoke-zakura#153 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
claude_code: step fails on session-scoped (`@inline`) plugins with `Invalid scope "session"`Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 79/100
topgrade-rs/topgrade#2395 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
app bug windows-os
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 67/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 88/100
matrix-org/matrix-rust-sdk#7217 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Improve sublime text syntaxĐang mởeditor good first issue
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 65/100
funnyboy-roks/inq#54 ·