GHSA-hr66-5mqr-8mpx CVE-2026-73406
Budibase: Unauthenticated user information disclosure via public tenant user lookup endpoint
- Severity
- 7.5 / 10
- High · CVSS 3.1
- Exploit chance (EPSS)
- 0.51%
- Chance of exploitation in the next 30 days. Higher than 43.43% of scored vulnerabilities.
- Known exploited
- No
- Not in CISA's catalog
- Weakness
- CWE-200 Exposure of Sensitive Information to an Unauthorized Actor
- Fix
- None yet
- No fixed version published
Description
Summary
The Budibase Worker service exposes a public, unauthenticated API endpoint (GET /api/global/users/tenant/:id) that returns sensitive user information including tenantId, userId, email, and ssoId. The endpoint is registered in the PUBLIC_ENDPOINTS list with a TODO comment acknowledging it "should be an internal API." Any unauthenticated party can enumerate user emails or IDs to extract sensitive tenant and user metadata, enabling targeted attacks against multi-tenant deployments.
Details
Public endpoint registration at packages/worker/src/api/index.ts lines 56-59:
// TODO: This should be an internal api
{
route: "/api/global/users/tenant/:id",
method: "GET",
},
This endpoint is listed in PUBLIC_ENDPOINTS, which is passed to auth.buildAuthMiddleware(PUBLIC_ENDPOINTS) at line 154. When a request matches a public endpoint pattern, the authentication middleware sets ctx.publicEndpoint = true and calls next() without performing any authentication (verified at packages/backend-core/src/middleware/authenticated.ts lines 124-126, 249-251).
All subsequent middleware also skips for public endpoints:
buildTenancyMiddleware— passes throughactiveTenant— passes throughbuildCsrfMiddleware— skipped for GET methods (line 48 of csrf.ts)- The
budibaseAccessgate at lines 160-168 explicitly returnsnext()whenctx.publicEndpointis true
Route registration at packages/worker/src/api/routes/global/users.ts line 139:
loggedInRoutes
.get("/api/global/users/tenant/:id", controller.tenantUserLookup)
loggedInRoutes has no auth middleware group — it is created with endpointGroupList.group() (no middleware).
Handler implementation at packages/worker/src/api/controllers/global/users.ts lines 548-562:
export const tenantUserLookup = async (
ctx: UserCtx<void, LookupTenantUserResponse>
) => {
const id = ctx.params.id
// is email, check its valid
if (id.includes("@") && !emailValidator.validate(id)) {
ctx.throw(400, `${id} is not a valid email address to lookup.`)
}
const user = await userSdk.core.getFirstPlatformUser(id)
if (user) {
ctx.body = user // Returns full PlatformUser object — no field filtering
} else {
ctx.throw(400, "No tenant user found.")
}
}
The id parameter accepts either an email address (detected by @ presence) or a user ID. The response returns the full PlatformUser object from packages/types/src/documents/platform/users.ts:
export interface PlatformUserByEmail extends Document {
tenantId: string // Tenant identifier
userId: string // Internal user ID
}
export interface PlatformUserById extends Document {
tenantId: string // Tenant identifier
email?: string // User email address
ssoId?: string // SSO provider identifier
}
export interface PlatformUserBySsoId extends Document {
tenantId: string // Tenant identifier
userId: string // Internal user ID
email: string // User email address
ssoId?: string // SSO provider identifier
}
The lookup function (packages/backend-core/src/users/lookup.ts:48-53) queries the PLATFORM_USERS_LOWERCASE CouchDB view with include_docs: true, returning the complete platform user document including CouchDB _id and _rev.
Affected files:
packages/worker/src/api/index.ts:56-59— Public endpoint registrationpackages/worker/src/api/routes/global/users.ts:139— Route on unauthenticated grouppackages/worker/src/api/controllers/global/users.ts:548-562— Handler returning full user objectpackages/backend-core/src/users/lookup.ts:48-53— Platform user lookup withinclude_docs: truepackages/types/src/documents/platform/users.ts:6-36— PlatformUser types
PoC
Static verification:
- Observe
packages/worker/src/api/index.ts:56-59: endpoint inPUBLIC_ENDPOINTSwith// TODO: This should be an internal api - Trace handler at
packages/worker/src/api/controllers/global/users.ts:548-562: no auth checks, returnsctx.body = user(full object) - Trace middleware chain: all middleware passes through for
ctx.publicEndpoint === true - Confirm no field filtering, sanitization, or authorization between request and response
Dynamic verification (requires running Budibase instance with at least one user):
# No authentication headers or cookies required
# Lookup by email:
curl -s http://localhost:4002/api/global/users/tenant/admin@example.com
# Response (200 OK):
# {
# "_id": "admin@example.com",
# "_rev": "1-abc123...",
# "tenantId": "tenant-uuid-here",
# "userId": "us_uuid-here"
# }
# Lookup by user ID:
curl -s http://localhost:4002/api/global/users/tenant/us_someuserid123
# Response (200 OK):
# {
# "_id": "us_someuserid123",
# "_rev": "1-abc123...",
# "tenantId": "tenant-uuid-here",
# "email": "admin@example.com",
# "ssoId": "google-oauth-id"
# }
# Non-existent user:
curl -s http://localhost:4002/api/global/users/tenant/nonexistent@example.com
# Response: 400 "No tenant user found."
# (Different response confirms user enumeration)
Negative case: Requesting a non-existent user returns HTTP 400 with "No tenant user found.", while an existing user returns HTTP 200 with full data. The different status codes confirm user existence, enabling enumeration.
Impact
This is a CWE-200: Exposure of Sensitive Information to an Unauthorized Actor vulnerability.
Who is impacted: All Budibase deployments — both self-hosted and cloud. The impact is highest for multi-tenant (cloud) deployments where tenant IDs are security boundaries and user enumeration across tenants enables targeted attacks.
An unauthenticated attacker can:
- Enumerate all user accounts by testing known or guessed email addresses against the endpoint
- Extract tenant IDs for any known user, enabling targeted cross-tenant attacks
- Extract user IDs (
userId) for use in other API calls or attacks - Extract SSO identifiers (
ssoId) which may link to external identity providers (Google, OIDC) - Confirm user existence through different HTTP responses (200 vs 400)
- Harvest CouchDB revision tokens (
_rev) which could assist in CouchDB-level attacks
The returned tenant IDs are particularly dangerous in multi-tenant deployments because they identify the security boundary between organizations. Combined with the hardcoded session keys (separate finding), an attacker could use enumerated tenant IDs to craft targeted session fixation attacks.
Suggested remediation
- Remove the endpoint from
PUBLIC_ENDPOINTSand move it to internal-only routes, as the TODO comment at line 56 already suggests - Add authentication and authorization if the endpoint must remain accessible — require at least
builderOrAdminrole - Limit returned fields to only what the consumer actually needs (strip
_rev,ssoId, and other sensitive fields) - Return generic 404 for both "not found" and "access denied" to prevent user enumeration
- Add rate limiting to prevent automated mass enumeration
- Regression test: Add a test verifying
GET /api/global/users/tenant/:idreturns 403 without authentication
Affected versions
npm
| Package | Vulnerable | Fixed in |
|---|---|---|
| @budibase/server | <= 3.38.1 |
No fix yet |
How to fix
No fixed version has been published yet. Until one is, remove or replace the package, or turn off or isolate the affected feature. This page updates when a fix ships.
Severity breakdown
CVSS 3.1 7.5
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
- Attack vector
- Network
- Attack complexity
- Low
- Privileges required
- None
- User interaction
- None
- Scope
- Unchanged
- Confidentiality
- High
- Integrity
- None
- Availability
- None
Weakness
CWE-200: Exposure of Sensitive Information to an Unauthorized Actor
The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.
How to prevent it
- Architecture and Design: Compartmentalize the system to have "safe" areas where trust boundaries can be unambiguously drawn. Do not allow sensitive data to go outside of the trust boundary and always be careful when interfacing with a compartment outside of the safe area. Ensure that appropriate compartmentalization is built into the system design, and the compartmentalization allows for and reinforces privilege separation functionality. Architects and designers should rely on the principle of least privilege to decide the appropriate time to use privileges and the time to drop privileges.
References
Packages
Other links
- github.com/Budibase/budibase/security/advisories/GHSA-hr66-5mqr-8mpx
- github.com/Budibase/budibase/pull/19221
- github.com/Budibase/budibase/commit/e6bf245fbfdaa35804ef7ee901103282edf0c381
- github.com/Budibase/budibase/releases/tag/3.39.32
- github.com/Budibase/budibase/commit/0bdf648ff4c024a138a546ef84217e4efbee538e
Timeline
- Published
- Last updated by the source
- Latest EPSS score
Related advisories
| Advisory | Severity | Why it's related |
|---|---|---|
| GHSA-6x9p-4r67-5gjx Budibase authenticated arbitrary S3 signed upload URL issuance via `/api/attachments/:datasourceId/url` | High | Also affects @budibase/server |
| GHSA-5fpj-28rv-84r7 Budibase: SSRF in Automation Steps - Webhook, Zapier, N8N, Slack, Discord Bypass IP Blacklist | High | Also affects @budibase/server |
| GHSA-hfhx-w8p8-4hc7 Budibase: SSRF via bare fetch() in uploadUrl during AI table generation | Medium | Also affects @budibase/server |
| GHSA-v42f-v8xc-j435 Budibase: SSRF via DNS rebinding in the REST datasource integration | High | Also affects @budibase/server |
| GHSA-pmpg-2mxq-6xwr Budibase: NoSQL injection in MongoDB integration: collection dump, $where JS exec, cross-collection pivot, arbitrary update/delete | High | Also affects @budibase/server |
| GHSA-cr7p-cr3q-h5cm Budibase: Account Enumeration via Login Lockout Response Differential | Medium | Also affects @budibase/server |
| GHSA-pvcr-8mvp-w8qr Budibase: Chat-Link Handoff Identity Confusion (Same-Tenant Account-Link CSRF) | High | Also affects @budibase/server |
| GHSA-2xgg-r2wc-c5r2 Budibase: MySQL DESCRIBE Backtick Injection via multipleStatements in Database Connector | High | Also affects @budibase/server |
MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.
Check your repos with MergeHarbor