@budibase/server

CVE-2026-50137

CVE-2026-50137 is a high-severity missing authorization vulnerability in @budibase/server (npm), affecting versions < 3.39.0. It is fixed in 3.39.0.

Key facts
CVSS score
N/A
High
Attack vector
Not available
Issuing authority
GitHub Advisory Database
Affected package
@budibase/server
Fixed in
3.39.0
Disclosed
2026

Summary

Summary The Budibase server route POST /api/attachments/:datasourceId/url (packages/server/src/api/routes/static.ts) is registered with only the recaptcha middleware. There is no authorized(...) middleware in the chain. The controller (packages/server/src/api/controllers/static/index.ts::getSignedUploadURL) looks the requested datasource up, instantiates an AWS S3 client with the datasource's stored accessKeyId / secretAccessKey, and returns an AWS Signature V4 pre-signed PutObjectCommand URL for the caller-supplied bucket and key. The bucket is not pinned to the datasource's configured bucket. The workspace context required by sdk.datasources.get is sourced by getWorkspaceIdFromCtx (packages/backend-core/src/utils/utils.ts) from any of: the x-budibase-app-id header, the JSON body appId, a path segment that begins with the workspace prefix, or ?appId=. auth.buildAuthMiddleware([], { publicAllowed: true }) runs before any of this and explicitly allows anonymous requests. The currentWorkspace middleware's "deny access to dev preview" branch only triggers under isBrowser(ctx) && !isApiKey(ctx); isBrowser checks the parsed User-Agent for a recognised browser, so any non-browser client (curl, the supplied PoC, any tool not setting a browser UA) is neither and reaches dev workspaces too. Net effect: an anonymous attacker who knows or can enumerate a workspace id (app...) and an S3-source datasource id (ds...) can call this endpoint with no auth and obtain a 15-minute pre-signed PUT URL minted on the victim's IAM identity. The endpoint also returns the publicUrl so the attacker knows exactly where their PUT lands. Because bucket is attacker-controlled, the attacker can write to any bucket those IAM credentials can write to, not only the bucket the datasource was configured for. Affected code packages/server/src/api/routes/static.ts at HEAD 56d2a984 (master, 2026-05-18): Note the asymmetry: every other mutating endpoint on this router carries an authorized(...) middleware. The signed-URL endpoint does not. packages/server/src/api/controllers/static/index.ts:595-645: sdk.datasources.get(datasourceId, { enriched: true }) (packages/server/src/sdk/workspace/datasources/datasources.ts) does the workspace DB read and also substitutes {{ env.* }} references in the config via processObjectSync, so even if the operator stored credentials as environment-variable references, those values are resolved before the S3 client is built. recaptcha (packages/server/src/middleware/recaptcha.ts) short-circuits to next() whenever the workspace either is not a production workspace or does not have features.recaptchaEnabled = true on its metadata. Neither is set by default. Even on workspaces with recaptcha enabled, builders carrying the x-budibase-type: builder header skip the check, but that branch is irrelevant here, the broader case is that an anonymous attacker simply chooses a non-prod workspace (which is the default for any in-development app) and the middleware no-ops. Reproduction Proof-of-concept Node.js script (no AWS SDK dependency, no external libraries): Wire-level request: Response: The attacker then PUTs arbitrary bytes to signedUrl and they land at publicUrl, signed by, and IAM-scoped to, the victim's stored S3 credentials. The existing test that exercises the endpoint, packages/server/src/api/routes/tests/static.spec.ts:123-146, sends the same request with config.defaultHeaders() (a builder auth cookie). That confirms the request shape; no negative-auth test (.set({}) or publicHeaders()) exists for this route, which is how the missing authorized(...) slipped past code review. Impact Confidentiality / Integrity: any anonymous internet user can write arbitrary objects to any bucket the configured IAM credentials can write to. The bucket parameter is attacker-controlled, so the blast radius is the full IAM policy attached to the credential, not just the bucket the operator wired into the datasource. Typical realistic outcomes: planting HTML/JS that the bucket serves at a known path (the response gives back publicUrl), overwriting an existing key the application later reads back as trusted data, racking up S3 storage / PUT cost. Availability: storage / cost exhaustion. Repeated PUTs of large objects to attacker-chosen keys cost the victim. Authorization scope leak: the endpoint discloses (a) whether a given datasourceId exists and is S3-typed (200 vs 400 'not found'), and (b) the resolved publicUrl which includes the region. No MFA / OAuth / per-user check exists between the request and the issued pre-signed URL. The credentials are not returned in plaintext, but the pre-signed URL is functionally equivalent to a 15-minute capability to PUT to the chosen bucket/key. Suggested fix Attach authorized(PermissionType.TABLE, PermissionLevel.WRITE) (or a higher gate, e.g. BUILDER, depending on intended audience) to the route, mirroring the sibling /api/attachments/:tableId/upload registration. Additionally, validate that the requested bucket matches datasource.config.bucket so the IAM blast radius is reduced to the configured bucket only. Minimal patch shape: And in the controller, before calling getSignedUrl: Credit Reported by tonghuaroot ([email protected]). Fix PR A candidate fix has been prepared on the temporary private fork that was created from this advisory: PR: https://github.com/Budibase/budibase-ghsa-35c4-rvc8-frhm/pull/1 Branch: fix/attachment-url-auth-and-bucket-pin Commit: Require builder auth and pin bucket on POST /api/attachments/:datasourceId/url The patch is the canonical two-part fix: Attach authorized(BUILDER) to POST /api/attachments/:datasourceId/url on packages/server/src/api/routes/static.ts, mirroring the surrounding POST /api/attachments/process and POST /api/pwa/process-zip registrations. Anonymous callers now receive 401 regardless of whether the recaptcha middleware fails open. Pin Bucket to datasource.config.bucket inside getSignedUploadURL (packages/server/src/api/controllers/static/index.ts) and ignore any bucket value supplied in the request body. If the datasource has no bucket configured, the route now returns 400 instead of issuing an unbounded pre-signed URL. Two regression tests are added in packages/server/src/api/routes/tests/static.spec.ts: should reject unauthenticated callers (anonymous request with config.publicHeaders() now returns 401, was 200 before). should ignore a client-supplied bucket and pin to the datasource's configured bucket (authenticated request with body { bucket: "other-bucket", key: "bar" } returns a signed URL bound to foo.s3.eu-west-1.amazonaws.com/bar, not other-bucket). Test run on the patch (Jest, packages/server):

Impact

What is missing authorization?

The application does not perform an authorization check before performing a sensitive operation. Typical impact: unauthorized access to restricted functionality or data.

Affected versions

npm

  • @budibase/server (< 3.39.0)

Security releases

  • @budibase/server → 3.39.0 (npm)
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 instead of chasing every advisory.

Kodem's runtime-powered SCA identifies whether CVE-2026-50137 is reachable in your applications. Explore open-source security for your team.

See if CVE-2026-50137 is reachable in your applications. Get a demo

Already deployed Kodem? See CVE-2026-50137 in your environment

Remediation advice

Upgrade @budibase/server to 3.39.0 or later to resolve this vulnerability.

Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.

Frequently asked questions about CVE-2026-50137

What is CVE-2026-50137?

CVE-2026-50137 is a high-severity missing authorization vulnerability in @budibase/server (npm), affecting versions < 3.39.0. It is fixed in 3.39.0. The application does not perform an authorization check before performing a sensitive operation.

Which versions of @budibase/server are affected by CVE-2026-50137?

@budibase/server (npm) versions < 3.39.0 is affected.

Is there a fix for CVE-2026-50137?

Yes. CVE-2026-50137 is fixed in 3.39.0. Upgrade to this version or later.

Is CVE-2026-50137 exploitable, and should I be worried?

Whether CVE-2026-50137 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

What actually determines whether CVE-2026-50137 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.

How do I fix CVE-2026-50137?

Upgrade @budibase/server to 3.39.0 or later.

Stop the waste.
Protect your environment with Kodem.