CVE-2026-50132: Budibase Chat-Link Handoff Identity Confusion CSRF
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…

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.
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.
Related research
- high · 8.8Budibase: App-Scoped Builder Privilege Escalation via Public Role Assignment API
- highBudibase Email Change IDOR Allows Full Account Takeover via POST /api/v2/email
- high · 7.1Budibase MongoDB Integration NoSQL Operator Injection
- high · 8.5Budibase REST Datasource SSRF via DNS Rebinding (undici dispatcher bypasses IP pin)