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
orgMember middleware to the subscription endpoint
- Resolve API key auth to populate
req.user.id for consistent filtering
Reported responsibly per SECURITY.md.
Summary
Two authorization issues in the Rybbit server:
1. Subscription Data Exposure (HIGH)
File:
server/src/api/stripe/getSubscription.tsRoute:
GET /stripe/subscription(registered withauthOnlymiddleware)The endpoint accepts an
organizationIdquery parameter but only checks that the user is authenticated (viaauthOnly), 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.tscorrectly verifies owner role:2. API Key Bypasses Restricted Site Access (MEDIUM)
File:
server/src/api/sites/getSitesFromOrg.tsWhen authenticated via API key,
req.user?.idis null, causing the restricted site access filter to be skipped. A member withhasRestrictedSiteAccess=true(intentionally limited to specific sites by an org admin) sees ALL sites when using an API key instead of session auth.Recommended Fix
orgMembermiddleware to the subscription endpointreq.user.idfor consistent filteringReported responsibly per SECURITY.md.