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

A working proof-of-concept for CVE-2026-54351 in @budibase/server, with the exact payload below.

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]

Related research