Overview

cross-app builder privilege escalation in budibase

September 13, 2026
3 min read
share

TL;DR

Budibase before 3.41.3 let an authenticated app-scoped builder grant builder access to apps they don’t control, through the public user create/update API.

  • CVE-2026-82240 (High, CVSS 8.1, CWE-862)
  • A builder for App A could give themselves (or anyone) builder access to App B
  • The fix: validate app-scoped role assignments per target app, patched in 3.41.3

One note on attribution, because the record matters: I reported this on July 6 and I’m credited as reporter on the advisory. CVE-2026-82240 was assigned through VulnCheck’s report of the same bug, published August 28, after mine. One vulnerability gets one CVE ID, so GitHub dedupes to a single number. Two independent finders, mine first.

The bug

Budibase has a concept of app-scoped builders: a user who can build App A but has no power over App B. The public API (PUT /api/public/v1/users/:userId and POST /api/public/v1/users) accepts a builder.apps field in the request body.

The problem: the vulnerable path calls createUpdateResponse(), which validates global role updates just fine, but app-scoped role assignments like builder.apps never go through that validation at all. When expanded public API behavior is enabled, nothing strips or rejects the field, and the worker-side user save happily persists it.

So the authorization model had a hole exactly shaped like the feature: global roles checked, app-scoped roles not.

Attacker model

  • Authenticated
  • A builder for App A (nothing more)
  • Allowed to use App A’s public API key
  • Not a global admin, not a global builder, not a builder for App B

Proof of concept

As the App A builder, update any user (including yourself) and slip in builder access for App B:

PUT /api/public/v1/users/{targetUserId}
x-budibase-api-key: <attacker_public_api_key>
x-budibase-app-id: <app_a_id>
Cookie: <attacker_app_builder_session>
Content-Type: application/json
{
"_id": "{targetUserId}",
"email": "target@example.com",
"roles": {},
"builder": {
"apps": ["app_target_b"]
}
}

Response comes back with the access persisted:

{
"builder": {
"apps": ["app_target_b"]
}
}

I confirmed this end to end with a temporary Jest test against Budibase 3.39.26 (removed after validation):

  • Basic non-builder user sending the same request: 403
  • App A builder sending it: 200, and the target user can now perform builder-only actions against App B

The negative control matters here. The same harness correctly denied a basic user, so this isn’t a test setup artifact: the endpoint genuinely distinguishes “no access” from “builder access” and just never asks the second question about which app.

Impact

Builder access in Budibase is not a vanity role. It covers the target app’s design and data plane: screens, automations, queries, datasource-backed actions. A low-privileged builder (or a compromised builder account) could silently expand into any app in the same tenant.

CVSS 3.1 vector: AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N: network-reachable, low complexity, no user interaction, high confidentiality and integrity impact.

The fix

Patched in Budibase 3.41.3. Conceptually the fix is what you’d expect: reject or strip app-scoped role fields like builder.apps from public user create/update requests unless the caller is allowed to assign builder access for every target app ID, not just the app in the request context.

The broader lesson, which I keep running into across projects: when an endpoint accepts a nested object, every level of that object needs its own authorization check. Validating the top level (roles) while passing the nested level (builder.apps) straight through to the persistence layer is the same shape as half the IDORs I write about.

I also suggested maintaining an explicit allowlist of user fields for the public API instead of letting role-bearing internal fields flow through to the worker-side save.

Timeline

  • Jul 6, 2026: Opened the private advisory, credited as reporter
  • Jul 22, 2026: Accepted by the Budibase team, CVE requested
  • Aug 28, 2026: CVE-2026-82240 published (assigned via VulnCheck’s independent report of the same bug, which came after mine; one bug, one ID)
  • Sep 2026: Advisory reviewed and published, fix in 3.41.3

References

Related posts