Budibase: Unanchored Regex in matchers.ts Allows CSRF Bypass via Query String Injection in Budibase Worker
The buildMatcherRegex() / matches() functions in packages/backend-core/src/middleware/matchers.ts share the same structural root cause as the recently patched CVE-2026-31816: route patterns are compiled into unanchored regular expressions and tested against ctx.request.url, which includes the full query string. The CSRF middleware in the Budibase Worker uses this matching system to decide whether to skip CSRF token validation. An unauthenticated attacker can forge state-changing cross-origin requests against any Worker API endpoint by injecting a public route pattern into the query string, causing the CSRF middleware to skip token validation entirely. This allows actions such as sending admin invites, modifying global configuration, and managing users without a valid CSRF token.
CVE-2026-31816 fixed the same unanchored-regex-on-full-URL bug in server/middleware/utils.ts but left backend-core/middleware/matchers.ts untouched.
Root cause — packages/backend-core/src/middleware/matchers.ts:
```typescript
export const buildMatcherRegex = (patterns: EndpointMatcher[]): RegexMatcher[] => {
return patterns.map(pattern => {
let route = pattern.route
// replaces :param segments with /.*
const matches = route.match(PARAM_REGEX)
if (matches) {
for (let match of matches) {
const suffix = match.endsWith("/") ? "/" : ""
route = route.replace(match, "/.*" + suffix)
}
}
return { regex: new RegExp(route), method, route }
// ^ no ^ anchor, no $ anchor — matches anywhere in string
})
}
export const matches = (ctx: Ctx, options: RegexMatcher[]) => {
return options.find(({ regex, method }) => {
const urlMatch = regex.test(ctx.request.url) // full URL including query string
const methodMatch = method === "ALL" ? true
: ctx.request.method.toLowerCase() === method.toLowerCase()
return urlMatch && methodMatch
})
}
```
Two compounding bugs identical to the patched CVE:
1. new RegExp(route) — no ^ start anchor, no $ end anchor.
2. ctx.request.url — full URL string including ?query=value, not just the path.
CSRF middleware — packages/backend-core/src/middleware/csrf.ts:
```typescript
export function csrf(
opts: { noCsrfPatterns: EndpointMatcher[] } = { noCsrfPatterns: [] }
) {
const noCsrfOptions = buildMatcherRegex(opts.noCsrfPatterns)
return (async (ctx: Ctx, next: Next) => {
const found = matches(ctx, noCsrfOptions)
if (found) {
return next() // <-- CSRF check entirely skipped when pattern matches
}
// ... CSRF token validation ...
}) as Middleware
}
```
Worker registration — packages/worker/src/api/index.ts:
```typescript
const NO_CSRF_ENDPOINTS = [...PUBLIC_ENDPOINTS]
// PUBLIC_ENDPOINTS includes (among others):
// { route: "/api/global/auth/:tenantId", method: "POST" }
// { route: "/api/global/users/init", method: "POST" }
// { route: "/api/system/restored", method: "POST" }
router
.use(auth.buildCsrfMiddleware({ noCsrfPatterns: NO_CSRF_ENDPOINTS }))
```
buildMatcherRegex compiles "/api/global/auth/:tenantId" into the regex /api/global/auth/.* (via PARAM_REGEX replacing /:tenantId → /.*). Since the regex is unanchored, it matches the substring "/api/global/auth/" anywhere in ctx.request.url — including inside a query string parameter on a completely different endpoint.
Triggering condition:
```
POST /api/global/users/invite?x=/api/global/auth/evil
```
ctx.request.url = "/api/global/users/invite?x=/api/global/auth/evil"new RegExp("/api/global/auth/.*").test(ctx.request.url) → true (substring found in query string)ctx.request.method === "POST" → truematches() returns the pattern entry → CSRF skippedAdditional affected middleware (same matches() call):
| Middleware | Pattern list | Security effect when bypassed |
|---|---|---|
| csrf() | NO_CSRF_ENDPOINTS | CSRF token validation skipped |
| tenancy() | NO_TENANCY_ENDPOINTS | allowNoTenant = true, bypasses tenant ID requirement |
| authenticated() | PUBLIC_ENDPOINTS | Marks endpoint as publicEndpoint = true |
The NO_TENANCY_ENDPOINTS entry { route: "/api/system", method: "ALL" } compiles to /api/system (no param replacement). Since method: "ALL" matches every HTTP verb and the pattern is unanchored, any request with ?x=/api/system/x in its URL matches, potentially bypassing tenant isolation in multi-tenant deployments.
Prerequisites: Victim user is logged into a Budibase Worker instance (e.g., https://budibase.target.com) in their browser. Attacker hosts a page at https://evil.com.
Step 1 — Verify CSRF is normally enforced:
```bash
# Without the bypass — CSRF token missing → rejected
cur
This issue can be reached over the network, attack complexity is low, an attacker needs no privileges on the target. A user must be tricked into taking some action. The scope is unchanged, so the impact stays within the vulnerable component. Rated impact: confidentiality none, integrity high, availability none.
The score comes from this vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N
CVE-2026-48147 is classified as CWE-185: Incorrect Regular Expression. The product specifies a regular expression in a way that causes data to be improperly matched or compared.
CVE-2026-48147 is recorded against 2 packages.
Published on 12 June 2026. No public exploit is currently recorded for this entry. Record sourced from NVD.
github.com (Web)
nvd.nist.gov (Advisory)
github.com (Package)
@budibase/backend-core has other advisories on record. If you are patching this one, these are worth checking on the same host:
These advisories are the same class of weakness (CWE-185: Incorrect Regular Expression) in other software:
Details
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N
Affected Packages
| Software | From version | Fixed in |
|---|---|---|
| @budibase/backend-core | — | — |
| unknown | — | — |
References
Similar Threats
Vulnerability Monitoring
CVE-2026-48147 is rated CVSS 6.5 Medium. BotEraser monitors your WordPress installation and notifies you when software you use appears in our vulnerability database.
Set Up Free Alerts →No credit card required · Results in minutes
ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.
Stay up to date with the latest from Boteraser.
We use cookies to improve your experience on our site. By using our site, you consent to cookies.
Manage your cookie preferences below:
Essential cookies enable basic functions and are necessary for the proper function of the website.
CloudFlare provides web performance and security solutions, enhancing site speed and protecting against threats.
Service URL: developers.cloudflare.com (opens in a new window)
These cookies are needed for adding comments on this website.
These cookies are used for managing login functionality on this website.
Statistics cookies collect information anonymously. This information helps us understand how visitors use our website.
Google Analytics is a powerful tool that tracks and analyzes website traffic for informed marketing decisions.
Service URL: policies.google.com (opens in a new window)
You can find more information in our Cookie Policy and Privacy Policy.