Skip to content

Bug: GET /api/events/:slug exposes internal organizerId UUID to unauthenticated callers #330

@anshul23102

Description

@anshul23102

Summary

The GET /api/events/:slug endpoint is publicly accessible (no jwtVerify call) and its response type EventDetails includes organizerId - the raw database UUID of the user who created the event. Exposing internal primary keys to unauthenticated clients is an information disclosure vulnerability that widens the attack surface for enumeration attacks on user IDs.


Affected File

apps/backend/src/routes/event.ts - GET /:slug handler (lines 123-155)

type EventDetails = {
    id: string;
    name: string;
    slug: string;
    location: string;
    description: string | null;
    organizerId: string;  // <-- internal UUID sent to any anonymous caller
    startDate: Date;
    endDate: Date;
    createdAt: Date;
    attendeesCount: number
}

// ...

const response: EventDetails = {
    id: details.id,
    name: details.name,
    slug: details.slug,
    description: details.description,
    location: details.location,
    organizerId: details.organizerId,  // <-- exposed here
    startDate: details.startDate,
    endDate: details.endDate,
    createdAt: details.createdAt,
    attendeesCount: details._count.attendees
}

Impact

  • Information disclosure: Any anonymous visitor can harvest the internal UUID of every event organizer by iterating over event slugs.
  • Enumeration risk: Internal UUIDs returned by one endpoint can be cross-referenced with other endpoints that accept a user ID parameter, potentially facilitating IDOR probes.
  • Principle of least privilege violation: Public API surfaces should expose the minimum data needed. A public event page does not need the organizer's database ID - a public-facing identifier (username or display name) is sufficient.

Suggested Fix

Replace organizerId with the organizer's public username by joining the user table in the Prisma query:

const details = await app.prisma.event.findUnique({
    where: { slug: paramsSlug },
    include: {
        _count: { select: { attendees: true } },
        organizer: { select: { username: true, displayName: true } }  // join user
    }
})

Then update the response type and mapping:

type EventDetails = {
    id: string;
    name: string;
    slug: string;
    location: string;
    description: string | null;
    organizerUsername: string;   // public identifier, not internal UUID
    organizerDisplayName: string;
    startDate: Date;
    endDate: Date;
    createdAt: Date;
    attendeesCount: number
}

This removes the internal UUID from the public surface entirely while still giving the frontend everything it needs to display event organizer info.


Environment

  • apps/backend/src/routes/event.ts
  • Prisma ORM with PostgreSQL
  • Fastify backend

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions