GitHub Security Advisory: GHSA-8783-3wgf-jggf
Package: @budibase/backend-core (NPM)
Severity: CRITICAL (CVSS 9.1)
CWE: CWE-287: Improper Authentication
Vulnerable versions: <= 3.35.3
Patched in: unpatched

Summary: Budibase: Authentication Bypass via Unanchored Regex in Public Endpoint Matcher — Unauthenticated Access to Protected Endpoints

Details:
### Summary

The `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.

### Details

**Step 1 — Public endpoint patterns compiled without anchors**

`packages/backend-core/src/middleware/matchers.ts`, line 26:

```typescript
return { regex: new RegExp(route), method, route }
```

No `^` prefix, no `$` suffix. The regex matches anywhere in the test string.

**Step 2 — Regex tested against full URL including query string**

`packages/backend-core/src/middleware/matchers.ts`, line 32:

```typescript
const urlMatch = regex.test(ctx.request.url)
```

Koa'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.

**Step 3 — publicEndpoint flag set to true**

`packages/backend-core/src/middleware/authenticated.ts`, lines 123-125:

```typescript
const found = matches(ctx, noAuthOptions)
if (found) {
  publicEndpoint = true
}
```

**Step 4 — Worker's global auth check skipped**

`packages/worker/src/api/index.ts`, lines 160-162:

```typescript
.use((ctx, next) => {
  if (ctx.publicEndpoint) {
    return next()        // ← SKIPS the auth check below
  }
  if ((!ctx.isAuthenticated || ...) && !ctx.internal) {
    ctx.throw(403, "Unauthorized")   // ← never reached
  }
})
```

When `ctx.publicEndpoint` is `true`, the 403 check at line 165-168 is never executed.

**Step 5 — Routes without per-route auth middleware are exposed**

`loggedInRoutes` in `packages/worker/src/api/routes/endpointGroups/standard.ts` line 23:

```typescript
export const loggedInRoutes = endpointGroupList.group()   // no middleware
```

Endpoints on `loggedInRoutes` have NO secondary auth check. The global check at `index.ts:160-169` was their only protection.

**Affected endpoints (no per-route auth — fully exposed):**
- `POST /api/global/users/search` — search all users (emails, names, roles)
- `GET /api/global/self` — get current user info
- `GET /api/global/users/accountholder` — account holder lookup
- `GET /api/global/template/definitions` — template definitions
- `POST /api/global/license/refresh` — refresh license
- `POST /api/global/event/publish` — publish events

**Not affected (have secondary per-route auth that blocks undefined user):**
- `GET /api/global/users` — on `builderOrAdminRoutes` which checks `isAdmin(ctx.user)` → returns false for undefined → throws 403
- `DELETE /api/global/users/:id` — on `adminRoutes` → same secondary check blocks it

### PoC

```bash
# Step 1: Confirm normal request is blocked
$ curl -s -o /dev/null -w "%{