VYPR
Critical severity9.9NVD Advisory· Published May 27, 2026· Updated May 27, 2026

CVE-2026-46425

CVE-2026-46425

Description

Budibase is an open-source low-code platform. Prior to 3.38.2, packages/worker/src/api/routes/global/scim.ts attaches only two middlewares to the SCIM router: requireSCIM (checks the Enterprise feature flag and SCIM config) and doInScimContext (sets the SCIM request context). There is no role check. Any authenticated user who reaches the worker (BASIC role, workspace-scoped builder, anyone) can call SCIM endpoints and CRUD every user and group in the tenant. This vulnerability is fixed in 3.38.2.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Missing role authorization on SCIM endpoints in Budibase before 3.38.2 allows any authenticated user to CRUD all tenant users and groups.

Vulnerability

In Budibase versions prior to 3.38.2, the SCIM router at packages/worker/src/api/routes/global/scim.ts only applies the requireSCIM and doInScimContext middlewares, which check the Enterprise feature flag and SCIM configuration but do not enforce any role-based authorization. This means that any authenticated user—including those with the BASIC role or workspace-scoped builders—can access all SCIM endpoints and perform CRUD operations on every user and group in the tenant [1]. The non-SCIM counterparts (e.g., /api/global/groups) correctly enforce auth.adminOnly or auth.builderOrAdmin, making the SCIM routes the missing authorization half [1].

Exploitation

An attacker needs only a valid authenticated session for any user account that can reach the worker service (BASIC role, builder role, or any other authenticated role). No special admin privileges are required. The attacker can then send HTTP requests to the SCIM endpoints (e.g., GET /api/global/scim/v2/users, POST /api/global/scim/v2/users, PATCH /api/global/scim/v2/users/:id, DELETE /api/global/scim/v2/users/:id, and similar routes for groups) to enumerate, create, update, or delete users and groups within the same tenant [1].

Impact

Successful exploitation allows the attacker to fully manage all tenant users and groups, including creating new administrative accounts, modifying existing user roles, deleting users, and altering group membership. This leads to a complete compromise of tenant user administration, potentially enabling privilege escalation to administrative roles and full control over the application [1].

Mitigation

The vulnerability is fixed in Budibase version 3.38.2, released on or around 2026-05-27 [2]. The fix adds the auth.adminOnly middleware to SCIM routes, matching the authorization level of the non-SCIM group endpoints [1][2]. All users should upgrade to version 3.38.2 or later immediately. No workaround is available for earlier versions beyond restricting network access to the worker service or disabling SCIM feature if not in use.

AI Insight generated on May 27, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2
  • Budibase/Budibasereferences2 versions
    (expand)+ 1 more
    • (no CPE)
    • (no CPE)range: <3.38.2

Patches

2
3bec06c55489

Require admin access for SCIM routes

https://github.com/Budibase/budibasePeter ClementMay 11, 2026Fixed in 3.38.2via llm-release-walk
2 files changed · +49 1
  • packages/worker/src/api/routes/global/scim.ts+2 0 modified
    @@ -1,4 +1,5 @@
     import Router from "@koa/router"
    +import { auth } from "@budibase/backend-core"
     import { middleware as proMiddleware } from "@budibase/pro"
     import { Feature } from "@budibase/types"
     import * as userController from "../../controllers/global/scim/users"
    @@ -10,6 +11,7 @@ const router: Router = new Router({
     
     router.use(proMiddleware.requireSCIM)
     router.use(proMiddleware.doInScimContext)
    +router.use(auth.adminOnly)
     
     router.get("/users", userController.get)
     router.get("/users/:id", proMiddleware.scimUserOnly("id"), userController.find)
    
  • packages/worker/src/api/routes/global/tests/scim.spec.ts+47 1 modified
    @@ -8,8 +8,14 @@ import {
       ScimUpdateRequest,
       ScimUserResponse,
     } from "@budibase/types"
    +import {
    +  context,
    +  db as dbCore,
    +  encryption,
    +  events,
    +  utils,
    +} from "@budibase/backend-core"
     import { TestConfiguration } from "../../../../tests"
    -import { events } from "@budibase/backend-core"
     
     jest.setTimeout(30000)
     
    @@ -28,6 +34,33 @@ describe("scim", () => {
     
       const config = new TestConfiguration()
     
    +  async function createAPIKeyForUser(userId: string) {
    +    const apiKey = encryption.encrypt(
    +      `${config.tenantId}${dbCore.SEPARATOR}${utils.newid()}`
    +    )
    +
    +    await config.doInTenant(async () => {
    +      const db = context.getGlobalDB()
    +      await db.put({
    +        _id: dbCore.generateDevInfoID(userId),
    +        userId,
    +        apiKey,
    +      })
    +    })
    +
    +    return apiKey
    +  }
    +
    +  async function withAPIKey<T>(apiKey: string, fn: () => Promise<T>) {
    +    const previousAPIKey = config.apiKey
    +    config.apiKey = apiKey
    +    try {
    +      return await fn()
    +    } finally {
    +      config.apiKey = previousAPIKey
    +    }
    +  }
    +
       const unauthorisedTests = (fn: (...params: any) => Promise<any>) => {
         describe("unauthorised calls", () => {
           it("unauthorised calls are not allowed", async () => {
    @@ -82,6 +115,19 @@ describe("scim", () => {
       })
     
       describe("/api/global/scim/v2/users", () => {
    +    describe("authorisation", () => {
    +      it("returns 403 when a non-admin user API key calls SCIM", async () => {
    +        const user = await config.createUser()
    +        const apiKey = await createAPIKeyForUser(user._id!)
    +
    +        const response = await withAPIKey(apiKey, () =>
    +          config.api.scimUsersAPI.get({ expect: 403 })
    +        )
    +
    +        expect(response).toEqual(config.adminOnlyResponse())
    +      })
    +    })
    +
         describe("GET /api/global/scim/v2/users", () => {
           const getScimUsers = config.api.scimUsersAPI.get
     
    
b82475752cb7

Invalidate user cache on bulk update

https://github.com/Budibase/budibasePeter ClementMay 11, 2026Fixed in 3.38.2via llm-release-walk
2 files changed · +31 1
  • packages/backend-core/src/users/db.ts+3 1 modified
    @@ -227,7 +227,9 @@ export class UserDB {
       }
     
       static async bulkUpdate(users: User[]) {
    -    return await usersCore.bulkUpdateGlobalUsers(users)
    +    const response = await usersCore.bulkUpdateGlobalUsers(users)
    +    await Promise.all(users.map(user => cache.user.invalidateUser(user._id!)))
    +    return response
       }
     
       static async save(user: User, opts: SaveUserOpts = {}): Promise<User> {
    
  • packages/backend-core/src/users/test/db.spec.ts+28 0 modified
    @@ -1,6 +1,7 @@
     import { BulkUserCreated, User, UserGroup, UserStatus } from "@budibase/types"
     import { DBTestConfiguration, generator, structures } from "../../../tests"
     import * as accounts from "../../accounts"
    +import * as cache from "../../cache"
     import { getGlobalDB } from "../../context"
     import { withEnv } from "../../environment"
     import { UserDB } from "../db"
    @@ -42,6 +43,33 @@ describe("UserDB", () => {
         groups.getDefaultGroup.mockResolvedValue(undefined)
       })
     
    +  describe("bulkUpdate", () => {
    +    it("invalidates cached user data for updated users", async () => {
    +      await config.doInTenant(async () => {
    +        const user = await db.save(
    +          structures.users.user({
    +            email: generator.email({}),
    +            firstName: "Original",
    +            tenantId: config.getTenantId(),
    +          })
    +        )
    +
    +        await cache.user.getUser({
    +          userId: user._id!,
    +          tenantId: config.getTenantId(),
    +        })
    +
    +        await db.bulkUpdate([{ ...user, firstName: "Updated" }])
    +
    +        const refreshedUser = await cache.user.getUser({
    +          userId: user._id!,
    +          tenantId: config.getTenantId(),
    +        })
    +        expect(refreshedUser.firstName).toBe("Updated")
    +      })
    +    })
    +  })
    +
       describe("save", () => {
         describe("create", () => {
           it("creating a new user will persist it", async () => {
    

Vulnerability mechanics

Root cause

"Missing role-based authorization check on the SCIM router allows any authenticated user to call admin-level SCIM endpoints."

Attack vector

An authenticated user with any role (including BASIC app users) who reaches the worker can call SCIM endpoints at `/api/global/scim/v2/*` because the router lacks a role-based authorization check [ref_id=1]. The worker's global auth middleware only enforces `isAuthenticated`, so any logged-in tenant user passes [ref_id=1]. An attacker can list all users (PII leakage), create new SCIM-synced users, change existing users' emails (leading to account takeover via password reset), deactivate or delete users including admins, and modify group membership to inherit group-assigned roles [ref_id=1].

Affected code

The SCIM router in `packages/worker/src/api/routes/global/scim.ts` attaches only `requireSCIM` (feature-flag gate) and `doInScimContext` (request-context setter) as middlewares. No role-check middleware such as `auth.adminOnly` or `auth.builderOrAdmin` is applied, unlike the non-SCIM equivalents at `packages/worker/src/api/routes/global/groups.ts` which enforce `auth.adminOnly` on every mutating route [ref_id=1].

What the fix does

The fix adds `auth.adminOnly` as a middleware on the SCIM router, placed alongside `requireSCIM` and `doInScimContext` [ref_id=1]. This single line closes every SCIM route to non-admin users, matching the authorization pattern already used by the non-SCIM group and user routers [ref_id=1]. The patch is in commit `3bec06c5548969af4bd1e770b7dde1566ffdb942` [patch_id=2725535].

Preconditions

  • configThe tenant must be an Enterprise tenant with SCIM feature enabled
  • authAttacker must have a valid authenticated session (any role, including BASIC)
  • networkAttacker must be able to reach the worker service over the network

Reproduction

Step 1: Log in as a BASIC user and capture the CSRF token from `/api/global/self`. Step 2: List all SCIM users via `GET /api/global/scim/v2/users` with the CSRF token and `Accept: application/scim+json` header. Step 3: Create a new user via `POST /api/global/scim/v2/users` with a SCIM JSON body. Step 4: Optionally PATCH a user's email for account takeover, DELETE to deactivate, or POST to `/groups` to manipulate group membership [ref_id=1].

Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

2

News mentions

0

No linked articles in our index yet.