{"id":"GHSA-6vp2-6r7m-2jvx","revision":null,"records":[{"revision":"d64247a6d8f14800a22d2859c0301df9","observedAt":"2026-10-23T15:48:27.903+00:00","statement":{"source":"cve-program","id":"CVE-2026-46424","aliases":[],"title":"Budibase: Missing Cache Invalidation on Public API Role Unassignment Allows Revoked Users to Retain Privileges for Up to 1 Hour","description":"Budibase is an open-source low-code platform. Prior to 3.38.2, 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. This vulnerability is fixed in 3.38.2.","state":0,"publishedAt":"2026-05-27T17:05:21.894+00:00","modifiedAt":"2026-05-28T14:08:42.106+00:00","affected":[{"ecosystem":"Vendor product","name":"budibase","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"Budibase\",\n                    \"product\": \"budibase\",\n                    \"versions\": [\n                        {\n                            \"version\": \"< 3.38.2\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:N","score":4.2,"severity":"MEDIUM"}],"references":["https://github.com/Budibase/budibase/security/advisories/GHSA-6vp2-6r7m-2jvx","https://github.com/Budibase/budibase/releases/tag/3.38.2"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-46424","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-46424","identifiers":["CVE-2026-46424"]},"retained":false,"historicalAffected":[]},{"revision":"9981454c917e456c89466f11b805c60d","observedAt":"2026-11-13T13:05:54.496+00:00","statement":{"source":"euvd","id":"EUVD-2026-32597","aliases":["CVE-2026-46424","GHSA-6vp2-6r7m-2jvx"],"title":"Budibase is an open-source low-code platform. Prior to 3.38.2, 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 entri…","description":"Budibase is an open-source low-code platform. Prior to 3.38.2, 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. This vulnerability is fixed in 3.38.2.","state":0,"publishedAt":null,"modifiedAt":null,"affected":[],"metrics":[{"system":"EUVD CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:N","score":4.2,"severity":null}],"references":["https://github.com/Budibase/budibase/security/advisories/GHSA-6vp2-6r7m-2jvx","https://github.com/Budibase/budibase/releases/tag/3.38.2"],"sourceUrl":"https://euvd.enisa.europa.eu/vulnerability/EUVD-2026-32597","license":"ENISA EUVD · source terms and attribution apply","enrichment":{"sourceStatus":null,"weaknesses":[],"productCriteria":[],"sourceDates":{"datePublished":"May 27, 2026, 5:05:21 PM","dateUpdated":"May 28, 2026, 2:08:42 PM"},"relatedAdvisories":[],"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-32597","identifiers":["EUVD-2026-32597","CVE-2026-46424","GHSA-6vp2-6r7m-2jvx"]},"retained":false,"historicalAffected":[]},{"revision":"e7fa61d1ec5f48e2873234505736d520","observedAt":"2026-10-25T12:17:14.897+00:00","statement":{"source":"nvd","id":"CVE-2026-46424","aliases":[],"title":"Budibase is an open-source low-code platform. Prior to 3.38.2, 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 entri…","description":"Budibase is an open-source low-code platform. Prior to 3.38.2, 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. This vulnerability is fixed in 3.38.2.","state":0,"publishedAt":"2026-05-27T18:16:26.15+00:00","modifiedAt":"2026-06-17T10:53:39.61+00:00","affected":[],"metrics":[{"system":"CVSS 3.1 · security-advisories@github.com · Secondary","vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:N","score":4.2,"severity":"MEDIUM"}],"references":["https://github.com/Budibase/budibase/releases/tag/3.38.2","https://github.com/Budibase/budibase/security/advisories/GHSA-6vp2-6r7m-2jvx"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-46424","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Deferred","weaknesses":["CWE-269"],"productCriteria":[],"sourceDates":{"published":"2026-05-27T18:16:26.150","lastModified":"2026-06-17T10:53:39.610"},"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-46424","identifiers":["CVE-2026-46424"]},"retained":false,"historicalAffected":[]},{"revision":"24e499d72e154bfe8b1ea61f747ec72b","observedAt":"2026-10-24T13:17:17.793+00:00","statement":{"source":"osv","id":"GHSA-6vp2-6r7m-2jvx","aliases":["CVE-2026-46424"],"title":"Budibase: Missing Cache Invalidation on Public API Role Unassignment Allows Revoked Users to Retain Privileges for Up to 1 Hour","description":"## Summary\n\nThe 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.\n\n## Details\n\nThe root cause is an inconsistency between the `UserDB.save()` and `UserDB.bulkUpdate()` code paths.\n\n**Vulnerable path** — `packages/pro/src/sdk/publicApi/roles.ts:49-75`:\n```typescript\nexport async function unAssign(userIds: string[], opts: AssignmentOpts) {\n  // ... modifies user objects: deletes roles, admin, builder ...\n  await userDB.bulkUpdate(users)  // line 74\n}\n```\n\n`bulkUpdate` delegates to `bulkUpdateGlobalUsers()` at `packages/backend-core/src/users/users.ts:82-85`:\n```typescript\nexport async function bulkUpdateGlobalUsers(users: User[]) {\n  const db = getGlobalDB()\n  return (await db.bulkDocs(users)) as BulkDocsResponse\n}\n```\n\nThis writes directly to CouchDB with **no cache invalidation**.\n\n**Correct path** — `packages/backend-core/src/users/db.ts:355` (used by admin UI):\n```typescript\nawait cache.user.invalidateUser(response.id)\n```\n\n**Cache configuration** — `packages/backend-core/src/cache/user.ts:11`:\n```typescript\nconst EXPIRY_SECONDS = 3600  // 1 hour TTL\n```\n\n**Authentication middleware** — `packages/backend-core/src/middleware/authenticated.ts:153-160`:\n```typescript\nuser = await getUser({\n  userId,\n  tenantId: session.tenantId,\n  email: session.email,\n})\n```\n\n`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.\n\nNotably, 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.\n\n## PoC\n\n```bash\n# Prerequisites: Enterprise license, admin API key, a second user with admin role\n\n# Step 1: Confirm user has admin access\ncurl -s -X GET http://localhost:10000/api/global/roles \\\n  -H 'Cookie: budibase:auth=<target-user-session>' \\\n  -H 'x-budibase-app-id: app_xyz'\n# Returns 200 with roles list\n\n# Step 2: Revoke admin role via public API\ncurl -s -X POST http://localhost:10000/api/public/v1/roles/unassign \\\n  -H 'x-budibase-api-key: <admin-api-key>' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"userIds\": [\"<target-user-id>\"], \"admin\": true}'\n# Returns 200 — role removed from CouchDB\n\n# Step 3: Verify DB was updated (admin field removed)\n# (check CouchDB directly - user document no longer has admin: {global: true})\n\n# Step 4: Immediately retry admin endpoint as revoked user\ncurl -s -X GET http://localhost:10000/api/global/roles \\\n  -H 'Cookie: budibase:auth=<target-user-session>' \\\n  -H 'x-budibase-app-id: app_xyz'\n# STILL returns 200 — stale cache serves old admin privileges\n\n# Step 5: Wait for cache expiry (up to 3600 seconds) and retry\n# After cache expires, the request correctly returns 403\n```\n\n## Impact\n\nA 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.\n\nThe impact is bounded by:\n- Requires enterprise license (expanded public API feature)\n- Maximum 1-hour window before cache expires\n- Only affects the public API revocation path; revocations via the admin UI (`UserDB.save()`) invalidate cache correctly\n- The `assign` direction has the inverse issue (newly granted roles are delayed) but this is less security-critical\n\n## Recommended Fix\n\nAdd cache invalidation to `bulkUpdateGlobalUsers` or to the callers that need it. The most targeted fix is in the `unAssign` function:\n\n```typescript\n// packages/pro/src/sdk/publicApi/roles.ts\nimport { cache } from \"@budibase/backend-core\"\n\nexport async function unAssign(userIds: string[], opts: AssignmentOpts) {\n  // ... existing role removal logic ...\n  await userDB.bulkUpdate(users)\n  \n  // Invalidate cache for all affected users\n  await Promise.all(\n    users.map(user => cache.user.invalidateUser(user._id!))\n  )\n}\n```\n\nAlternatively, fix it at the `bulkUpdate` level to prevent future callers from having the same gap:\n\n```typescript\n// packages/backend-core/src/users/db.ts\nstatic async bulkUpdate(users: User[]) {\n  const result = await usersCore.bulkUpdateGlobalUsers(users)\n  await Promise.all(\n    users.map(user => cache.user.invalidateUser(user._id!))\n  )\n  return result\n}\n```\n\nThe same fix should also be applied to the `assign` function in the same file.","state":0,"publishedAt":"2026-05-19T16:30:38+00:00","modifiedAt":"2026-09-10T03:50:45.8923617+00:00","affected":[{"ecosystem":"npm","name":"@budibase/backend-core","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"3.38.2"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:N","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"MODERATE"}],"references":["https://github.com/Budibase/budibase/security/advisories/GHSA-6vp2-6r7m-2jvx","https://nvd.nist.gov/vuln/detail/CVE-2026-46424","https://github.com/Budibase/budibase","https://github.com/Budibase/budibase/releases/tag/3.38.2"],"sourceUrl":"https://github.com/advisories/GHSA-6vp2-6r7m-2jvx","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-6vp2-6r7m-2jvx","identifiers":["GHSA-6vp2-6r7m-2jvx","CVE-2026-46424"]},"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-15T10:02:17.3763497+00:00","complete":true,"records":1781,"lastAttemptAt":"2026-11-15T10:02:17.3763497+00:00","lastSuccessAt":"2026-11-15T10:02:17.6058686+00:00","error":null,"lastVerifiedAt":"2026-11-15T10:02:17.6058701+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-3113592d787b958ba87cf6300a2f439f8836169d49c36cdd65ca959c60a1acfe","complete":true,"records":425784,"lastAttemptAt":"2026-11-15T10:02:16.903149+00:00","lastSuccessAt":"2026-11-15T10:02:16.8893294+00:00","error":null,"lastVerifiedAt":"2026-11-15T10:02:16.903149+00:00","snapshotPublishedAt":"2026-11-15T09:28:12+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-69c365e50f2646d8b2098d8eaf4594a8","complete":false,"records":13821,"lastAttemptAt":"2026-11-15T10:04:29.5449619+00:00","lastSuccessAt":"2026-11-15T10:04:29.6472676+00:00","error":null,"lastVerifiedAt":"2026-11-15T10:04:29.6472676+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"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-15T10:02:17.3763497+00:00","complete":true,"records":1125,"lastAttemptAt":"2026-11-15T10:02:17.3763497+00:00","lastSuccessAt":"2026-11-15T10:02:17.760821+00:00","error":null,"lastVerifiedAt":"2026-11-15T10:02:17.7608224+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-d33b5144615b488f88b7a7390696bc39","complete":true,"records":13833,"lastAttemptAt":"2026-11-15T10:02:36.0431422+00:00","lastSuccessAt":"2026-11-15T10:02:36.038+00:00","error":null,"lastVerifiedAt":"2026-11-15T10:02:36.038+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-15T10:02:17.2383659+00:00","lastSuccessAt":"2026-11-14T13:57:23.1208135+00:00","error":null,"lastVerifiedAt":"2026-11-15T10:02:17.2383659+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-15T10:02:16.9649371+00:00","lastSuccessAt":"2026-11-13T19:57:08.2864435+00:00","error":null,"lastVerifiedAt":"2026-11-15T10:02:16.9649371+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-15T10:02:17.2553329+00:00","lastSuccessAt":"2026-11-14T04:57:03.8279646+00:00","error":null,"lastVerifiedAt":"2026-11-15T10:02:17.2553329+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-15T10:02:17.1321415+00:00","lastSuccessAt":"2026-11-15T05:33:06.8241102+00:00","error":null,"lastVerifiedAt":"2026-11-15T10:02:17.1321415+00:00","snapshotPublishedAt":"2026-11-15T04:33:08+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null}]}