# [CRIT] [GHSA / CRITICAL] CVE-2026-48150: Budibase: Workspace-scoped builder escalates to global admin via /api/public/v1/roles/assign

**Source:** GitHub Security Advisories
**Published:** 2026-06-12
**Article:** https://github.com/advisories/GHSA-6xp4-cf37-ppjh

## Threat Profile

Budibase: Workspace-scoped builder escalates to global admin via /api/public/v1/roles/assign

## Summary

`/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 `a…

## Indicators of Compromise (high-fidelity only)

- **CVE:** `CVE-2026-48150`

## MITRE ATT&CK Techniques

- **T1190** — Exploit Public-Facing Application
- **T1068** — Exploitation for Privilege Escalation
- **T1098** — Account Manipulation
- **T1098.001** — Additional Cloud Credentials

## Kill chain phases observed

_(none detected from narrative keywords)_

## Recommended hunts

### Budibase CVE-2026-48150: POST /api/public/v1/roles/assign with global builder/admin grant in body

`UC_20_1` · phase: **exploit** · confidence: **High** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats `summariesonly` count min(_time) as firstTime max(_time) as lastTime values(Web.http_user_agent) as user_agent values(Web.status) as status values(Web.uri_query) as uri_query from datamodel=Web where Web.url="*/api/public/v1/roles/assign*" AND Web.http_method="POST" by Web.src, Web.user, Web.url, Web.dest | `drop_dm_object_name(Web)` | search uri_query="*\"builder\":true*" OR uri_query="*\"admin\":true*" OR uri_query="*builder%22%3Atrue*" OR uri_query="*admin%22%3Atrue*" | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`
```

### Budibase audit log: builder.global / admin.global granted to user by non-global caller

`UC_20_2` · phase: **actions** · confidence: **High** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats `summariesonly` count min(_time) as firstTime max(_time) as lastTime values(All_Changes.user) as caller values(All_Changes.dest) as dest values(All_Changes.object) as target_user values(All_Changes.command) as command from datamodel=Change where All_Changes.object_category=user All_Changes.change_type=modified All_Changes.action=updated by All_Changes.src, All_Changes.user, All_Changes.object | `drop_dm_object_name(All_Changes)` | search command="*roles/assign*" AND (command="*\"builder\":true*" OR command="*\"admin\":true*") | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`
```

### Budibase: rapid bulk POSTs to /api/public/v1/roles/assign from single source

`UC_20_3` · phase: **actions** · confidence: **Medium** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats `summariesonly` count min(_time) as firstTime max(_time) as lastTime values(Web.url) as urls values(Web.status) as status values(Web.http_user_agent) as user_agents dc(Web.uri_query) as DistinctBodies from datamodel=Web where Web.url="*/api/public/v1/roles/assign*" AND Web.http_method="POST" by Web.src, Web.user, _time span=5m | `drop_dm_object_name(Web)` | where count > 3 | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`
```

### Budibase: API key minted via /api/global/self/api_key then /api/public/v1/roles/assign within 5m

`UC_20_4` · phase: **exploit** · confidence: **High** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats `summariesonly` count min(_time) as keyMintTime from datamodel=Web where Web.url="*/api/global/self/api_key*" AND Web.http_method="GET" by Web.src, Web.user | `drop_dm_object_name(Web)` | join type=inner src, user [| tstats `summariesonly` count min(_time) as assignTime values(Web.url) as urls values(Web.status) as status from datamodel=Web where Web.url="*/api/public/v1/roles/assign*" AND Web.http_method="POST" by Web.src, Web.user | `drop_dm_object_name(Web)` | rename count as assignCount] | eval delaySec = assignTime - keyMintTime | where delaySec >= 0 AND delaySec <= 300 | table keyMintTime, assignTime, delaySec, src, user, assignCount, urls, status
```

### IOC-driven hunts (use shared templates)

These are standard IOC-substitution hunts — the canonical SPL and KQL live once in [`_TEMPLATES.md`](../_TEMPLATES.md), so we don't repeat the same boilerplate on every CVE / hash / network-IOC briefing.

- **Asset exposure — vulnerability matches article CVE(s)** ([template](../_TEMPLATES.md#asset-exposure)) — phase: **recon**, confidence: **High**
  - CVE(s): `CVE-2026-48150`


## Why this matters

Severity classified as **CRIT** based on: CVE present, 5 use case(s) fired, 4 technique(s) inferred. Read the full article for actor attribution, tooling details, and any defanged IOCs in the body that aren't visible in the RSS summary.
