VDB
KO
MEDIUM 6.5

GHSA-363w-hvwh-w7m6

Budibase: CouchDB Reduce Injection via Unsanitized Calculation Parameter in V1 Views API

Details

# Security Advisory: CouchDB Reduce Injection via Unsanitized Calculation Parameter in V1 Views API

**Affected Software:** Budibase **Affected Component:** `packages/server/src/api/controllers/view/viewBuilder.ts`, `packages/server/src/api/routes/view.ts` **CWE:** CWE-94 (Improper Control of Generation of Code) **Discovery Date:** 2026-03-24

---

## Summary

The V1 Views API (`POST /api/views`) accepts a `calculation` parameter from the request body that is interpolated directly into a CouchDB reduce function definition without validation. Although an internal `SCHEMA_MAP` object defines the valid calculation types (`sum`, `count`, `stats`), no actual validation is performed against this map before the value is used in string interpolation.

A user with Builder permissions can inject arbitrary JavaScript code that will be executed within the CouchDB JavaScript engine when the view is queried.

---

## Affected Component

**Route:** `POST /api/views` (V1 legacy views endpoint) **File:** `packages/server/src/api/routes/view.ts`, line 45

```js .post("/api/views", viewController.v1.save) ```

Note: This route has no Joi request body validator, unlike the V2 views endpoint which uses `viewValidator()`.

**Vulnerable code:** `packages/server/src/api/controllers/view/viewBuilder.ts`, line 213

```js const reduction = field && calculation ? { reduce: `_${calculation}` } : {}

return { meta: { field, tableId, groupBy, filters, schema, calculation, ... }, map: `function (doc) { ... }`, ...reduction, // <-- unvalidated calculation string becomes CouchDB reduce } ```

---

## Vulnerability Detail

The `viewBuilder` function constructs a CouchDB design document view definition. It correctly sanitizes all inputs that flow into the `map` function string (using `JSON.stringify` for field names and a strict `TOKEN_MAP` allowlist for filter operators).

However, the `calculation` parameter follows a different path:

1. User submits `calculation` via `POST /api/views` request body 2. No Joi validator is present on this V1 route 3. `viewBuilder` receives `calculation` as a raw string 4. It is interpolated as: `` reduce: `_${calculation}` `` 5. This reduce definition is saved to a CouchDB design document 6. When the view is queried, CouchDB evaluates the reduce value

CouchDB's behavior for reduce functions: - Values starting with `_` followed by a known built-in (`_sum`, `_count`, `_stats`) are executed as native reducers - Any other value is treated as a **JavaScript function string** and executed in CouchDB's SpiderMonkey JS engine

The `SCHEMA_MAP` object in the same file defines `sum`, `count`, and `stats` as valid keys, but this map is only used for schema construction — it is never used as an input validator for the `calculation` parameter.

---

## Steps to Reproduce

**Prerequisites:** Authenticated session with Builder role permissions.

**1. Send a crafted view creation request:**

```bash curl -X POST https://<budibase-instance>/api/views \ -H "Content-Type: application/json" \ -H "Cookie: <builder-session-cookie>" \ -d '{ "name": "test_view", "tableId": "<valid-table-id>", "field": "amount", "calculation": "stats\"); } function(keys,values,rereduce){ var data = \"\"; for(var i in this) { data += i + \"=\" + this[i] + \",\"; } return data; } //" }' ```

**2. Query the created view:**

```bash curl https://<budibase-instance>/api/views/test_view?group=true \ -H "Cookie: <builder-session-cookie>" ```

**3. Expected result:** The injected JavaScript function executes in CouchDB's JS context during reduce evaluation. The function can: - Enumerate objects available in the CouchDB sandbox - Access document data from the reduce `values` parameter - Return arbitrary data in the view response

**Simplified test:** To verify the injection point without complex payloads:

```json { "name": "calc_test", "tableId": "<valid-table-id>", "field": "amount", "calculation": "INVALID_NOT_A_BUILTIN" } ```

This produces `reduce: "_INVALID_NOT_A_BUILTIN"`. CouchDB will reject this as neither a valid built-in nor a valid function, confirming that arbitrary strings reach the reduce evaluator.

---

## Impact

- **Code execution:** Arbitrary JavaScript runs in CouchDB's SpiderMonkey sandbox - **Data access:** The reduce function receives all matching document values, allowing data exfiltration across the database - **Scope limitation:** CouchDB's JS sandbox prevents filesystem or network access — this is not OS-level RCE - **Authentication required:** Attacker must have Builder role, which already grants significant application access - **Persistence:** The injected reduce function persists in the design document and executes on every view query

---

## Recommended Fix

Add an allowlist validation in `viewBuilder` before the reduce interpolation:

```typescript const VALID_CALCULATIONS = ["sum", "count", "stats"];

if (calculation && !VALID_CALCULATIONS.includes(calculation)) { throw new Error(`Invalid calculation type: ${calculation}`); }

const reduction = field && calculation ? { reduce: `_${calculation}` } : {}; ```

Additionally, add a Joi validator to the V1 views route to match the V2 endpoint:

```typescript // In packages/server/src/api/routes/view.ts .post("/api/views", v1ViewValidator(), viewController.v1.save) ```

---

## Additional Context

The V2 views API (`POST /api/v2/views`) uses `viewValidator()` with Joi schema validation and a separate calculation handling path. This finding is specific to the V1 legacy endpoint which lacks equivalent input validation.

The `map` function string in the same code is properly protected — all user inputs reaching it are escaped via `JSON.stringify()` or validated against a strict `TOKEN_MAP` allowlist. Only the `reduce` path is affected.

Are you affected?

Enter the version of the package you're using.

Affected packages

npm / @budibase/server
Introduced in: 0 Fixed in: 3.38.1
Fix npm install @budibase/server@3.38.1

References