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.
| Package | Affected versions | Patched versions |
|---|---|---|
@budibase/servernpm | < 3.23.32 | 3.23.32 |
Affected products
1Patches
19fdbff32fb9eRemove SQL troubleshooting section and external schema endpoint
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- github.com/advisories/GHSA-726g-59wr-cj4cghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-25041ghsaADVISORY
- github.com/Budibase/budibase/blob/f34d545602a7c94427bae63312a5ee9bf2aa6c85/packages/server/src/integrations/postgres.tsghsax_refsource_MISCWEB
- github.com/Budibase/budibase/commit/9fdbff32fb9e69650ba899a799e13f80d9b09e93ghsax_refsource_MISCWEB
- github.com/Budibase/budibase/security/advisories/GHSA-726g-59wr-cj4cghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.