GHSA-mqhr-6j6h-74p5 Budibase: Unauthenticated REST Datasource Credential Theft via Cross-Origin Auth Leak
SummaryBudibase attaches a REST datasource's stored credentials (Bearer/Basic tokens and static headers) to an outgoing request before it decides which host the request goes to, and never checks that the destination host matches the datasource. A query's request path can be pointed at any host (via an absolute URL or a user-supplied `{{ parameter }}`), so the stored credentials are delivered to an attacker-chosen server. Because a query can be published with the PUBLIC role, this is reachable by a fully unauthenticated attacker with one HTTP request. The credentials are masked (`--secret-value--`) everywhere in the API, yet this recovers them in cleartext. This is a bypass of GHSA-3gp5 (which fixed the base-URL rewrite vector but added no same-origin check; the query-path vector remains, now unauthenticated).Root Cause`packages/server/src/integrations/rest.ts`: `_req()` builds `authHeaders = getAuthHeaders(...)` from the stored auth configs and merges them with `config.defaultHeaders` into every request, before the URL is resolved. `getUrl()`: if the (parameter-enriched) query `path` starts with `http`, the datasource base URL is ignored and `path` becomes the absolute request URL. There is no comparison between the resolved request host and the datasource base host before the credentials are attached. The execute route `POST /api/v2/queries/:queryId` runs under `authorized(QUERY, WRITE)`; a query published at role PUBLIC is executable with no session.POCReproduction — copy each line, paste in your terminal, press Enter,Line 1:Line 2:RESPONSEquery=query_datasource_9938805b2d0348a4a4f28dc9d1c97f19_7727920d8ccb4708a495eafb5a1351a7Line 3:RESPONSEpublishedLine 4 — THE BUG (anonymous, no cookie — this prints the leaked Bearer UNAUTH_LEAK_TOKEN_5150):RESPONSE{"data":[{"args":{},"headers":{"host":"postman-echo.com","accept":"*/*","accept-language":"*","accept-encoding":"gzip, br","user-agent":"undici","sec-fetch-mode":"cors","x-static-secret":"STATIC_HDR_LEAK_4242","authorization":"Bearer UNAUTH_LEAK_TOKEN_5150","x-datadog-trace-id":"3035031720617373998","x-datadog-parent-id":"5509262114738533737","x-datadog-sampling-priority":"0","x-datadog-tags":"_dd.p.tid=6a56b19a00000000,_dd.p.llmobs_ml_app=app-service","traceparent":"00-6a56b19a000000002a1e99450570f12e-4c74d4b43b81f569-00","tracestate":"dd=t.tid:6a56b19a00000000;t.dm:-1;s:0;p:4c74d4b43b81f569","x-forwarded-proto":"https"},"url":"https://postman-echo.com/get"}],"pagination":{},"headers":{"cf-cache-status":"DYNAMIC","cf-ray":"a1b3cda318a3f0e7-DUB","content-encoding":"gzip","content-type":"application/json; charset=utf-8","date":"Tue, 14 Jul 2026 22:00:58 GMT","etag":"W/\"292-YpfNwvZmX1jVU7GPgCQX7hoZtMw\"","server":"cloudflare","set-cookie":"_cfuvid=lJDfxAv0j6gmoaOpkyXVKpYK0yYSlYwgBjSHcA.rn.0-1784066458.0972874-1.0.1.1-q7WOtNlMsl5YmqkV34ms7hdy4oi1sw17X9IguTaJOKE; HttpOnly; SameSite=None; Secure; Path=/; Domain=postman-echo.com","vary":"Accept-Encoding","x-envoy-upstream-service-time":"4"},"code":200,"size":"658B","time":"139ms"}Actual output of Line 4 (the leaked credential)The anonymous request returns HTTP 200; the outbound `headers` echoed by `postman-echo.com` contain the datasource's stored, otherwise-masked credentials:`authorization: Bearer UNAUTH_LEAK_TOKEN_5150` and `x-static-secret: STATIC_HDR_LEAK_4242` were delivered to a host chosen by a fully anonymous caller. Confirmed 3/3 separate runs.Full Real-World AttackImpact| Who / what is affected | How |
|---|---|
| Every REST datasource with stored auth used by a PUBLIC query | Its Bearer/Basic token + static headers are exfiltrated |
| The third-party service behind that datasource | Attacker uses the stolen token directly (data theft / abuse) |
| Unauthenticated attackers | One request, no login, no CSRF, no user interaction |
| The credential-masking control | Fully defeated - masked secrets recovered in cleartext |Recommended FixBefore attaching stored auth, require the resolved request host to equal the datasource base host (strict same-origin check); strip stored credentials on any cross-origin resolution.Reject absolute-URL query paths and host/scheme changes introduced via path parameter bindings.Re-audit the GHSA-3gp5 fix - it closed the base-URL rewrite vector only; the query-path vector remained and is now reachable unauthenticated.