Every CVE Search/ Get API key

CVE-2026-73305

Budibase: Privilege escalation via public role assignment API missing app-level authorization

8.8 High

Budibase is an open-source low-code platform. Prior to 3.39.24, POST /api/public/v1/roles/assign called validateGlobalRoleUpdate without checking appBuilder.appId or role.appId in packages/server/src/api/controllers/public/globalRoleValidation.ts. An app-scoped builder could scope the request to an app they control and then grant themselves builder access or an arbitrary role in another app, exposing that app data, datasource configuration, and automations. This issue is fixed in version 3.39.24.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
Low
Privileges needed
Low
User interaction
None

What an attacker can affect

Confidentiality
High
Integrity
High
Availability
High

Exploit likelihood

0.52%Up 0.2 points since Aug 14, 2026
90%50%10%1%

What each source reports

CVE List Score used 8.8

Budibase: Privilege escalation via public role assignment API missing app-level authorization

Budibase is an open-source low-code platform. Prior to 3.39.24, POST /api/public/v1/roles/assign called validateGlobalRoleUpdate without checking appBuilder.appId or role.appId in packages/server/src/api/controllers/public/globalRoleValidation.ts. An app-scoped builder could scope the request to an app they control and then grant themselves builder access or an arbitrary role in another app, exposing that app data, datasource configuration, and automations. This issue is fixed in version 3.39.24.

Record
CVE-2026-73305
CVSS 3.1
8.8 HighCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Weakness
CWE-269, CWE-862, CWE-863
Updated
2026-11-02
EUVDEUVD-2026-58528 8.8

Budibase is an open-source low-code platform. Prior to 3.39.24, POST /api/public/v1/roles/assign called validateGlobalRoleUpdate without checking appBuilder.appId or role.appId in packages/server/src/api/controllers/public/globalRoleValidation.ts. An app-scoped builder could scope the request to an app they control and then grant themselves builder access or an arbitrary role in another app, exposing that app data, datasource configuration, and automations. This issue is fixed in version 3.39.24.

Record
EUVD-2026-58528
CVSS 3.1
8.8 HighCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Updated
2026-11-02
GitHub AdvisoriesGHSA-j9fc-w3mr-x6mv 8.8

Budibase: Privilege escalation via public role assignment API missing app-level authorization

Summary

Budibase 3.39.19 (commit 03fbabae4) is affected by a privilege-escalation / missing-authorization flaw in the public role-assignment API. An app-scoped builder (a user who builds only specific apps — user.builder.apps = [appA], not a global builder or admin) can grant themselves builder access to ANY other app in the tenant, or assign themselves/any user an arbitrary data-plane role (e.g. ADMIN) in any app, by calling POST /api/public/v1/roles/assign. The endpoint only authorizes the two global flags (admin, builder); the per-app appBuilder and role:{appId,roleId} grant vectors are passed to the backend without any authorization check that the caller controls the target app. Reproduced in a local authorized lab using the verbatim authorization-gate and SDK logic.

This is an incomplete fix of the role-assignment hardening in commit d63d1d9054 ("Inline public user global role validation"), which only ever validated the global flags.

Details

The issue is caused by authorization being implemented as a flag-level allowlist (admin/builder) instead of validating the scope the caller is granting.

Relevant code paths:

  • packages/server/src/api/controllers/public/globalRoleValidation.ts — validateGlobalRoleUpdate(ctx, roleUpdate) only checks roleUpdate.admin (requires isAdmin) and roleUpdate.builder (requires isGlobalBuilder). The GlobalRoleUpdate interface declares only { builder?, admin? }; appBuilder and role are not referenced.
  • packages/server/src/api/controllers/public/roles.ts — assign() does const { userIds, ...assignmentProps } = ctx.request.body; validateGlobalRoleUpdate(ctx, assignmentProps); await sdk.publicApi.roles.assign(userIds, assignmentProps). The appBuilder/role props pass through unvalidated.
  • packages/pro/src/sdk/publicApi/roles.ts — assign(): for opts.appBuilder it sets user.builder = { apps: existing.concat([getProdWorkspaceID(opts.appBuilder.appId)]) }; for opts.role it sets user.roles[getProdWorkspaceID(opts.role.appId)] = opts.role.roleId. No check that the caller builds the target app, and userIds is an arbitrary list (bulkGet). The only gate is the isExpandedPublicApiEnabled() license check.
  • packages/server/src/api/routes/public/index.ts — applyAdminRoutes(roleEndpoints) attaches only middleware.builderOrAdmin (no publicApi, no authorized(PermissionType.USER, …)).
  • packages/backend-core/src/middleware/builderOrAdmin.ts — with a workspaceId present, it only requires isBuilder(ctx.user, workspaceId). The attacker sets x-budibase-app-id to their own app (appA), so the gate passes.
  • packages/backend-core/src/middleware/builderOnly.ts — on the worker, POST /api/global/self/api_key only requires hasBuilderPermissions(ctx.user), which is true for app-scoped builders, so the attacker can self-issue a public API key.
  • packages/shared-core/src/sdk/documents/users.ts — isGlobalBuilder is false for app-scoped builders (so the global builder flag is correctly blocked), while isBuilder(user, appA) and hasBuilderPermissions(user) are true.

Attack flow:

  1. Attacker is an app-scoped builder of appA only (no global builder/admin).
  2. POST /api/global/self/api_key (worker) — passes builderOnly via hasBuilderPermissions → attacker obtains a public API key.
  3. POST /api/public/v1/roles/assign with header x-budibase-app-id: <appA prod id> and body {"userIds":["<self>"],"appBuilder":{"appId":"<appB>"}} — builderOrAdmin passes (isBuilder(user, appA)), validateGlobalRoleUpdate ignores appBuilder, the SDK pushes appB into user.builder.apps.
  4. Attacker is now a builder of appB (and, via the role vector, can set any data-role such as ADMIN in any app).

Security boundary crossed:

  • Before: builder of appA only.
  • After: builder of appB (and any other app) → read/modify all rows, read datasource configs…
Record
GHSA-j9fc-w3mr-x6mv
CVSS 3.1
8.8 HighCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Weakness
CWE-269, CWE-862, CWE-863
Updated
2026-11-02
NVD 8.8

Budibase is an open-source low-code platform. Prior to 3.39.24, POST /api/public/v1/roles/assign called validateGlobalRoleUpdate without checking appBuilder.appId or role.appId in packages/server/src/api/controllers/public/globalRoleValidation.ts. An app-scoped builder could scope the request to an app they control and then grant themselves builder access or an arbitrary role in another app, exposing that app data, datasource configuration, and automations. This issue is fixed in version 3.39.24.

Record
CVE-2026-73305
CVSS 3.1
8.8 HighCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Weakness
CWE-269, CWE-862, CWE-863
Updated
2026-11-02
OSV 8.8

Budibase: Privilege escalation via public role assignment API missing app-level authorization

Budibase is an open-source low-code platform. Prior to 3.39.24, POST /api/public/v1/roles/assign called validateGlobalRoleUpdate without checking appBuilder.appId or role.appId in packages/server/src/api/controllers/public/globalRoleValidation.ts. An app-scoped builder could scope the request to an app they control and then grant themselves builder access or an arbitrary role in another app, exposing that app data, datasource configuration, and automations. This issue is fixed in version 3.39.24.

Record
CVE-2026-73305
CVSS 3.1
8.8 HighCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Weakness
CWE-269, CWE-862, CWE-863
Updated
2026-11-02
OSVGHSA-j9fc-w3mr-x6mv 8.8

Budibase: Privilege escalation via public role assignment API missing app-level authorization

Summary

Budibase 3.39.19 (commit 03fbabae4) is affected by a privilege-escalation / missing-authorization flaw in the public role-assignment API. An app-scoped builder (a user who builds only specific apps — user.builder.apps = [appA], not a global builder or admin) can grant themselves builder access to ANY other app in the tenant, or assign themselves/any user an arbitrary data-plane role (e.g. ADMIN) in any app, by calling POST /api/public/v1/roles/assign. The endpoint only authorizes the two global flags (admin, builder); the per-app appBuilder and role:{appId,roleId} grant vectors are passed to the backend without any authorization check that the caller controls the target app. Reproduced in a local authorized lab using the verbatim authorization-gate and SDK logic.

This is an incomplete fix of the role-assignment hardening in commit d63d1d9054 ("Inline public user global role validation"), which only ever validated the global flags.

Details

The issue is caused by authorization being implemented as a flag-level allowlist (admin/builder) instead of validating the scope the caller is granting.

Relevant code paths:

  • packages/server/src/api/controllers/public/globalRoleValidation.ts — validateGlobalRoleUpdate(ctx, roleUpdate) only checks roleUpdate.admin (requires isAdmin) and roleUpdate.builder (requires isGlobalBuilder). The GlobalRoleUpdate interface declares only { builder?, admin? }; appBuilder and role are not referenced.
  • packages/server/src/api/controllers/public/roles.ts — assign() does const { userIds, ...assignmentProps } = ctx.request.body; validateGlobalRoleUpdate(ctx, assignmentProps); await sdk.publicApi.roles.assign(userIds, assignmentProps). The appBuilder/role props pass through unvalidated.
  • packages/pro/src/sdk/publicApi/roles.ts — assign(): for opts.appBuilder it sets user.builder = { apps: existing.concat([getProdWorkspaceID(opts.appBuilder.appId)]) }; for opts.role it sets user.roles[getProdWorkspaceID(opts.role.appId)] = opts.role.roleId. No check that the caller builds the target app, and userIds is an arbitrary list (bulkGet). The only gate is the isExpandedPublicApiEnabled() license check.
  • packages/server/src/api/routes/public/index.ts — applyAdminRoutes(roleEndpoints) attaches only middleware.builderOrAdmin (no publicApi, no authorized(PermissionType.USER, …)).
  • packages/backend-core/src/middleware/builderOrAdmin.ts — with a workspaceId present, it only requires isBuilder(ctx.user, workspaceId). The attacker sets x-budibase-app-id to their own app (appA), so the gate passes.
  • packages/backend-core/src/middleware/builderOnly.ts — on the worker, POST /api/global/self/api_key only requires hasBuilderPermissions(ctx.user), which is true for app-scoped builders, so the attacker can self-issue a public API key.
  • packages/shared-core/src/sdk/documents/users.ts — isGlobalBuilder is false for app-scoped builders (so the global builder flag is correctly blocked), while isBuilder(user, appA) and hasBuilderPermissions(user) are true.

Attack flow:

  1. Attacker is an app-scoped builder of appA only (no global builder/admin).
  2. POST /api/global/self/api_key (worker) — passes builderOnly via hasBuilderPermissions → attacker obtains a public API key.
  3. POST /api/public/v1/roles/assign with header x-budibase-app-id: <appA prod id> and body {"userIds":["<self>"],"appBuilder":{"appId":"<appB>"}} — builderOrAdmin passes (isBuilder(user, appA)), validateGlobalRoleUpdate ignores appBuilder, the SDK pushes appB into user.builder.apps.
  4. Attacker is now a builder of appB (and, via the role vector, can set any data-role such as ADMIN in any app).

Security boundary crossed:

  • Before: builder of appA only.
  • After: builder of appB (and any other app) → read/modify all rows, read datasource configs…
Record
GHSA-j9fc-w3mr-x6mv
CVSS 3.1
8.8 HighCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Weakness
CWE-269, CWE-862, CWE-863
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
@budibase/server
npm
through 3.38.1 None known

History

History
RecordedChangedSources
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-73305 is a high vulnerability (CVSS 8.8) in Budibase, published July 24, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.52%, the 44th percentile. No source names a fixed version.

References

This page doesn't list everything

Left out here: the rest of 2 source descriptions. The JSON record has them.

MoveEnter Open