CVE-2026-73406: Budibase Unauthenticated User Information Disclosure Vulnerability
HIGHCVE-2026-73406 is a HIGH (CVSS 7.5) unauthenticated user information disclosure vulnerability (CWE-200: Exposure of Sensitive Information to an Unauthorized Actor) affecting Budibase libraries in the npm ecosystem. The issue involves unauthenticated user information disclosure via public tenant user lookup endpoint. Remediation is available in the latest vendor maintenance releases.
CVE-2026-73406 at a Glance
Security Impact
CVSS: 7.5
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N).
How to Fix CVE-2026-73406
To remediate this vulnerability, upgrade all affected Budibase dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
npm install @budibase/server@Latest secure release
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
npm ls @budibase/server
How to Check if You Are Affected
To determine whether your Node.js application is affected by CVE-2026-73406, inspect your project dependencies using npm:
npm ls @budibase/server
Analyze the terminal output to locate any version of Budibase in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than the patched release), your application is affected.
Note: The npm ls @budibase/server command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-200: Exposure of Sensitive Information to an Unauthorized Actor, where the product exposes sensitive information to an actor that is not explicitly authorized to have access to that information. In Budibase, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-73406 Affected Budibase Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
@budibase/server
|
npm |
≤ 3.38.1
|
|
References
Frequently Asked Questions
What is CVE-2026-73406?
Which Budibase versions are affected?
What version fixes CVE-2026-73406?
Is @budibase/server affected?
What is the CVSS score of CVE-2026-73406?
What CWE is associated with CVE-2026-73406?
How can I check npm dependencies for CVE-2026-73406?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-hr66-5mqr-8mpx",
"modified" : "2026-08-12T19:03:58Z",
"published" : "2026-07-24T21:25:00Z",
"aliases" : [ "CVE-2026-73406" ],
"summary" : " Budibase: Unauthenticated user information disclosure via public tenant user lookup endpoint",
"details" : "#### Summary\nThe 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.\n\n#### Details\n\n**Public endpoint registration** at `packages/worker/src/api/index.ts` lines 56-59:\n\n```typescript\n// TODO: This should be an internal api\n{\n route: \"/api/global/users/tenant/:id\",\n method: \"GET\",\n},\n```\n\nThis 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).\n\nAll subsequent middleware also skips for public endpoints:\n- `buildTenancyMiddleware` — passes through\n- `activeTenant` — passes through\n- `buildCsrfMiddleware` — skipped for GET methods (line 48 of csrf.ts)\n- The `budibaseAccess` gate at lines 160-168 explicitly returns `next()` when `ctx.publicEndpoint` is true\n\n**Route registration** at `packages/worker/src/api/routes/global/users.ts` line 139:\n\n```typescript\nloggedInRoutes\n .get(\"/api/global/users/tenant/:id\", controller.tenantUserLookup)\n```\n\n`loggedInRoutes` has no auth middleware group — it is created with `endpointGroupList.group()` (no middleware).\n\n**Handler implementation** at `packages/worker/src/api/controllers/global/users.ts` lines 548-562:\n\n```typescript\nexport const tenantUserLookup = async (\n ctx: UserCtx<void, LookupTenantUserResponse>\n) => {\n const id = ctx.params.id\n // is email, check its valid\n if (id.includes(\"@\") && !emailValidator.validate(id)) {\n ctx.throw(400, `${id} is not a valid email address to lookup.`)\n }\n const user = await userSdk.core.getFirstPlatformUser(id)\n if (user) {\n ctx.body = user // Returns full PlatformUser object — no field filtering\n } else {\n ctx.throw(400, \"No tenant user found.\")\n }\n}\n```\n\nThe `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`:\n\n```typescript\nexport interface PlatformUserByEmail extends Document {\n tenantId: string // Tenant identifier\n userId: string // Internal user ID\n}\n\nexport interface PlatformUserById extends Document {\n tenantId: string // Tenant identifier\n email?: string // User email address\n ssoId?: string // SSO provider identifier\n}\n\nexport interface PlatformUserBySsoId extends Document {\n tenantId: string // Tenant identifier\n userId: string // Internal user ID\n email: string // User email address\n ssoId?: string // SSO provider identifier\n}\n```\n\nThe 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`.\n\n**Affected files:**\n- `packages/worker/src/api/index.ts:56-59` — Public endpoint registration\n- `packages/worker/src/api/routes/global/users.ts:139` — Route on unauthenticated group\n- `packages/worker/src/api/controllers/global/users.ts:548-562` — Handler returning full user object\n- `packages/backend-core/src/users/lookup.ts:48-53` — Platform user lookup with `include_docs: true`\n- `packages/types/src/documents/platform/users.ts:6-36` — PlatformUser types\n\n#### PoC\n\n**Static verification:**\n\n1. Observe `packages/worker/src/api/index.ts:56-59`: endpoint in `PUBLIC_ENDPOINTS` with `// TODO: This should be an internal api`\n2. Trace handler at `packages/worker/src/api/controllers/global/users.ts:548-562`: no auth checks, returns `ctx.body = user` (full object)\n3. Trace middleware chain: all middleware passes through for `ctx.publicEndpoint === true`\n4. Confirm no field filtering, sanitization, or authorization between request and response\n\n**Dynamic verification (requires running Budibase instance with at least one user):**\n\n```bash\n# No authentication headers or cookies required\n# Lookup by email:\ncurl -s http://localhost:4002/api/global/users/tenant/admin@example.com\n\n# Response (200 OK):\n# {\n# \"_id\": \"admin@example.com\",\n# \"_rev\": \"1-abc123...\",\n# \"tenantId\": \"tenant-uuid-here\",\n# \"userId\": \"us_uuid-here\"\n# }\n\n# Lookup by user ID:\ncurl -s http://localhost:4002/api/global/users/tenant/us_someuserid123\n\n# Response (200 OK):\n# {\n# \"_id\": \"us_someuserid123\",\n# \"_rev\": \"1-abc123...\",\n# \"tenantId\": \"tenant-uuid-here\",\n# \"email\": \"admin@example.com\",\n# \"ssoId\": \"google-oauth-id\"\n# }\n\n# Non-existent user:\ncurl -s http://localhost:4002/api/global/users/tenant/nonexistent@example.com\n# Response: 400 \"No tenant user found.\"\n# (Different response confirms user enumeration)\n```\n\n**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.\n\n#### Impact\nThis is a **CWE-200: Exposure of Sensitive Information to an Unauthorized Actor** vulnerability.\n\n**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.\n\nAn unauthenticated attacker can:\n1. **Enumerate all user accounts** by testing known or guessed email addresses against the endpoint\n2. **Extract tenant IDs** for any known user, enabling targeted cross-tenant attacks\n3. **Extract user IDs** (`userId`) for use in other API calls or attacks\n4. **Extract SSO identifiers** (`ssoId`) which may link to external identity providers (Google, OIDC)\n5. **Confirm user existence** through different HTTP responses (200 vs 400)\n6. **Harvest CouchDB revision tokens** (`_rev`) which could assist in CouchDB-level attacks\n\nThe 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.\n\n### Suggested remediation\n1. **Remove the endpoint from `PUBLIC_ENDPOINTS`** and move it to internal-only routes, as the TODO comment at line 56 already suggests\n2. **Add authentication and authorization** if the endpoint must remain accessible — require at least `builderOrAdmin` role\n3. **Limit returned fields** to only what the consumer actually needs (strip `_rev`, `ssoId`, and other sensitive fields)\n4. **Return generic 404** for both \"not found\" and \"access denied\" to prevent user enumeration\n5. **Add rate limiting** to prevent automated mass enumeration\n6. **Regression test:** Add a test verifying `GET /api/global/users/tenant/:id` returns 403 without authentication",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "npm",
"name" : "@budibase/server"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"last_affected" : "3.38.1"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/Budibase/budibase/security/advisories/GHSA-hr66-5mqr-8mpx"
}, {
"type" : "WEB",
"url" : "https://github.com/Budibase/budibase/pull/19221"
}, {
"type" : "WEB",
"url" : "https://github.com/Budibase/budibase/commit/e6bf245fbfdaa35804ef7ee901103282edf0c381"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/Budibase/budibase"
}, {
"type" : "WEB",
"url" : "https://github.com/Budibase/budibase/releases/tag/3.39.32"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-200" ],
"severity" : "HIGH",
"github_reviewed" : true,
"github_reviewed_at" : "2026-07-24T21:25:00Z",
"nvd_published_at" : null
}
}