{"id":"GHSA-2wfh-rcwf-wh23","revision":null,"records":[{"revision":"110a3e5d93004545a66002682cd871a9","observedAt":"2026-10-23T15:48:27.903+00:00","statement":{"source":"cve-program","id":"CVE-2026-35214","aliases":[],"title":"Budibase: Path traversal in plugin file upload enables arbitrary directory deletion and file write","description":"Budibase is an open-source low-code platform. Prior to version 3.33.4, the plugin file upload endpoint (POST /api/plugin/upload) passes the user-supplied filename directly to createTempFolder() without sanitizing path traversal sequences. An attacker with Global Builder privileges can craft a multipart upload with a filename containing ../ to delete arbitrary directories via rmSync and write arbitrary files via tarball extraction to any filesystem path the Node.js process can access. This issue has been patched in version 3.33.4.","state":0,"publishedAt":"2026-04-03T15:43:12.426+00:00","modifiedAt":"2026-04-03T16:04:36.168+00:00","affected":[{"ecosystem":"Vendor product","name":"budibase","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"Budibase\",\n                    \"product\": \"budibase\",\n                    \"versions\": [\n                        {\n                            \"version\": \"< 3.33.4\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:N/I:H/A:H","score":8.7,"severity":"HIGH"}],"references":["https://github.com/Budibase/budibase/security/advisories/GHSA-2wfh-rcwf-wh23","https://github.com/Budibase/budibase/pull/18240","https://github.com/Budibase/budibase/commit/6344d06d703660fd05995e61d581593c2349c879","https://github.com/Budibase/budibase/releases/tag/3.33.4"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-35214","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-35214","identifiers":["CVE-2026-35214"]},"retained":false,"historicalAffected":[]},{"revision":"62fc5b2493524bc4a2ce9039c0938b98","observedAt":"2026-11-11T09:30:27.577+00:00","statement":{"source":"euvd","id":"EUVD-2026-18793","aliases":["CVE-2026-35214","GHSA-2wfh-rcwf-wh23"],"title":"Budibase is an open-source low-code platform. Prior to version 3.33.4, the plugin file upload endpoint (POST /api/plugin/upload) passes the user-supplied filename directly to createTempFolder() without sanitizing path traversal sequences. A…","description":"Budibase is an open-source low-code platform. Prior to version 3.33.4, the plugin file upload endpoint (POST /api/plugin/upload) passes the user-supplied filename directly to createTempFolder() without sanitizing path traversal sequences. An attacker with Global Builder privileges can craft a multipart upload with a filename containing ../ to delete arbitrary directories via rmSync and write arbitrary files via tarball extraction to any filesystem path the Node.js process can access. This issue has been patched in version 3.33.4.","state":0,"publishedAt":null,"modifiedAt":null,"affected":[],"metrics":[{"system":"EUVD CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:N/I:H/A:H","score":8.7,"severity":null}],"references":["https://github.com/Budibase/budibase/security/advisories/GHSA-2wfh-rcwf-wh23","https://github.com/Budibase/budibase/pull/18240","https://github.com/Budibase/budibase/commit/6344d06d703660fd05995e61d581593c2349c879","https://github.com/Budibase/budibase/releases/tag/3.33.4"],"sourceUrl":"https://euvd.enisa.europa.eu/vulnerability/EUVD-2026-18793","license":"ENISA EUVD · source terms and attribution apply","enrichment":{"sourceStatus":null,"weaknesses":[],"productCriteria":[],"sourceDates":{"datePublished":"Apr 3, 2026, 3:43:12 PM","dateUpdated":"Apr 3, 2026, 4:04:36 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-18793","identifiers":["EUVD-2026-18793","CVE-2026-35214","GHSA-2wfh-rcwf-wh23"]},"retained":false,"historicalAffected":[]},{"revision":"53b4a827fa3d429c97d393e569d7a67a","observedAt":"2026-10-25T08:17:41.961+00:00","statement":{"source":"nvd","id":"CVE-2026-35214","aliases":[],"title":"Budibase is an open-source low-code platform. Prior to version 3.33.4, the plugin file upload endpoint (POST /api/plugin/upload) passes the user-supplied filename directly to createTempFolder() without sanitizing path traversal sequences. A…","description":"Budibase is an open-source low-code platform. Prior to version 3.33.4, the plugin file upload endpoint (POST /api/plugin/upload) passes the user-supplied filename directly to createTempFolder() without sanitizing path traversal sequences. An attacker with Global Builder privileges can craft a multipart upload with a filename containing ../ to delete arbitrary directories via rmSync and write arbitrary files via tarball extraction to any filesystem path the Node.js process can access. This issue has been patched in version 3.33.4.","state":0,"publishedAt":"2026-04-03T16:16:41.607+00:00","modifiedAt":"2026-10-06T22:10:00.247+00:00","affected":[],"metrics":[{"system":"CVSS 3.1 · security-advisories@github.com · Secondary","vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:N/I:H/A:H","score":8.7,"severity":"HIGH"}],"references":["https://github.com/Budibase/budibase/commit/6344d06d703660fd05995e61d581593c2349c879","https://github.com/Budibase/budibase/pull/18240","https://github.com/Budibase/budibase/releases/tag/3.33.4","https://github.com/Budibase/budibase/security/advisories/GHSA-2wfh-rcwf-wh23"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-35214","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Analyzed","weaknesses":["CWE-22"],"productCriteria":["cpe:2.3:a:budibase:budibase:*:*:*:*:*:*:*:*"],"sourceDates":{"published":"2026-04-03T16:16:41.607","lastModified":"2026-10-06T22:10:00.247"},"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-35214","identifiers":["CVE-2026-35214"]},"retained":false,"historicalAffected":[]},{"revision":"cbb265b50ba14b9a9952c69f7369e3f2","observedAt":"2026-10-24T13:17:17.793+00:00","statement":{"source":"osv","id":"GHSA-2wfh-rcwf-wh23","aliases":["CVE-2026-35214"],"title":"Budibase: Path traversal in plugin file upload enables arbitrary directory deletion and file write","description":"## Summary\n\nThe plugin file upload endpoint (`POST /api/plugin/upload`) passes the user-supplied filename directly to `createTempFolder()` without sanitizing path traversal sequences. An attacker with Global Builder privileges can craft a multipart upload with a filename containing `../` to delete arbitrary directories via `rmSync` and write arbitrary files via tarball extraction to any filesystem path the Node.js process can access.\n\n## Severity\n\n- **Attack Vector:** Network — exploitable via the plugin upload HTTP API\n- **Attack Complexity:** Low — no special conditions; a single crafted multipart request suffices\n- **Privileges Required:** High — requires Global Builder role (`GLOBAL_BUILDER` permission)\n- **User Interaction:** None\n- **Scope:** Changed — the plugin upload feature is scoped to a temp directory, but the traversal escapes to the host filesystem\n- **Confidentiality Impact:** None — the vulnerability enables deletion and writing, not reading\n- **Integrity Impact:** High — attacker can delete arbitrary directories and write arbitrary files via tarball extraction\n- **Availability Impact:** High — recursive deletion of application or system directories causes denial of service\n\n### Severity Rationale\n\n Despite the real filesystem impact, severity is bounded by the requirement for Global Builder privileges (PR:H), which is the highest non-admin role in Budibase. In self-hosted deployments the Global Builder may already have server access, further reducing practical impact. In cloud/multi-tenant deployments the impact is more significant as it could affect the host infrastructure.\n\n## Affected Component\n\n- `packages/server/src/api/controllers/plugin/file.ts` — `fileUpload()` (line 15)\n- `packages/server/src/utilities/fileSystem/filesystem.ts` — `createTempFolder()` (lines 78-91)\n\n## Description\n\n### Unsanitized filename flows into filesystem operations\n\nIn `packages/server/src/api/controllers/plugin/file.ts`, the uploaded file's name is used directly after stripping the `.tar.gz` suffix:\n\n```typescript\n// packages/server/src/api/controllers/plugin/file.ts:8-19\nexport async function fileUpload(file: KoaFile) {\n  if (!file.name || !file.path) {\n    throw new Error(\"File is not valid - cannot upload.\")\n  }\n  if (!file.name.endsWith(\".tar.gz\")) {\n    throw new Error(\"Plugin must be compressed into a gzipped tarball.\")\n  }\n  const path = createTempFolder(file.name.split(\".tar.gz\")[0])\n  await extractTarball(file.path, path)\n\n  return await getPluginMetadata(path)\n}\n```\n\nThe `file.name` originates from the `Content-Disposition` header's `filename` field in the multipart upload, parsed by formidable (via koa-body 4.2.0). Formidable does not sanitize path traversal sequences from filenames.\n\nThe `createTempFolder` function in `packages/server/src/utilities/fileSystem/filesystem.ts` uses `path.join()` which resolves `../` sequences, then performs destructive filesystem operations:\n\n```typescript\n// packages/server/src/utilities/fileSystem/filesystem.ts:78-91\nexport const createTempFolder = (item: string) => {\n  const path = join(budibaseTempDir(), item)\n  try {\n    // remove old tmp directories automatically - don't combine\n    if (fs.existsSync(path)) {\n      fs.rmSync(path, { recursive: true, force: true })\n    }\n    fs.mkdirSync(path)\n  } catch (err: any) {\n    throw new Error(`Path cannot be created: ${err.message}`)\n  }\n\n  return path\n}\n```\n\nThe `budibaseTempDir()` returns `/tmp/.budibase` (from `packages/backend-core/src/objectStore/utils.ts:33`). With a filename like `../../etc/target.tar.gz`, `path.join(\"/tmp/.budibase\", \"../../etc/target\")` resolves to `/etc/target`.\n\n### Inconsistent defenses confirm the gap\n\nThe codebase is aware of the risk in similar paths:\n\n1. **Safe path in `utils.ts`**: The `downloadUnzipTarball` function (for NPM/GitHub/URL plugin sources) generates a random name server-side:\n   ```typescript\n   // packages/server/src/api/controllers/plugin/index.ts:68\n   const name = \"PLUGIN_\" + Math.floor(100000 + Math.random() * 900000)\n   ```\n   This is safe because `name` never contains user input.\n\n2. **Safe path in `objectStore.ts`**: Other uses of `budibaseTempDir()` use UUID-generated names:\n   ```typescript\n   // packages/backend-core/src/objectStore/objectStore.ts:546\n   const outputPath = join(budibaseTempDir(), v4())\n   ```\n\n3. **Sanitization exists but is not applied**: The codebase has `sanitizeKey()` in `objectStore.ts` for sanitizing object store paths, but no equivalent is applied to `createTempFolder`'s input.\n\nThe file upload path is the only caller of `createTempFolder` that passes unsanitized user input.\n\n### Execution chain\n\n1. Authenticated Global Builder sends `POST /api/plugin/upload` with a multipart file whose `Content-Disposition` filename contains path traversal (e.g., `../../etc/target.tar.gz`)\n2. koa-body/formidable parses the upload, setting `file.name` to the raw filename from the header\n3. `controller.upload` → `sdk.plugins.processUploaded()` → `fileUpload(file)`\n4. `.endsWith(\".tar.gz\")` check passes (the suffix is present)\n5. `.split(\".tar.gz\")[0]` extracts `../../etc/target`\n6. `createTempFolder(\"../../etc/target\")` is called\n7. `path.join(\"/tmp/.budibase\", \"../../etc/target\")` resolves to `/etc/target`\n8. `fs.rmSync(\"/etc/target\", { recursive: true, force: true })` — **deletes the target directory recursively**\n9. `fs.mkdirSync(\"/etc/target\")` — **creates a directory at the traversed path**\n10. `extractTarball(file.path, \"/etc/target\")` — **extracts attacker-controlled tarball contents to the traversed path**\n\n## Proof of Concept\n\n```bash\n# Create a minimal tarball with a test file\nmkdir -p /tmp/plugin-poc && echo \"pwned\" > /tmp/plugin-poc/test.txt\ntar czf /tmp/poc-plugin.tar.gz -C /tmp/plugin-poc .\n\n# Upload with a traversal filename targeting /tmp/pwned (non-destructive demo)\ncurl -X POST 'http://localhost:10000/api/plugin/upload' \\\n  -H 'Cookie: <global_builder_session_cookie>' \\\n  -F \"file=@/tmp/poc-plugin.tar.gz;filename=../../tmp/pwned.tar.gz\"\n\n# Result: server executes:\n#   rm -rf /tmp/pwned        (if exists)\n#   mkdir /tmp/pwned\n#   tar xzf <upload> -C /tmp/pwned\n# Verify: ls /tmp/pwned/test.txt\n```\n\n## Impact\n\n- **Arbitrary directory deletion**: `rmSync` with `{ recursive: true, force: true }` deletes any directory the Node.js process can access, including application data directories\n- **Arbitrary file write**: Tarball extraction writes attacker-controlled files to any writable path, potentially overwriting application code, configuration, or system files\n- **Denial of service**: Deleting critical directories (e.g., the application's data directory, node_modules, or system directories) crashes the application\n- **Potential code execution**: In containerized deployments (common for Budibase) where Node.js runs as root, an attacker could overwrite startup scripts or application code to achieve remote code execution on subsequent restarts\n\n## Recommended Remediation\n\n### Option 1: Sanitize at `createTempFolder` (preferred — protects all callers)\n\n```typescript\nimport { join, resolve } from \"path\"\n\nexport const createTempFolder = (item: string) => {\n  const tempDir = budibaseTempDir()\n  const resolved = resolve(tempDir, item)\n\n  // Ensure the resolved path is within the temp directory\n  if (!resolved.startsWith(tempDir + \"/\") && resolved !== tempDir) {\n    throw new Error(\"Invalid path: directory traversal detected\")\n  }\n\n  try {\n    if (fs.existsSync(resolved)) {\n      fs.rmSync(resolved, { recursive: true, force: true })\n    }\n    fs.mkdirSync(resolved)\n  } catch (err: any) {\n    throw new Error(`Path cannot be created: ${err.message}`)\n  }\n\n  return resolved\n}\n```\n\n### Option 2: Sanitize at the upload handler (defense-in-depth)\n\nStrip path components from the filename before use:\n\n```typescript\nimport path from \"path\"\n\nexport async function fileUpload(file: KoaFile) {\n  if (!file.name || !file.path) {\n    throw new Error(\"File is not valid - cannot upload.\")\n  }\n  if (!file.name.endsWith(\".tar.gz\")) {\n    throw new Error(\"Plugin must be compressed into a gzipped tarball.\")\n  }\n  // Strip directory components from the filename\n  const safeName = path.basename(file.name).split(\".tar.gz\")[0]\n  const dir = createTempFolder(safeName)\n  await extractTarball(file.path, dir)\n\n  return await getPluginMetadata(dir)\n}\n```\n\nBoth options should ideally be applied together for defense-in-depth.\n\n## Credit\n\nThis vulnerability was discovered and reported by [bugbunny.ai](https://bugbunny.ai).","state":0,"publishedAt":"2026-04-04T06:04:19+00:00","modifiedAt":"2026-04-04T06:26:31.876982+00:00","affected":[{"ecosystem":"npm","name":"@budibase/server","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"3.33.4"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:N/I:H/A:H","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"HIGH"}],"references":["https://github.com/Budibase/budibase/security/advisories/GHSA-2wfh-rcwf-wh23","https://nvd.nist.gov/vuln/detail/CVE-2026-35214","https://github.com/Budibase/budibase/pull/18240","https://github.com/Budibase/budibase/commit/6344d06d703660fd05995e61d581593c2349c879","https://github.com/Budibase/budibase","https://github.com/Budibase/budibase/releases/tag/3.33.4"],"sourceUrl":"https://github.com/advisories/GHSA-2wfh-rcwf-wh23","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-2wfh-rcwf-wh23","identifiers":["GHSA-2wfh-rcwf-wh23","CVE-2026-35214"]},"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-11T13:57:33.6529555+00:00","complete":true,"records":1779,"lastAttemptAt":"2026-11-11T13:57:33.6529555+00:00","lastSuccessAt":"2026-11-11T13:57:33.8241235+00:00","error":null,"lastVerifiedAt":"2026-11-11T13:57:33.8241245+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-de3b77103708811b6494d2ff7c979037d73ae579742cd1649a5c765137ae5083-50d710c9a9d82d7c4f0dbc882eeaa04edd96b04262f8e1dc90b5d781e709dfb5","complete":true,"records":424128,"lastAttemptAt":"2026-11-11T13:57:32.8474715+00:00","lastSuccessAt":"2026-11-11T13:57:32.6784104+00:00","error":null,"lastVerifiedAt":"2026-11-11T13:57:32.8474715+00:00","snapshotPublishedAt":"2026-11-11T13:27:44+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-f0a4583a21084daab8e852bf5d038b99","complete":false,"records":13759,"lastAttemptAt":"2026-11-11T14:09:33.1310684+00:00","lastSuccessAt":"2026-11-11T14:09:33.127+00:00","error":null,"lastVerifiedAt":"2026-11-11T14:09:33.127+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13569,"expectedIdentifiers":13813,"failedIdentifiers":0},{"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-11T13:57:33.6529555+00:00","complete":true,"records":1097,"lastAttemptAt":"2026-11-11T13:57:33.6529555+00:00","lastSuccessAt":"2026-11-11T13:57:33.8250558+00:00","error":null,"lastVerifiedAt":"2026-11-11T13:57:33.8250561+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-11785b13898646a1917b06b09d2df4a4","complete":true,"records":13753,"lastAttemptAt":"2026-11-11T13:57:34.9144807+00:00","lastSuccessAt":"2026-11-11T13:57:34.909+00:00","error":null,"lastVerifiedAt":"2026-11-11T13:57:34.909+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13813,"expectedIdentifiers":13813,"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-3562b6c78fd1bd0f0b55daf66a0926ae24f65e462df7f6d928caad458fc696bf","complete":true,"records":9947,"lastAttemptAt":"2026-11-11T13:57:33.3950468+00:00","lastSuccessAt":"2026-11-11T04:27:43.6877707+00:00","error":null,"lastVerifiedAt":"2026-11-11T13:57:33.3950468+00:00","snapshotPublishedAt":"2026-11-11T03:32:46+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-456002455e0ab94bb92c95476d4e6d175c104cd594749982afd96c893728ecbc","complete":true,"records":2010,"lastAttemptAt":"2026-11-11T13:57:32.9057946+00:00","lastSuccessAt":"2026-11-11T02:36:58.1276999+00:00","error":null,"lastVerifiedAt":"2026-11-11T13:57:32.9057946+00:00","snapshotPublishedAt":"2026-11-11T01:17:51+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-baa8362b4daf9e5f39d5563775a55935e7b442c0735d33274a921579ce4a0d66","complete":true,"records":3093,"lastAttemptAt":"2026-11-11T13:57:33.6132068+00:00","lastSuccessAt":"2026-11-11T13:00:02.5446881+00:00","error":null,"lastVerifiedAt":"2026-11-11T13:57:33.6132068+00:00","snapshotPublishedAt":"2026-11-11T12:47:47+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-10cb353ca77c2a8069d6892d1d394d936b12c0add3c8749e2a1a03a1c5d50426","complete":true,"records":231666,"lastAttemptAt":"2026-11-11T13:57:33.0989698+00:00","lastSuccessAt":"2026-11-11T12:59:55.8981437+00:00","error":null,"lastVerifiedAt":"2026-11-11T13:57:33.0989698+00:00","snapshotPublishedAt":"2026-11-11T12:32:53+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null}]}