GitHub Security Advisory: GHSA-2wfh-rcwf-wh23
Package: @budibase/server (NPM)
Severity: HIGH (CVSS 8.7)
CWE: CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
Vulnerable versions: < 3.33.4
Patched in: 3.33.4

Summary: Budibase: Path traversal in plugin file upload enables arbitrary directory deletion and file write

Details:
## Summary

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.

## Severity

- **Attack Vector:** Network — exploitable via the plugin upload HTTP API
- **Attack Complexity:** Low — no special conditions; a single crafted multipart request suffices
- **Privileges Required:** High — requires Global Builder role (`GLOBAL_BUILDER` permission)
- **User Interaction:** None
- **Scope:** Changed — the plugin upload feature is scoped to a temp directory, but the traversal escapes to the host filesystem
- **Confidentiality Impact:** None — the vulnerability enables deletion and writing, not reading
- **Integrity Impact:** High — attacker can delete arbitrary directories and write arbitrary files via tarball extraction
- **Availability Impact:** High — recursive deletion of application or system directories causes denial of service

### Severity Rationale

 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.

## Affected Component

- `packages/server/src/api/controllers/plugin/file.ts` — `fileUpload()` (line 15)
- `packages/server/src/utilities/fileSystem/filesystem.ts` — `createTempFolder()` (lines 78-91)

## Description

### Unsanitized filename flows into filesystem operations

In `packages/server/src/api/controllers/plugin/file.ts`, the uploaded file's name is used directly after stripping the `.tar.gz` suffix:

```typescript
// packages/server/src/api/controllers/plugin/file.ts:8-19
export async function fileUpload(file: KoaFile) {
  if (!file.name || !file.path) {
    throw new Error("File is not valid - cannot upload.")
  }
  if (!file.name.endsWith(".tar.gz")) {
    throw new Error("Plugin must be compressed into a gzipped tarball.")
  }
  const path = createTempFolder(file.name.split(".tar.gz")[0])
  await extractTarball(file.path, path)

  return await getPluginMetadata(path)
}
```

The `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.

The `createTempFolder` function in `packages/server/src/utilities/fileSystem/filesystem.ts` uses `path.join()` which resolves `../` sequences, then performs destructive filesystem operations:

```typescript
// packages/server/src/utilities/fileSystem/files