high · 7.7Jul 24, 2026

CVE-2026-50132: Budibase Chat-Link Handoff Identity Confusion CSRF

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

An attacker who has a Budibase account in the same tenant can trick any authenticated user into permanently linking the attacker's Slack, Discord, or MS Teams identity to the victim's Budibase…

Package@budibase/server
Ecosystemnpm
Affected<= 3.38.1
CVE-2026-50132: Budibase Chat-Link Handoff Identity Confusion CSRF

The problem

The chat-link handoff endpoint (GET /api/chat-links/:instance/:token/handoff) is registered on publicRoutes with no authentication middleware and no CSRF protection. The endpoint performs a permanent, state-changing write on a GET request: it upserts an identity link that binds the requesting user's globalUserId to whatever externalUserId is embedded in the session token.

Because the attacker creates the session token themselves (via the /link slash command in Slack/Discord), they fully control which external identity gets bound. Any authenticated tenant-mate who visits the attacker's URL gets silently linked, with no consent UI shown.

The server replies only with a generic "Authentication succeeded." page.

Proof of concept

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

http
GET /api/chat-links/ws_abc123/tok_xxxxxxxxxxxxxxxx/handoff HTTP/1.1
Host: budibase.company.com
Cookie: budibase:session=VICTIM_SESSION

<!-- Attacker-hosted phishing page that auto-submits (for the two-step POST variant) -->
<form id="f" method="post"
      action="https://budibase.company.com/api/chat-links/ws_abc123/tok_xxxxxxxxxxxxxxxx/handoff">
  <input name="confirmationToken" value="<token-from-GET-page-source>">
</form>
<script>document.getElementById('f').submit()</script>

<!-- Result stored in CouchDB -->
{
  "provider": "slack",
  "externalUserId": "UA12345678",
  "globalUserId": "ro_global_us_VICTIM_ADMIN_ID",
  "linkedBy":     "ro_global_us_VICTIM_ADMIN_ID"
}

The root cause is that handoffChatLinkSession performs a destructive upsert on a GET request with zero caller verification. The session retrieved from Redis was created by the attacker and carries their externalUserId; the only identity injected into the database record is ctx.user.globalId, which belongs to whoever clicked the link.

For the two-step POST variant (GHSA-pvcr-8mvp-w8qr), the confirmationToken is a UUIDv4 stored in plain text in the hidden form input rendered by renderLinkConfirmationPage. An attacker who loads the GET page reads the token from source and replays it in a CSRF form submission.

There is no binding between the token and the requester's session at preparation time, so any authenticated user who submits it gets linked.

The fix (PR #19194, commit 362e6c6) stores the requester's globalUserId inside the session at preparation time and checks that getCurrentGlobalUserId(ctx) matches it at confirmation time, breaking the cross-user confusion. CWE-352 (CSRF) and CWE-287 (Improper Authentication) both apply.

The fix

Upgrade to Budibase 3.39.30 or later (the fix landed in the 3.39.30 release via PR #19194, commit 362e6c654fb4da6e123c734333da2d6cbdcdb7ef). The patch binds the confirmation token to the globalUserId of the user who initiated the handoff, so a different user submitting the same token is rejected.

As a temporary mitigation, restrict network access to the GET /POST /api/chat-links/:instance/:token/handoff endpoints.

Reporter not attributed.

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

Related research