VYPR
High severityNVD Advisory· Published Mar 9, 2026· Updated Mar 9, 2026

Budibase has a Command Injection in PostgreSQL Dump Command

CVE-2026-25041

Description

Budibase is a low code platform for creating internal tools, workflows, and admin panels. In 3.23.22 and earlier, the PostgreSQL integration constructs shell commands using user-controlled configuration values (database name, host, password, etc.) without proper sanitization. The password and other connection parameters are directly interpolated into a shell command. This affects packages/server/src/integrations/postgres.ts.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
@budibase/servernpm
< 3.23.323.23.32

Affected products

1

Patches

1
9fdbff32fb9e

Remove SQL troubleshooting section and external schema endpoint

https://github.com/Budibase/budibaseMel O'HaganNov 27, 2025via ghsa
11 files changed · +1 244
  • packages/builder/src/pages/builder/workspace/[application]/data/datasource/[datasourceId]/_components/panels/Settings.svelte+0 38 removed
    @@ -1,38 +0,0 @@
    -<script>
    -  import { Body, Button, Layout, notifications } from "@budibase/bbui"
    -  import Panel from "./Panel.svelte"
    -  import { API } from "@/api"
    -  import { downloadText } from "@budibase/frontend-core"
    -
    -  export let datasource
    -
    -  async function download() {
    -    if (!datasource?._id) {
    -      notifications.error("Datasource invalid")
    -    }
    -    const response = await API.fetchExternalSchema(datasource._id)
    -    downloadText(`${datasource.name}-dump.sql`, response.schema)
    -  }
    -</script>
    -
    -<Panel>
    -  <div class="main">
    -    <Layout gap="S" noPadding>
    -      <Body size="L" weight="700">Troubleshooting</Body>
    -      <Body>Download your schema to share with the Budibase team</Body>
    -      <div class="download-button">
    -        <Button cta on:click={download}>Download</Button>
    -      </div>
    -    </Layout>
    -  </div>
    -</Panel>
    -
    -<style>
    -  .main {
    -    margin-top: calc(var(--spacing-l) * -1);
    -  }
    -
    -  .download-button {
    -    padding-top: var(--spacing-s);
    -  }
    -</style>
    
  • packages/builder/src/pages/builder/workspace/[application]/data/datasource/[datasourceId]/index.svelte+1 18 modified
    @@ -12,10 +12,7 @@
       import RestAuthenticationPanel from "./_components/panels/Authentication/index.svelte"
       import RestVariablesPanel from "./_components/panels/Variables/index.svelte"
       import PromptQueryModal from "./_components/PromptQueryModal.svelte"
    -  import SettingsPanel from "./_components/panels/Settings.svelte"
    -  import { helpers } from "@budibase/shared-core"
    -  import { admin } from "@/stores/portal"
    -  import { IntegrationTypes } from "@/constants/backend"
    +import { IntegrationTypes } from "@/constants/backend"
       import Tooltip from "./_components/panels/Tooltip.svelte"
       import SaveDatasourceButton from "./_components/panels/SaveDatasourceButton.svelte"
       import { cloneDeep } from "lodash/fp"
    @@ -50,8 +47,6 @@
           )?.icon
         : undefined
     
    -  $: isCloud = $admin.cloud
    -  $: isPostgres = datasource?.source === IntegrationTypes.POSTGRES
       $: isRestDatasource = datasource?.source === IntegrationTypes.REST
       $: getOptions(datasource)
     
    @@ -104,16 +99,6 @@
           panelOptions = isRest ? [] : ["Queries"]
           selectedPanel = isRest ? null : "Queries"
         }
    -    // always the last option for SQL
    -    if (!isRestDatasource && helpers.isSQL(datasource)) {
    -      if (isCloud && isPostgres) {
    -        // We don't show the settings panel for Postgres on Budicloud because
    -        // it requires pg_dump to work and we don't want to enable shell injection
    -        // attacks.
    -      } else {
    -        panelOptions.push("Settings")
    -      }
    -    }
       }
     </script>
     
    @@ -192,8 +177,6 @@
             <RelationshipsPanel {datasource} />
           {:else if selectedPanel === "Queries"}
             <QueriesPanel {datasource} />
    -      {:else if selectedPanel === "Settings"}
    -        <SettingsPanel {datasource} />
           {:else}
             <Body>Something went wrong</Body>
           {/if}
    
  • packages/frontend-core/src/api/datasources.ts+0 12 modified
    @@ -9,7 +9,6 @@ import {
       FetchDatasourceInfoResponse,
       FetchDatasourceViewInfoRequest,
       FetchDatasourceViewInfoResponse,
    -  FetchExternalSchemaResponse,
       UpdateDatasourceRequest,
       UpdateDatasourceResponse,
       VerifyDatasourceRequest,
    @@ -42,9 +41,6 @@ export interface DatasourceEndpoints {
       fetchViewInfoForDatasource: (
         datasource: Datasource
       ) => Promise<FetchDatasourceViewInfoResponse>
    -  fetchExternalSchema: (
    -    datasourceId: string
    -  ) => Promise<FetchExternalSchemaResponse>
     }
     
     export const buildDatasourceEndpoints = (
    @@ -139,12 +135,4 @@ export const buildDatasourceEndpoints = (
         })
       },
     
    -  /**
    -   * Fetches the external schema of a datasource
    -   */
    -  fetchExternalSchema: async (datasourceId: string) => {
    -    return await API.get({
    -      url: `/api/datasources/${datasourceId}/schema/external`,
    -    })
    -  },
     })
    
  • packages/server/src/api/controllers/datasource.ts+0 20 modified
    @@ -14,7 +14,6 @@ import {
       FetchDatasourceViewInfoRequest,
       FetchDatasourceViewInfoResponse,
       FetchDatasourcesResponse,
    -  FetchExternalSchemaResponse,
       FieldType,
       FindDatasourcesResponse,
       RelationshipFieldMetadata,
    @@ -321,22 +320,3 @@ export async function find(ctx: UserCtx<void, FindDatasourcesResponse>) {
       const datasource = await sdk.datasources.get(ctx.params.datasourceId)
       ctx.body = await sdk.datasources.removeSecretSingle(datasource)
     }
    -
    -export async function getExternalSchema(
    -  ctx: UserCtx<void, FetchExternalSchemaResponse>
    -) {
    -  const datasource = await sdk.datasources.get(ctx.params.datasourceId)
    -  const enrichedDatasource =
    -    await sdk.datasources.getAndMergeDatasource(datasource)
    -  const connector = await sdk.datasources.getConnector(enrichedDatasource)
    -
    -  if (!connector.getExternalSchema) {
    -    ctx.throw(400, "Datasource does not support exporting external schema")
    -  }
    -
    -  try {
    -    ctx.body = { schema: await connector.getExternalSchema() }
    -  } catch (e: any) {
    -    ctx.throw(400, e.message)
    -  }
    -}
    
  • packages/server/src/api/routes/datasource.ts+0 4 modified
    @@ -27,10 +27,6 @@ builderRoutes
       )
       .post("/api/datasources", datasourceValidator(), datasourceController.save)
       .delete("/api/datasources/:datasourceId/:revId", datasourceController.destroy)
    -  .get(
    -    "/api/datasources/:datasourceId/schema/external",
    -    datasourceController.getExternalSchema
    -  )
     
     authorizedRoutes
       .get("/api/datasources/:datasourceId", datasourceController.find)
    
  • packages/server/src/integrations/microsoftSqlServer.ts+0 45 modified
    @@ -85,7 +85,6 @@ const SCHEMA: Integration = {
       features: {
         [DatasourceFeature.CONNECTION_CHECKING]: true,
         [DatasourceFeature.FETCH_TABLE_NAMES]: true,
    -    [DatasourceFeature.EXPORT_SCHEMA]: true,
       },
       datasource: {
         user: {
    @@ -609,50 +608,6 @@ class SqlServerIntegration extends Sql implements DatasourcePlus {
         return dataType
       }
     
    -  async getExternalSchema() {
    -    const scriptParts = []
    -    const tables: any = {}
    -    const columns = await this.getColumnDefinitions()
    -    for (const row of columns) {
    -      const { TableName, ColumnName, IsNullable, IsIdentity } = row
    -
    -      if (!tables[TableName]) {
    -        tables[TableName] = {
    -          columns: [],
    -        }
    -      }
    -
    -      const nullable = IsNullable ? "NULL" : "NOT NULL"
    -      const identity = IsIdentity ? "IDENTITY" : ""
    -      const columnDefinition = `[${ColumnName}] ${this.getDataType(
    -        row
    -      )} ${nullable} ${identity}`
    -
    -      tables[TableName].columns.push(columnDefinition)
    -
    -      if (IsIdentity) {
    -        tables[TableName].identityColumn = ColumnName
    -      }
    -    }
    -
    -    // Generate SQL statements for table creation
    -    for (const tableName in tables) {
    -      const { columns, identityColumn } = tables[tableName]
    -
    -      let createTableStatement = `CREATE TABLE [${tableName}] (\n`
    -      createTableStatement += columns.join(",\n")
    -
    -      if (identityColumn) {
    -        createTableStatement += `,\n CONSTRAINT [PK_${tableName}] PRIMARY KEY (${identityColumn})`
    -      }
    -
    -      createTableStatement += "\n);"
    -
    -      scriptParts.push(createTableStatement)
    -    }
    -
    -    return scriptParts.join("\n")
    -  }
     }
     
     export default {
    
  • packages/server/src/integrations/mysql.ts+0 29 modified
    @@ -47,7 +47,6 @@ const SCHEMA: Integration = {
       features: {
         [DatasourceFeature.CONNECTION_CHECKING]: true,
         [DatasourceFeature.FETCH_TABLE_NAMES]: true,
    -    [DatasourceFeature.EXPORT_SCHEMA]: true,
       },
       datasource: {
         host: {
    @@ -426,34 +425,6 @@ class MySQLIntegration extends Sql implements DatasourcePlus {
         }
       }
     
    -  async getExternalSchema() {
    -    try {
    -      const [databaseResult] = await this.internalQuery({
    -        sql: `SHOW CREATE DATABASE IF NOT EXISTS \`${this.config.database}\``,
    -      })
    -      let dumpContent = [databaseResult["Create Database"]]
    -
    -      const tablesResult = await this.internalQuery({
    -        sql: `SHOW TABLES`,
    -      })
    -
    -      for (const row of tablesResult) {
    -        const tableName = row[`Tables_in_${this.config.database}`]
    -
    -        const createTableResults = await this.internalQuery({
    -          sql: `SHOW CREATE TABLE \`${tableName}\``,
    -        })
    -
    -        const createTableStatement = createTableResults[0]["Create Table"]
    -
    -        dumpContent.push(createTableStatement)
    -      }
    -
    -      return dumpContent.join(";\n") + ";"
    -    } finally {
    -      this.disconnect()
    -    }
    -  }
     }
     
     export default {
    
  • packages/server/src/integrations/postgres.ts+0 58 modified
    @@ -1,4 +1,3 @@
    -import fs from "fs"
     import {
       Integration,
       DatasourceFieldType,
    @@ -29,8 +28,6 @@ import { escapeDangerousCharacters } from "../utilities"
     
     import { Client, ClientConfig, types } from "pg"
     import { getReadableErrorMessage } from "./base/errorMapping"
    -import { exec } from "child_process"
    -import { storeTempFile } from "../utilities/fileSystem"
     import { env, sql } from "@budibase/backend-core"
     
     // Return "date" and "timestamp" types as plain strings.
    @@ -69,7 +66,6 @@ const SCHEMA: Integration = {
       features: {
         [DatasourceFeature.CONNECTION_CHECKING]: true,
         [DatasourceFeature.FETCH_TABLE_NAMES]: true,
    -    [DatasourceFeature.EXPORT_SCHEMA]: true,
       },
       datasource: {
         host: {
    @@ -489,60 +485,6 @@ class PostgresIntegration extends Sql implements DatasourcePlus {
         }
       }
     
    -  async getExternalSchema() {
    -    if (!env.SELF_HOSTED) {
    -      // This is because it relies on shelling out to pg_dump and we don't want
    -      // to enable shell injection attacks.
    -      throw new Error(
    -        "schema export for Postgres is not supported in Budibase Cloud"
    -      )
    -    }
    -
    -    const dumpCommandParts = [
    -      `user=${this.config.user}`,
    -      `host=${this.config.host}`,
    -      `port=${this.config.port}`,
    -      `dbname=${this.config.database}`,
    -    ]
    -
    -    if (this.config.ssl) {
    -      dumpCommandParts.push("sslmode=verify-ca")
    -      if (this.config.ca) {
    -        const caFilePath = storeTempFile(this.config.ca)
    -        fs.chmodSync(caFilePath, "0600")
    -        dumpCommandParts.push(`sslrootcert=${caFilePath}`)
    -      }
    -
    -      if (this.config.clientCert) {
    -        const clientCertFilePath = storeTempFile(this.config.clientCert)
    -        fs.chmodSync(clientCertFilePath, "0600")
    -        dumpCommandParts.push(`sslcert=${clientCertFilePath}`)
    -      }
    -
    -      if (this.config.clientKey) {
    -        const clientKeyFilePath = storeTempFile(this.config.clientKey)
    -        fs.chmodSync(clientKeyFilePath, "0600")
    -        dumpCommandParts.push(`sslkey=${clientKeyFilePath}`)
    -      }
    -    }
    -
    -    const dumpCommand = `PGPASSWORD="${
    -      this.config.password
    -    }" pg_dump --schema-only "${dumpCommandParts.join(" ")}"`
    -
    -    return new Promise<string>((resolve, reject) => {
    -      exec(dumpCommand, (error, stdout, stderr) => {
    -        if (error || stderr) {
    -          console.error(stderr)
    -          reject(new Error(stderr))
    -          return
    -        }
    -
    -        resolve(stdout)
    -        console.log("SQL dump generated successfully!")
    -      })
    -    })
    -  }
     }
     
     export default {
    
  • packages/server/src/tests/utilities/api/datasource.ts+0 14 modified
    @@ -3,7 +3,6 @@ import {
       CreateDatasourceResponse,
       Datasource,
       FetchDatasourceInfoResponse,
    -  FetchExternalSchemaResponse,
       FieldType,
       RelationshipType,
       UpdateDatasourceRequest,
    @@ -97,19 +96,6 @@ export class DatasourceAPI extends TestAPI {
         )
       }
     
    -  externalSchema = async (
    -    datasource: Datasource | string,
    -    expectations?: Expectations
    -  ): Promise<FetchExternalSchemaResponse> => {
    -    const id = typeof datasource === "string" ? datasource : datasource._id
    -    return await this._get<FetchExternalSchemaResponse>(
    -      `/api/datasources/${id}/schema/external`,
    -      {
    -        expectations,
    -      }
    -    )
    -  }
    -
       addExistingRelationship = async (
         {
           one,
    
  • packages/types/src/api/web/workspace/datasource.ts+0 4 modified
    @@ -58,7 +58,3 @@ export type FindDatasourcesResponse = Datasource
     export interface DeleteDatasourceResponse {
       message: string
     }
    -
    -export interface FetchExternalSchemaResponse {
    -  schema: string
    -}
    
  • packages/types/src/sdk/datasources.ts+0 2 modified
    @@ -85,7 +85,6 @@ export enum FilterType {
     export enum DatasourceFeature {
       CONNECTION_CHECKING = "connection",
       FETCH_TABLE_NAMES = "fetch_table_names",
    -  EXPORT_SCHEMA = "export_schema",
     }
     
     export interface StepDefinition {
    @@ -176,7 +175,6 @@ export interface IntegrationBase {
       delete?(query: any): Promise<any[] | any>
       patch?(query: any): Promise<any[] | any>
       testConnection?(): Promise<ConnectionInfo>
    -  getExternalSchema?(): Promise<string>
       defineTypeCastingFromSchema?(schema: {
         [key: string]: {
           name: string
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.