CVE-2026-73308: Budibase OAuth2 Token Disclosure Vulnerability

MODERATE
Advisory Identifier: GHSA-gh4h-34gr-87r7
CVSS 5.7
CWE-200
Published
Advisory Summary

CVE-2026-73308 is a MODERATE (CVSS 5.7) oauth2 token disclosure vulnerability (CWE-200: Exposure of Sensitive Information to an Unauthorized Actor) affecting Budibase libraries in the npm ecosystem. The issue involves oauth2 token disclosure via automation test results broadcast to other builders. Remediation is available in the latest vendor maintenance releases.

CVE-2026-73308 at a Glance

CVE Identifier CVE-2026-73308
GHSA Identifier GHSA-gh4h-34gr-87r7
Severity MODERATE
CVSS v3.1 Base Score 5.7
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: 5.7

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

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

How to Fix CVE-2026-73308

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: Latest secure release
npm install @budibase/server@Latest secure release
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-73308, 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

## Summary When an SSO-authenticated user tests an automation in the Budibase builder, their OAuth2 access token and refresh token are included in the automation test results. These results are broadcast via WebSocket to all builders connected to the same dev app and stored in an in-memory cache accessible to any builder who polls the test status endpoint. This allows any co-builder of the same app to steal the testing user's OAuth2 tokens. ## Details The vulnerability exists because `getUserContextBindings()` intentionally includes OAuth2 tokens in user context bindings (so automations can call external APIs), but the automation test pipeline exposes the full result — including these tokens — to all builders of the same app without sanitization. **Step 1: Tokens included in user bindings** In `packages/server/src/sdk/users/utils.ts:134-161`: ```typescript export function getUserContextBindings(user: ContextUser): UserBindings { const bindings: UserBindings = { _id: user._id, email: user.email, // ... } if (isSSOUser(user) && user.oauth2) { bindings.oauth2 = { accessToken: user.oauth2.accessToken, // <-- sensitive refreshToken: user.oauth2.refreshToken, // <-- sensitive } } return bindings } ``` **Step 2: Bindings passed to automation execution** In `packages/server/src/api/controllers/automation.ts:311-312`: ```typescript const user = sdk.users.getUserContextBindings(ctx.user) return await triggers.externalTrigger( { ...automation, disabled: false }, { ...input, appId, user }, // user with tokens passed as event param { getResponses: true, onProgress: emitProgress } ) ``` **Step 3: Tokens placed in trigger outputs** In `packages/server/src/threads/automation.ts:409-413`: ```typescript const trigger: AutomationTriggerResult = { id: data.automation.definition.trigger.id, stepId: data.automation.definition.trigger.stepId, inputs: null, outputs: data.event, // data.event includes user.oauth2 tokens } ``` **Step 4: Result broadcast without sanitization** The full result (including `trigger.outputs.user.oauth2`) is exposed via two vectors: 1. **WebSocket broadcast** — `builderSocket.emitToRoom()` calls `this.io.in(room).emit()` (`packages/server/src/websockets/websocket.ts:291`) which sends to ALL sockets in the app's room, not just the originator. 2. **Test status endpoint** — `recordTestProgress()` stores the result in a Map keyed by `${appId}:${automationId}` with no user isolation (`packages/server/src/automations/testProgress.ts:41-74`). Any builder can call `GET /api/automations/:id/test/status` to retrieve another user's test results. ## PoC Requires two builder-level users on the same Budibase app, where User A authenticates via SSO/OIDC (Google, Azure AD, etc.) which provides OAuth2 tokens. ```bash # Step 1: User A (SSO-authenticated builder) tests an automation asynchronously curl -X POST http://localhost:10000/api/automations/<automation-id>/test?async=true \ -H 'x-budibase-app-id: app_dev_<appid>' \ -H 'Cookie: budibase:auth=<userA_session>' \ -H 'Content-Type: application/json' \ -d '{"row": {"tableId": "ta_xxx"}}' # Returns: {"message": "Automation test started"} # Step 2: User B (another builder on the same app) polls the test status curl -X GET http://localhost:10000/api/automations/<automation-id>/test/status \ -H 'x-budibase-app-id: app_dev_<appid>' \ -H 'Cookie: budibase:auth=<userB_session>' # Response includes the full automation result with: # result.trigger.outputs.user.oauth2.accessToken = "ya29.a0AfH6SM..." # result.trigger.outputs.user.oauth2.refreshToken = "1//0eXyz..." ``` Additionally, User B can passively receive the tokens by simply having the Budibase builder open (connected via WebSocket), as the `BuilderSocketEvent.AutomationTestProgress` event with `status: "complete"` includes the full result payload. ## Impact - **OAuth2 access tokens** for external services (Google Workspace, Azure AD, GitHub, etc.) are exposed to co-builders of the same app. These tokens can be used to access external APIs as the victim user. - **OAuth2 refresh tokens** provide persistent access — an attacker can generate new access tokens even after the original expires, maintaining long-term access to the victim's external service accounts. - The attack is passive via WebSocket — an attacker only needs to have the builder UI open to receive tokens when any co-builder tests an automation. - Test results persist in memory for 5 minutes (TTL in `testProgress.ts:15`), providing a window for polling-based attacks. ## Recommended Fix Strip OAuth2 tokens from automation test results before storing/broadcasting them. The tokens are needed during automation execution but should not be included in the result sent to clients. In `packages/server/src/api/controllers/automation.ts`, sanitize the result before passing to `emitProgress`: ```typescript function sanitizeAutomationResult(result: AutomationResults): AutomationResults { const sanitized = cloneDeep(result) if (sanitized.trigger?.outputs?.user?.oauth2) { delete sanitized.trigger.outputs.user.oauth2 } for (const step of sanitized.steps || []) { if (step.outputs?.user?.oauth2) { delete step.outputs.user.oauth2 } } return sanitized } ``` Apply sanitization in the `emitProgress` callback at line 290 and before returning `ctx.body` at line 342: ```typescript emitProgress({ status: "complete", occurredAt: Date.now(), result: sanitizeAutomationResult(result), }) ``` Additionally, the `testProgress` store should be scoped per-user (keyed by `${appId}:${automationId}:${userId}`) so that one builder's test results are not accessible to another builder via the `testStatus` endpoint.

CVE-2026-73308 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-73308?
CVE-2026-73308 is a security vulnerability in Budibase involving OAuth2 Token Disclosure via Automation Test Results Broadcast to Other Builders under CWE-200: Exposure of Sensitive Information to an Unauthorized Actor.
Which Budibase versions are affected?
Affected versions include releases prior to current release patches across affected packages: @budibase/server.
What version fixes CVE-2026-73308?
Upgrading to current release patches (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 Latest secure release. Upgrade your dependency to Latest secure release or later.
What is the CVSS score of CVE-2026-73308?
CVE-2026-73308 has a CVSS v3.1 base score of 5.7 (MODERATE) with vector string CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:N/A:N.
What CWE is associated with CVE-2026-73308?
CVE-2026-73308 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-73308?
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-gh4h-34gr-87r7",
  "modified" : "2026-08-12T19:01:22Z",
  "published" : "2026-07-24T21:25:20Z",
  "aliases" : [ "CVE-2026-73308" ],
  "summary" : " Budibase: OAuth2 Token Disclosure via Automation Test Results Broadcast to Other Builders",
  "details" : "## Summary\n\nWhen an SSO-authenticated user tests an automation in the Budibase builder, their OAuth2 access token and refresh token are included in the automation test results. These results are broadcast via WebSocket to all builders connected to the same dev app and stored in an in-memory cache accessible to any builder who polls the test status endpoint. This allows any co-builder of the same app to steal the testing user's OAuth2 tokens.\n\n## Details\n\nThe vulnerability exists because `getUserContextBindings()` intentionally includes OAuth2 tokens in user context bindings (so automations can call external APIs), but the automation test pipeline exposes the full result — including these tokens — to all builders of the same app without sanitization.\n\n**Step 1: Tokens included in user bindings**\n\nIn `packages/server/src/sdk/users/utils.ts:134-161`:\n```typescript\nexport function getUserContextBindings(user: ContextUser): UserBindings {\n  const bindings: UserBindings = {\n    _id: user._id,\n    email: user.email,\n    // ...\n  }\n  if (isSSOUser(user) && user.oauth2) {\n    bindings.oauth2 = {\n      accessToken: user.oauth2.accessToken,   // <-- sensitive\n      refreshToken: user.oauth2.refreshToken,  // <-- sensitive\n    }\n  }\n  return bindings\n}\n```\n\n**Step 2: Bindings passed to automation execution**\n\nIn `packages/server/src/api/controllers/automation.ts:311-312`:\n```typescript\nconst user = sdk.users.getUserContextBindings(ctx.user)\nreturn await triggers.externalTrigger(\n  { ...automation, disabled: false },\n  { ...input, appId, user },  // user with tokens passed as event param\n  { getResponses: true, onProgress: emitProgress }\n)\n```\n\n**Step 3: Tokens placed in trigger outputs**\n\nIn `packages/server/src/threads/automation.ts:409-413`:\n```typescript\nconst trigger: AutomationTriggerResult = {\n  id: data.automation.definition.trigger.id,\n  stepId: data.automation.definition.trigger.stepId,\n  inputs: null,\n  outputs: data.event,  // data.event includes user.oauth2 tokens\n}\n```\n\n**Step 4: Result broadcast without sanitization**\n\nThe full result (including `trigger.outputs.user.oauth2`) is exposed via two vectors:\n\n1. **WebSocket broadcast** — `builderSocket.emitToRoom()` calls `this.io.in(room).emit()` (`packages/server/src/websockets/websocket.ts:291`) which sends to ALL sockets in the app's room, not just the originator.\n\n2. **Test status endpoint** — `recordTestProgress()` stores the result in a Map keyed by `${appId}:${automationId}` with no user isolation (`packages/server/src/automations/testProgress.ts:41-74`). Any builder can call `GET /api/automations/:id/test/status` to retrieve another user's test results.\n\n## PoC\n\nRequires two builder-level users on the same Budibase app, where User A authenticates via SSO/OIDC (Google, Azure AD, etc.) which provides OAuth2 tokens.\n\n```bash\n# Step 1: User A (SSO-authenticated builder) tests an automation asynchronously\ncurl -X POST http://localhost:10000/api/automations/<automation-id>/test?async=true \\\n  -H 'x-budibase-app-id: app_dev_<appid>' \\\n  -H 'Cookie: budibase:auth=<userA_session>' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"row\": {\"tableId\": \"ta_xxx\"}}'\n# Returns: {\"message\": \"Automation test started\"}\n\n# Step 2: User B (another builder on the same app) polls the test status\ncurl -X GET http://localhost:10000/api/automations/<automation-id>/test/status \\\n  -H 'x-budibase-app-id: app_dev_<appid>' \\\n  -H 'Cookie: budibase:auth=<userB_session>'\n\n# Response includes the full automation result with:\n# result.trigger.outputs.user.oauth2.accessToken = \"ya29.a0AfH6SM...\"\n# result.trigger.outputs.user.oauth2.refreshToken = \"1//0eXyz...\"\n```\n\nAdditionally, User B can passively receive the tokens by simply having the Budibase builder open (connected via WebSocket), as the `BuilderSocketEvent.AutomationTestProgress` event with `status: \"complete\"` includes the full result payload.\n\n## Impact\n\n- **OAuth2 access tokens** for external services (Google Workspace, Azure AD, GitHub, etc.) are exposed to co-builders of the same app. These tokens can be used to access external APIs as the victim user.\n- **OAuth2 refresh tokens** provide persistent access — an attacker can generate new access tokens even after the original expires, maintaining long-term access to the victim's external service accounts.\n- The attack is passive via WebSocket — an attacker only needs to have the builder UI open to receive tokens when any co-builder tests an automation.\n- Test results persist in memory for 5 minutes (TTL in `testProgress.ts:15`), providing a window for polling-based attacks.\n\n## Recommended Fix\n\nStrip OAuth2 tokens from automation test results before storing/broadcasting them. The tokens are needed during automation execution but should not be included in the result sent to clients.\n\nIn `packages/server/src/api/controllers/automation.ts`, sanitize the result before passing to `emitProgress`:\n\n```typescript\nfunction sanitizeAutomationResult(result: AutomationResults): AutomationResults {\n  const sanitized = cloneDeep(result)\n  if (sanitized.trigger?.outputs?.user?.oauth2) {\n    delete sanitized.trigger.outputs.user.oauth2\n  }\n  for (const step of sanitized.steps || []) {\n    if (step.outputs?.user?.oauth2) {\n      delete step.outputs.user.oauth2\n    }\n  }\n  return sanitized\n}\n```\n\nApply sanitization in the `emitProgress` callback at line 290 and before returning `ctx.body` at line 342:\n\n```typescript\nemitProgress({\n  status: \"complete\",\n  occurredAt: Date.now(),\n  result: sanitizeAutomationResult(result),\n})\n```\n\nAdditionally, the `testProgress` store should be scoped per-user (keyed by `${appId}:${automationId}:${userId}`) so that one builder's test results are not accessible to another builder via the `testStatus` endpoint.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/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-gh4h-34gr-87r7"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/Budibase/budibase/pull/19107"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/Budibase/budibase/commit/bca426de7dc36d680285295655dc640dea2aab21"
  }, {
    "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:25:20Z",
    "nvd_published_at" : null
  }
}