Severity by source
AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:N
Primary rating from GitHub Advisory · only source for this CVE.
CVSS VectorGitHub Advisory
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:N
Lifecycle Timeline
2DescriptionGitHub Advisory
Summary
The public API role unassignment endpoint (POST /api/public/v1/roles/unassign) updates user documents in CouchDB but does not invalidate the corresponding Redis user cache entries. Because the authentication middleware resolves user identity and permissions from this cache (TTL: 3600 seconds), a user whose admin, builder, or app-level roles have been revoked via the public API retains those privileges for up to 1 hour.
Details
The root cause is an inconsistency between the UserDB.save() and UserDB.bulkUpdate() code paths.
Vulnerable path - packages/pro/src/sdk/publicApi/roles.ts:49-75:
export async function unAssign(userIds: string[], opts: AssignmentOpts) {
// ... modifies user objects: deletes roles, admin, builder ...
await userDB.bulkUpdate(users) // line 74
}bulkUpdate delegates to bulkUpdateGlobalUsers() at packages/backend-core/src/users/users.ts:82-85:
export async function bulkUpdateGlobalUsers(users: User[]) {
const db = getGlobalDB()
return (await db.bulkDocs(users)) as BulkDocsResponse
}This writes directly to CouchDB with no cache invalidation.
Correct path - packages/backend-core/src/users/db.ts:355 (used by admin UI):
await cache.user.invalidateUser(response.id)Cache configuration - packages/backend-core/src/cache/user.ts:11:
const EXPIRY_SECONDS = 3600 // 1 hour TTLAuthentication middleware - packages/backend-core/src/middleware/authenticated.ts:153-160:
user = await getUser({
userId,
tenantId: session.tenantId,
email: session.email,
})getUser() reads from Redis cache first; it only falls back to CouchDB on cache miss. After unAssign updates CouchDB without invalidating Redis, every authenticated request continues to use the stale cached user object with the old (revoked) privileges.
Notably, other bulk operations in the codebase handle this correctly - groups.addUsers() and groups.removeUsers() in packages/pro/src/sdk/groups/groups.ts both loop through affected users and call cache.user.invalidateUser() after bulkUpdateGlobalUsers(). The public API roles path was missed.
PoC
# Prerequisites: Enterprise license, admin API key, a second user with admin role
# Step 1: Confirm user has admin access
curl -s -X GET http://localhost:10000/api/global/roles \
-H 'Cookie: budibase:auth=<target-user-session>' \
-H 'x-budibase-app-id: app_xyz'
# Returns 200 with roles list
# Step 2: Revoke admin role via public API
curl -s -X POST http://localhost:10000/api/public/v1/roles/unassign \
-H 'x-budibase-api-key: <admin-api-key>' \
-H 'Content-Type: application/json' \
-d '{"userIds": ["<target-user-id>"], "admin": true}'
# Returns 200 - role removed from CouchDB
# Step 3: Verify DB was updated (admin field removed)
# (check CouchDB directly - user document no longer has admin: {global: true})
# Step 4: Immediately retry admin endpoint as revoked user
curl -s -X GET http://localhost:10000/api/global/roles \
-H 'Cookie: budibase:auth=<target-user-session>' \
-H 'x-budibase-app-id: app_xyz'
# STILL returns 200 - stale cache serves old admin privileges
# Step 5: Wait for cache expiry (up to 3600 seconds) and retry
# After cache expires, the request correctly returns 403Impact
A user whose admin, builder, or app-level roles have been revoked via the public API retains full access to those privileges for up to 1 hour. This is particularly concerning in automated offboarding scenarios where HR/IT systems use the public API to revoke access for terminated employees - the terminated user retains admin/builder access to all applications and data during the cache window.
The impact is bounded by:
- Requires enterprise license (expanded public API feature)
- Maximum 1-hour window before cache expires
- Only affects the public API revocation path; revocations via the admin UI (
UserDB.save()) invalidate cache correctly - The
assigndirection has the inverse issue (newly granted roles are delayed) but this is less security-critical
Recommended Fix
Add cache invalidation to bulkUpdateGlobalUsers or to the callers that need it. The most targeted fix is in the unAssign function:
// packages/pro/src/sdk/publicApi/roles.ts
import { cache } from "@budibase/backend-core"
export async function unAssign(userIds: string[], opts: AssignmentOpts) {
// ... existing role removal logic ...
await userDB.bulkUpdate(users)
// Invalidate cache for all affected users
await Promise.all(
users.map(user => cache.user.invalidateUser(user._id!))
)
}Alternatively, fix it at the bulkUpdate level to prevent future callers from having the same gap:
// packages/backend-core/src/users/db.ts
static async bulkUpdate(users: User[]) {
const result = await usersCore.bulkUpdateGlobalUsers(users)
await Promise.all(
users.map(user => cache.user.invalidateUser(user._id!))
)
return result
}The same fix should also be applied to the assign function in the same file.
AnalysisAI
Missing Redis cache invalidation in Budibase's public API role unassignment endpoint allows users with revoked admin, builder, or app-level privileges to retain full access for up to 1 hour (the hardcoded Redis TTL of 3600 seconds). Affected deployments are Budibase versions prior to 3.38.2 running an enterprise license, where the POST /api/public/v1/roles/unassign endpoint writes revocations to CouchDB but never calls cache.user.invalidateUser(), leaving the authentication middleware to serve stale permissions from Redis. Publicly available exploit code exists within the GHSA-6vp2-6r7m-2jvx advisory; no confirmed active exploitation (not listed in CISA KEV at time of analysis).
Technical ContextAI
Budibase is a low-code application platform that uses CouchDB as its primary user data store and Redis as a user permission cache. The affected npm package is @budibase/backend-core (pkg:npm/@budibase_backend-core). The root cause is CWE-269 (Improper Privilege Management): the bulkUpdateGlobalUsers() function at packages/backend-core/src/users/users.ts:82-85 writes user documents directly to CouchDB via db.bulkDocs() without invoking cache.user.invalidateUser(). The authentication middleware at packages/backend-core/src/middleware/authenticated.ts:153-160 resolves user identity and permissions from Redis first, only falling back to CouchDB on a cache miss. With a TTL of 3600 seconds (packages/backend-core/src/cache/user.ts:11), stale privilege data persists for up to one hour. The flaw is a code-path inconsistency: the admin UI uses UserDB.save() (packages/backend-core/src/users/db.ts:355) which correctly calls cache.user.invalidateUser(), while the public API's unAssign() function at packages/pro/src/sdk/publicApi/roles.ts:49-75 uses userDB.bulkUpdate() which does not. Other bulk operations such as groups.addUsers() and groups.removeUsers() already handle cache invalidation correctly, confirming this was an isolated oversight in the public API role path.
RemediationAI
Vendor-released patch: 3.38.2. Upgrade the @budibase/backend-core package (and the full Budibase deployment) to version 3.38.2 or later, which resolves this issue via PR #18762 ('Invalidate user cache after bulk role updates') as confirmed in the release notes at https://github.com/Budibase/budibase/releases/tag/3.38.2. The vendor advisory is at https://github.com/Budibase/budibase/security/advisories/GHSA-6vp2-6r7m-2jvx. If an immediate upgrade is not feasible, time-sensitive role revocations (such as employee offboarding) should be performed exclusively through the Budibase admin UI rather than the public API, as the admin UI uses the UserDB.save() code path which correctly invalidates the Redis cache. As an emergency compensating control, flushing the Redis cache (e.g., FLUSHDB or Redis service restart) will immediately evict all stale user objects and force re-authentication from CouchDB - note this causes an authentication latency spike and brief service disruption for all active users. Restricting network access to the public API endpoint (/api/public/v1/roles/unassign) to trusted internal IP ranges reduces exposure but does not eliminate the cache-staleness window if the endpoint is still reachable by automation systems.
LiteSpeed User-End cPanel Plugin before 2.4.5 allows privilege escalation (possibly to root), as exploited in the wild i
UAF in Redis 8.2.1 via crafted Lua scripts by authenticated users. EPSS 12.4%. Patch available.
It was discovered, that redis, a persistent key-value database, due to a packaging issue, is prone to a (Debian-specific
Memory Corruption was discovered in the cmsgpack library in the Lua subsystem in Redis before 3.2.12, 4.x before 4.0.10,
Redis is an open source, in-memory database that persists on disk. Versions 8.2.1 and below allow an authenticated user
Redis before 2.8.21 and 3.x before 3.0.2 allows remote attackers to execute arbitrary Lua bytecode via the eval command.
A buffer overflow in Redis 3.2.x prior to 3.2.4 causes arbitrary code execution when a crafted command is sent. Rated cr
Code injection in OneUptime monitoring via custom JS monitor using vm module. PoC and patch available.
In applications using jfinal 4.9.08 and below, there is a deserialization vulnerability when using redis,may be vulnerab
An issue was found in Apache Airflow versions 1.10.10 and below. Rated critical severity (CVSS 9.8), this vulnerability
An Integer Overflow issue was discovered in the struct library in the Lua subsystem in Redis before 3.2.12, 4.x before 4
goanother Another Redis Desktop Manager =<1.6.1 is vulnerable to Cross Site Scripting (XSS) via src/components/Setting.v
Same weakness CWE-269 – Improper Privilege Management
View allSame technique Privilege Escalation
View allShare
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-32597
GHSA-6vp2-6r7m-2jvx