-
-
Notifications
You must be signed in to change notification settings - Fork 636
Description
Summary
Two authorization issues in the Rybbit server:
1. Subscription Data Exposure (HIGH)
File: server/src/api/stripe/getSubscription.ts
Route: GET /stripe/subscription (registered with authOnly middleware)
The endpoint accepts an organizationId query parameter but only checks that the user is authenticated (via authOnly), not that they are a member of the specified organization. Any authenticated user can read any organization's subscription plan, monthly event count, billing period, and status.
Secure adjacent pattern: createCheckoutSession.ts correctly verifies owner role:
const memberResult = await db.select({ role: member.role })
.from(member)
.where(and(eq(member.userId, userId), eq(member.organizationId, organizationId)));
if (!memberResult.length || memberResult[0].role !== "owner") {
return reply.status(403).send({ error: "Only organization owners can manage billing" });
}2. API Key Bypasses Restricted Site Access (MEDIUM)
File: server/src/api/sites/getSitesFromOrg.ts
When authenticated via API key, req.user?.id is null, causing the restricted site access filter to be skipped. A member with hasRestrictedSiteAccess=true (intentionally limited to specific sites by an org admin) sees ALL sites when using an API key instead of session auth.
const userId = req.user?.id; // NULL with API key auth
const memberRecord = memberCheck[0]; // undefined when userId is null
if (memberRecord?.role === "member" && memberRecord.hasRestrictedSiteAccess) {
// This block NEVER reached with API key auth
}Recommended Fix
- Add
orgMembermiddleware to the subscription endpoint - Resolve API key auth to populate
req.user.idfor consistent filtering
Reported responsibly per SECURITY.md.