CVE-2026-73304: Budibase SSO OAuth2 Token Leakage Vulnerability

MODERATE
Advisory Identifier: GHSA-fcrw-f7gg-6g9f
CVSS 4.9
CWE-200
Published
Advisory Summary

CVE-2026-73304 is a MODERATE (CVSS 4.9) sso oauth2 token leakage vulnerability (CWE-200: Exposure of Sensitive Information to an Unauthorized Actor) affecting Budibase libraries in the npm ecosystem. The issue involves sso oauth2 token leakage via user metadata endpoints to power-role users. Remediation is available by upgrading affected packages to version 3.39.25.

CVE-2026-73304 at a Glance

CVE Identifier CVE-2026-73304
GHSA Identifier GHSA-fcrw-f7gg-6g9f
Severity MODERATE
CVSS v3.1 Base Score 4.9
CWE Classification CWE-200: Exposure of Sensitive Information to an Unauthorized Actor
Affected Product Budibase
Package Ecosystem npm
Published Date
Last Updated

Security Impact

CVSS: 4.9

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required High (PR:H)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality High (C:H)
Integrity None (I:N)
Availability None (A:N)

How to Fix CVE-2026-73304

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.

@budibase/server fixed: 3.39.25
npm install @budibase/server@3.39.25
Verify Direct and Transitive Dependencies

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-73304, 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 3.39.25), 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

## Summary The `/api/users/metadata` and `/api/users/metadata/:id` endpoints in `@budibase/server` return full global user profiles to any user with POWER role or above. For SSO-authenticated users (OIDC, Google), the response includes `oauth2.accessToken` and `oauth2.refreshToken` fields, leaking identity provider credentials to other users who should not have access to them. ## Details When a user authenticates via SSO (OIDC or Google), the OAuth2 tokens are stored in the global CouchDB user document at `packages/backend-core/src/auth/auth.ts:170-173`: ```typescript dbUser.oauth2 = { ...dbUser.oauth2, ...details, } await db.put(dbUser) ``` The user metadata endpoints are protected by `PermissionType.USER, PermissionLevel.READ` (`packages/server/src/api/routes/user.ts:11`), which maps to the POWER permission set (`packages/backend-core/src/security/permissions.ts:99`). **Path 1 — List all users** (`GET /api/users/metadata`): 1. `controller.fetchMetadata` → `sdk.users.fetchMetadata()` (`packages/server/src/sdk/users/utils.ts:78`) 2. → `getGlobalUsers()` → `getRawGlobalUsers()` (`packages/server/src/utilities/global.ts:101-122`) — strips only `password` and `forceResetPassword` 3. → `processUser()` (`packages/server/src/utilities/global.ts:15-76`) — strips only `password` and `roles` 4. The `oauth2` field containing `accessToken` and `refreshToken` is **never removed** **Path 2 — Single user** (`GET /api/users/metadata/:id`): 1. `controller.findMetadata` → `getFullUser()` (`packages/server/src/utilities/users.ts:6`) 2. → `getGlobalUser()` → `getRawGlobalUser()` (`packages/server/src/utilities/global.ts:90-92`) — raw CouchDB fetch, **no field stripping at all** 3. → `processUser()` — strips only `password` and `roles` 4. Same result: `oauth2` tokens are returned There is no output sanitization middleware on these routes that would strip sensitive fields before the response reaches the client. ## PoC **Prerequisites:** A Budibase instance with at least one SSO-authenticated user (OIDC or Google) and a separate user with POWER role in an app. **Step 1 — As the POWER user, list all users:** ```bash curl -s -X GET 'http://localhost:10000/api/users/metadata' \ -H 'Cookie: budibase:auth=<power-user-jwt>' \ -H 'x-budibase-app-id: app_<appid>' | jq '.[].oauth2' ``` **Expected output:** `null` for all users (tokens should not be exposed) **Actual output:** For SSO users, the response includes: ```json { "accessToken": "ya29.a0ARrdaM...", "refreshToken": "1//0eXxXxXxXx..." } ``` **Step 2 — Fetch a specific SSO user's profile:** ```bash curl -s -X GET 'http://localhost:10000/api/users/metadata/ro_ta_users_us_<sso-user-id>' \ -H 'Cookie: budibase:auth=<power-user-jwt>' \ -H 'x-budibase-app-id: app_<appid>' | jq '.oauth2' ``` This also returns the full OAuth2 tokens. ## Impact - A user with POWER role in any app can read **all SSO users' OAuth2 access tokens and refresh tokens** via the list endpoint, without needing to know individual user IDs. - Stolen access tokens can be used to access external identity provider resources (Google Workspace, Azure AD, Okta-protected services) as the victim user. - Refresh tokens allow indefinite token renewal, persisting access even after the original access token expires. - Additionally, `admin`, `builder`, `tenantId`, `ssoId`, and `userGroups` fields are leaked, revealing the full authorization topology of the instance. ## Recommended Fix Strip sensitive SSO fields in `processUser()` at `packages/server/src/utilities/global.ts:15`: ```typescript export async function processUser( user: ContextUser, opts: { appId?: string; groups?: UserGroup[] } = {} ) { if (!user || (!user.roles && !user.userGroups)) { return user } user = cloneDeep(user) delete user.password + delete (user as any).oauth2 + delete (user as any).provider + delete (user as any).providerType + delete (user as any).thirdPartyProfile + delete (user as any).profile + delete (user as any).ssoId + delete (user as any).forceResetPassword // ... rest of function ``` Additionally, `getRawGlobalUsers()` at line 101 should also strip `oauth2` alongside its existing `password`/`forceResetPassword` stripping for defense in depth.

CVE-2026-73304 Affected Budibase Versions

1 component(s)
Fixed Versions:
3.39.25
Product / Package Ecosystem Affected Versions Fixed Version
@budibase/server
npm
< 3.39.25
3.39.25

References

Frequently Asked Questions

What is CVE-2026-73304?
CVE-2026-73304 is a security vulnerability in Budibase involving SSO OAuth2 Token Leakage via User Metadata Endpoints to Power-Role Users under CWE-200: Exposure of Sensitive Information to an Unauthorized Actor.
Which Budibase versions are affected?
Affected versions include releases prior to 3.39.25 across affected packages: @budibase/server.
What version fixes CVE-2026-73304?
Upgrading to 3.39.25 (or the latest patched release for your respective branch) resolves the vulnerability.
Is @budibase/server affected?
Yes, @budibase/server is affected in releases prior to 3.39.25. Upgrade your dependency to 3.39.25 or later.
What is the CVSS score of CVE-2026-73304?
CVE-2026-73304 has a CVSS v3.1 base score of 4.9 (MODERATE) with vector string CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N.
What CWE is associated with CVE-2026-73304?
CVE-2026-73304 is classified under 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 can I check npm dependencies for CVE-2026-73304?
Run the dependency tree command: 'npm ls @budibase/server'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-fcrw-f7gg-6g9f",
  "modified" : "2026-08-12T18:57:12Z",
  "published" : "2026-07-24T21:14:31Z",
  "aliases" : [ "CVE-2026-73304" ],
  "summary" : " Budibase: SSO OAuth2 Token Leakage via User Metadata Endpoints to Power-Role Users",
  "details" : "## Summary\n\nThe `/api/users/metadata` and `/api/users/metadata/:id` endpoints in `@budibase/server` return full global user profiles to any user with POWER role or above. For SSO-authenticated users (OIDC, Google), the response includes `oauth2.accessToken` and `oauth2.refreshToken` fields, leaking identity provider credentials to other users who should not have access to them.\n\n## Details\n\nWhen a user authenticates via SSO (OIDC or Google), the OAuth2 tokens are stored in the global CouchDB user document at `packages/backend-core/src/auth/auth.ts:170-173`:\n\n```typescript\ndbUser.oauth2 = {\n  ...dbUser.oauth2,\n  ...details,\n}\nawait db.put(dbUser)\n```\n\nThe user metadata endpoints are protected by `PermissionType.USER, PermissionLevel.READ` (`packages/server/src/api/routes/user.ts:11`), which maps to the POWER permission set (`packages/backend-core/src/security/permissions.ts:99`).\n\n**Path 1 — List all users** (`GET /api/users/metadata`):\n1. `controller.fetchMetadata` → `sdk.users.fetchMetadata()` (`packages/server/src/sdk/users/utils.ts:78`)\n2. → `getGlobalUsers()` → `getRawGlobalUsers()` (`packages/server/src/utilities/global.ts:101-122`) — strips only `password` and `forceResetPassword`\n3. → `processUser()` (`packages/server/src/utilities/global.ts:15-76`) — strips only `password` and `roles`\n4. The `oauth2` field containing `accessToken` and `refreshToken` is **never removed**\n\n**Path 2 — Single user** (`GET /api/users/metadata/:id`):\n1. `controller.findMetadata` → `getFullUser()` (`packages/server/src/utilities/users.ts:6`)\n2. → `getGlobalUser()` → `getRawGlobalUser()` (`packages/server/src/utilities/global.ts:90-92`) — raw CouchDB fetch, **no field stripping at all**\n3. → `processUser()` — strips only `password` and `roles`\n4. Same result: `oauth2` tokens are returned\n\nThere is no output sanitization middleware on these routes that would strip sensitive fields before the response reaches the client.\n\n## PoC\n\n**Prerequisites:** A Budibase instance with at least one SSO-authenticated user (OIDC or Google) and a separate user with POWER role in an app.\n\n**Step 1 — As the POWER user, list all users:**\n```bash\ncurl -s -X GET 'http://localhost:10000/api/users/metadata' \\\n  -H 'Cookie: budibase:auth=<power-user-jwt>' \\\n  -H 'x-budibase-app-id: app_<appid>' | jq '.[].oauth2'\n```\n\n**Expected output:** `null` for all users (tokens should not be exposed)\n\n**Actual output:** For SSO users, the response includes:\n```json\n{\n  \"accessToken\": \"ya29.a0ARrdaM...\",\n  \"refreshToken\": \"1//0eXxXxXxXx...\"\n}\n```\n\n**Step 2 — Fetch a specific SSO user's profile:**\n```bash\ncurl -s -X GET 'http://localhost:10000/api/users/metadata/ro_ta_users_us_<sso-user-id>' \\\n  -H 'Cookie: budibase:auth=<power-user-jwt>' \\\n  -H 'x-budibase-app-id: app_<appid>' | jq '.oauth2'\n```\n\nThis also returns the full OAuth2 tokens.\n\n## Impact\n\n- A user with POWER role in any app can read **all SSO users' OAuth2 access tokens and refresh tokens** via the list endpoint, without needing to know individual user IDs.\n- Stolen access tokens can be used to access external identity provider resources (Google Workspace, Azure AD, Okta-protected services) as the victim user.\n- Refresh tokens allow indefinite token renewal, persisting access even after the original access token expires.\n- Additionally, `admin`, `builder`, `tenantId`, `ssoId`, and `userGroups` fields are leaked, revealing the full authorization topology of the instance.\n\n## Recommended Fix\n\nStrip sensitive SSO fields in `processUser()` at `packages/server/src/utilities/global.ts:15`:\n\n```typescript\nexport async function processUser(\n  user: ContextUser,\n  opts: { appId?: string; groups?: UserGroup[] } = {}\n) {\n  if (!user || (!user.roles && !user.userGroups)) {\n    return user\n  }\n  user = cloneDeep(user)\n  delete user.password\n+ delete (user as any).oauth2\n+ delete (user as any).provider\n+ delete (user as any).providerType\n+ delete (user as any).thirdPartyProfile\n+ delete (user as any).profile\n+ delete (user as any).ssoId\n+ delete (user as any).forceResetPassword\n  // ... rest of function\n```\n\nAdditionally, `getRawGlobalUsers()` at line 101 should also strip `oauth2` alongside its existing `password`/`forceResetPassword` stripping for defense in depth.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "npm",
      "name" : "@budibase/server"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "3.39.25"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/Budibase/budibase/security/advisories/GHSA-fcrw-f7gg-6g9f"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/Budibase/budibase/pull/19110"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/Budibase/budibase/commit/7b8ba11a2c8b233c35e7728dd752dba25ef919a4"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/Budibase/budibase/commit/80a31f6c3354620aa90e50af8a2c614333084621"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/Budibase/budibase"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/Budibase/budibase/releases/tag/3.39.25"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-200" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-07-24T21:14:31Z",
    "nvd_published_at" : null
  }
}