high · 8.2CVE-2026-54351Jun 26, 2026

CVE-2026-54351: Budibase Server Mass Assignment via Webhook Trigger

Shubham Kandhare
Security Engagement Manager, SecureLayer7

Any builder on a Budibase instance can hijack another workspace's data by injecting a foreign appId into a public webhook POST body, causing automations to run in the victim's database context.

Package@budibase/server
Ecosystemnpm
Affected< 3.39.9
Fixed in3.39.9

The problem

The webhook trigger endpoint (`POST /api/webhooks/trigger/:instance/:id`) is registered with no authentication. The controller spreads the raw request body into `externalTrigger()` as `params.fields` alongside a server-derived `appId`.

Inside `externalTrigger()`, `params.fields` is spread *after* `params`, so any matching key in the attacker body silently overwrites the server value. The poisoned `appId` then travels into the async job queue as `data.event.appId`, and the worker uses it verbatim to set the tenant/workspace context.

The result: the attacker's automation steps execute with full read/write access to the victim's database.

Proof of concept

bash
# Prerequisites:
# 1. Attacker has builder access to their own workspace and has created an automation
#    with a Webhook trigger and data-exfiltration steps (Query Rows, Execute Script, etc.).
# 2. Attacker has obtained the victim's workspace ID (format: app_<uuid>).
# 3. Attacker has created a webhook linked to that automation and noted its ID.

curl -X POST https://budibase.example.com/api/webhooks/trigger/app_ATTACKER_ID/wh_WEBHOOK_ID \
  -H 'Content-Type: application/json' \
  -d '{"appId": "app_VICTIM_WORKSPACE_ID", "normalData": "test"}'

Root cause is CWE-915: the object spread in `triggers.ts` lines 237-241 places `...params.fields` after `...params`, giving attacker-supplied body keys precedence over server-set values. Because `appId` is never re-pinned before the async job is enqueued, the worker in `automation.ts` trusts `job.data.event.appId` as the workspace selector.

The synchronous collect-step path already applied a post-queue override (`data.event.appId = context.getWorkspaceId()`), confirming the intent was always server-controlled. The async default path simply lacked that same guard.

Beyond `appId`, the same spread allows overriding `timeout`, `user`, and `metadata.automationChainCount`, widening the impact surface. The fix strips these privileged keys from `params.fields` before the spread, using either a post-spread reassignment or a destructuring allowlist.

The fix

Upgrade `@budibase/server` to **3.39.9** or later. The patch enforces a server-controlled `appId` on the async path in `packages/server/src/automations/triggers.ts` (mirroring the existing fix on the synchronous path), and strips internal fields (`appId`, `timeout`, `user`, `metadata`) from the webhook body before they can overwrite automation parameters.

Reporter not attributed.

References: [1][2]