-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
Security Report: Missing RBAC on Payment Endpoints
Summary
All 3 payment API endpoints check team membership (throwIfNoTeamAccess) but skip RBAC permission enforcement (throwIfNotAllowed), unlike every other team-scoped endpoint.
Affected Endpoints
POST /api/teams/[slug]/payments/create-checkout-sessionPOST /api/teams/[slug]/payments/create-portal-linkGET /api/teams/[slug]/payments/products
Root Cause (1-of-N Inconsistency)
Every other team-scoped endpoint (SSO, DSYNC, API keys, webhooks, invitations, members, team CRUD) consistently calls both:
throwIfNoTeamAccess(req, res)β verifies team membershipthrowIfNotAllowed(teamMember, 'team_payments', ...)β enforces RBAC permissions
The payment routes only call the first, skipping RBAC. The RBAC configuration in lib/permissions.ts defines team_payments as an OWNER-only resource.
Example
Correct pattern (from pages/api/teams/[slug]/api-keys/index.ts):
const teamMember = await throwIfNoTeamAccess(req, res);
throwIfNotAllowed(teamMember, 'team_api_key', 'read');Payment endpoint (from pages/api/teams/[slug]/payments/create-checkout-session.ts):
const teamMember = await throwIfNoTeamAccess(req, res);
// Missing: throwIfNotAllowed(teamMember, 'team_payments', 'create');Impact
Any MEMBER or ADMIN of a team (not just OWNER) can:
- Create Stripe checkout sessions (change subscription plan)
- Generate Stripe billing portal links (view/modify payment methods, invoices)
- View subscription and product data
The UI correctly hides the billing tab via client-side canAccess('team_payments', ...) checks, but direct API calls bypass this.
Recommended Fix
Add throwIfNotAllowed(teamMember, 'team_payments', 'create'|'read') to each payment endpoint handler, matching the pattern used by all other team resources.
Environment
- Version: Latest (commit at time of audit)
- Identified via: Static code analysis
Note
The rest of the auth/authz implementation is excellent β consistent RBAC enforcement, proper team membership validation, Zod schema validation, and privilege escalation protection via validateMembershipOperation.