CVE-2026-50136High· 7.4▾ TwilightBudibase: Unauthenticated S3 signed upload URL generation allows arbitrary writes with stored datasource credentials
▾ Twilight zone — High severity, or a signal on a lesser flaw
impact 40.7 · likelihood 0.1 · exploitation 0
Need a working PoC? Pro members can cast a request and our team develops one — it lands right here.
Exploit-prediction probability, daily snapshots since Jul 4.
Disclosure to exploitation, from the record and what we observed since indexing it.
Disclosed via GHSA
0.3%
The application server exposes an unauthenticated endpoint that generates S3 PutObject presigned URLs using credentials stored in a workspace datasource. The route is protected only by the recaptcha middleware and does not require authentication, table permission, datasource permission, or builder access. A public caller who knows a workspace ID and S3 datasource ID can request a signed upload URL for attacker-controlled bucket and key values.
The static route registers the signed upload URL endpoint with only recaptcha before the controller:
packages/server/src/api/routes/static.ts:44-4844: .post(
45: "/api/attachments/:datasourceId/url",
46: recaptcha,
47: controller.getSignedUploadURL
48: )
The controller loads the datasource by datasourceId with enriched secret values:
packages/server/src/api/controllers/static/index.ts:590-598590:export const getSignedUploadURL = async function (
591: ctx: Ctx<GetSignedUploadUrlRequest, GetSignedUploadUrlResponse>
592:) {
593: // Ensure datasource is valid
594: let datasource
595: try {
596: const { datasourceId } = ctx.params
597: datasource = await sdk.datasources.get(datasourceId, { enriched: true })
598: if (!datasource) {
The request body controls bucket and key, and the server signs a PUT URL using the stored datasource credentials:
packages/server/src/api/controllers/static/index.ts:609-629609: if (datasource?.source === "S3") {
610: const { bucket, key } = ctx.request.body || {}
611: if (!bucket || !key) {
612: ctx.throw(400, "bucket and key values are required")
613: }
614: try {
615: let endpoint = datasource?.config?.endpoint
616: if (endpoint && !utils.urlHasProtocol(endpoint)) {
617: endpoint = `https://${endpoint}`
618: }
619: const s3 = new S3({
620: region: awsRegion,
621: endpoint: endpoint,
622: credentials: {
623: accessKeyId: datasource?.config?.accessKeyId as string,
624: secretAccessKey: datasource?.config?.secretAccessKey as string,
625: },
626: })
627: const params = { Bucket: bucket, Key: key }
628: signedUrl = await getSignedUrl(s3, new PutObjectCommand(params))
629: if (endpoint) {
The endpoint returns the signed URL and public URL to the caller:
packages/server/src/api/controllers/static/index.ts:630-639630: publicUrl = `${endpoint}/${bucket}/${key}`
631: } else {
632: publicUrl = `https://${bucket}.s3.${awsRegion}.amazonaws.com/${key}`
633: }
634: } catch (error: any) {
635: ctx.throw(400, error)
636: }
637: }
638:
639: ctx.body = { signedUrl, publicUrl }
Because no authorization middleware is applied, the API trusts public input to choose where the stored S3 credentials will write.
Non-destructive validation approach:
POST /api/attachments/<datasourceId>/url HTTP/1.1
x-budibase-app-id: app_<workspace-id>
content-type: application/json
{"bucket":"attacker-controlled-or-permitted-bucket","key":"poc/budibase.txt"}
signedUrl and confirm the object is created using the datasource's stored S3 credentials.This allows unauthenticated arbitrary object writes wherever the stored S3 datasource credentials have PutObject access. Depending on the datasource permissions, this can corrupt application data, overwrite public assets, place attacker-controlled objects in trusted buckets, consume storage, or abuse an organization's cloud credentials.
@budibase/server < 3.39.2Upgrade to a patched release:
@budibase/server 3.39.2Connected by shared product, vendor, weakness, or advisory.
CVE-2026-54356High· 7.1Budibase is an open-source low-code platform
CVE-2026-35219HighBudibase is an open-source low-code platform
GHSA-pvcr-8mvp-w8qrHigh· 7.7Budibase: Chat-Link Handoff Identity Confusion (Same-Tenant Account-Link CSRF)
GHSA-cr7p-cr3q-h5cmMedium· 5.3Budibase: Account Enumeration via Login Lockout Response Differential
GHSA-pmpg-2mxq-6xwrHigh· 7.1Budibase: NoSQL injection in MongoDB integration: collection dump, $where JS exec, cross-collection pivot, arbitrary update/delete
GHSA-v42f-v8xc-j435High· 8.5Budibase: SSRF via DNS rebinding in the REST datasource integration