Clerk createRouteMatcher bypass: middleware-only auth isn't enough
CVE-2026-41248 let crafted requests skip the route matcher in Clerk's middleware. What it affects, how to fix it, and how to check that your own routes do not depend on middleware alone.
Published Last updated
Upgrade the Clerk web packages in your project to the patched versions, then put a sign-in check inside every route handler, server component and server action that returns or changes data. CVE-2026-41248, published on 15 April 2026, let crafted requests skip the route matcher in clerkMiddleware, so protection that lived only in middleware did not hold. Mark each route detected, unknown or confirmed.
What does the Clerk advisory say?
Clerk published GHSA-vqx2-fgx2-5wq9 on 15 April 2026 and rated it critical, CVSS 9.1. The GitHub Advisory Database record lists the weaknesses as CWE-436 (interpretation conflict) and CWE-863 (incorrect authorization), and gives the timeline: reported 13 April, patched and disclosed 15 April, published to the National Vulnerability Database on 24 April. The advisory credits the reporter, Christiaan Swiers.
The plain version: a request could be shaped so that createRouteMatcher did not recognise it as protected, and the request then reached the code behind the middleware. Apps that relied only on the middleware to keep signed-out visitors out were the ones exposed. Apps that also checked the user inside the handler were not relying on the matcher alone. We checked both Clerk pages on 24 October 2026. Neither mentions exploitation in the wild.
| Package | Affected | Patched |
|---|---|---|
| @clerk/nextjs | 5.0.0 to 6.39.1, 7.0.0 to 7.2.0 | 5.7.6, 6.39.2, 7.2.1 |
| @clerk/nuxt | 1.1.0 to 1.13.27, 2.0.0 to 2.2.1 | 1.13.28, 2.2.2 |
| @clerk/astro | 0.0.1 to 2.17.9, 3.0.0 to 3.0.14 | 1.5.7, 2.17.10, 3.0.15 |
| @clerk/shared | 2.20.17 to 3.47.3, 4.0.0 to 4.8.0 | 2.22.1, 3.47.4, 4.8.1 |
Ranges as listed in the Clerk advisory and the GitHub record, checked 24 October 2026. The records split the Astro line slightly differently, so read your own major version against the patched column.
Does this affect an app built with Newly?
Newly builds a real iPhone and Android app with React Native and Expo. The four affected packages are web and server packages for Next.js, Nuxt and Astro, so the phone app itself is not the place to look. The place to look is anything with a web server in the project: an admin panel, a web version of the app, a companion site or a backend you added. You can download your code or connect your own repository, so the lockfile is yours to read.
- Search the lockfile for
@clerk/and write down every package and version. Compare each against the table above. - Search the code for
createRouteMatcher. If it appears in a middleware file, list the paths it covers. - For each covered path, open the handler behind it and look for a user check inside. No check inside means the route depended on the middleware alone.
If your phone app calls an API that sits behind Clerk middleware, the same list applies to that API. A mobile client can send any request a browser can, so a hidden screen protects nothing. The 15-minute check covers the wider set of places to look.
Why isn't middleware-only auth enough?
Middleware decides based on the path of a request. Your framework decides which code handles it, also based on the path. When those two readings of the same request differ, the middleware lets through something the handler then serves. Clerk's Astro reference describes it this way: middleware auth checks rely on path matching, which can diverge from how the framework routes requests, and may leave protected resources reachable.
This is not new guidance. Clerk's Next.js reference says middleware is not the best place to protect routes and that you should protect access as close to the resource as possible, in the code that reads or mutates the data. The Next.js authentication guide says its proxy layer should not be your only line of defense, and that route handlers and server actions should be treated like public API endpoints. After CVE-2025-29927 in 2025, Vercel wrote in its postmortem that it does not recommend middleware as the sole method of protecting routes. OWASP lists the family under A01:2025 Broken Access Control and advises denying by default, except for public resources.
Reading the deprecation
Clerk now marks createRouteMatcher as deprecated in Next.js and documents its removal in @clerk/astro v4 and @clerk/nuxt v3. If an AI tool generated your middleware from an older example, it may still use the pattern. The fix is a version upgrade plus a check in each resource.
How do I fix it?
- Upgrade each Clerk package past the patched version for your major line, reinstall so the lockfile changes, and redeploy. An upgrade in a branch that was never deployed fixes nothing.
- Add the check to each resource. In Next.js, Clerk's auth() reference describes
auth.protect(): it returns the auth object for an authorized user, redirects an unauthenticated page request to sign-in, and returns a 401 for an unauthenticated server action request. - On Astro and Nuxt, follow the resource pattern in the Astro and Nuxt references: read the authenticated flag in the page or event handler and return a 401 or redirect when it is false.
- Invert your rule. List the public routes and require sign-in for everything else, so a route you forget to list is closed. This is our suggestion, in line with OWASP's deny-by-default advice. The Clerk advisory itself points to server-side auth checks inside route handlers, server components and server actions.
| Where the check lives | What it covers | What it means for this flaw |
|---|---|---|
| Middleware matcher only | Paths the matcher recognises | Depends on the matcher seeing the request as protected. This is the pattern the advisory describes. |
| Middleware plus handler check | Each handler that has the check | A request that skips the matcher still meets the handler check. |
| Check in the data layer | Every caller of the data function | Closest to the data. Next.js recommends a data access layer for this reason. |
| Hidden button or screen | The interface only | Covers nothing on the server. Anyone can call the endpoint. |
How do I check my own routes?
Do this on a staging copy or on routes you own, with your own test accounts. Do not probe anyone else's app.
1. Write the route inventory
List every page, API route, route handler and server action that shows or changes user data. Server actions count even though they have no visible URL, because they are reachable by request like any endpoint. The list is the work. A route that is not on it is a route nobody checked.
2. Read each handler for its own check
Open the file. You want a line near the top that reads the user and returns early when there is none, before any database call. If the first line is a database read, the route trusts something upstream. Mark it detected.
3. Test as a signed-out visitor
In a private window, request each protected path. You want a sign-in redirect or a 401. Try small variations of each path, such as a trailing slash or different letter casing, since path matching is where this advisory sits. A 200 with private data is a detected problem. Fix the handler first, then look at the logs.
4. Test as the wrong signed-in user
Sign in as a second test account and request a record that belongs to the first. Being signed in is not the same as being allowed. Clerk also published a separate high-severity advisory on 22 April 2026 about organization, billing and reverification checks, listed on its advisories page, so read the list if you use those features.
5. Read the logs back to 13 April
Look for requests to protected paths that came from signed-out sessions and returned 200. If your host keeps no logs, write that down. Unknown is the honest result, and it is better than a guess.
A free scan on red.run.cloud can help with the outside view of a live site, such as which routes answer without a login. It reads the stack and matches versions. It does not see your handler code, so it cannot move a route to confirmed. The pre-launch review and the Supabase anon key test cover the database side of the same question.
Where do you stand?
Everything starts as unknown. Move an item to confirmed only when you hold the evidence listed under it. Moving an item to detected is useful too, because it tells you what to fix first.
Your route protection check
- Detected: You found the problem.
- Unknown: Not checked yet, or the check could not tell.
- Confirmed by owner evidence: You checked and kept the proof.
Your answers stay in this browser tab. Nothing is saved or sent.
Frequently asked questions
Is CVE-2026-41248 being exploited?
The Clerk advisory and the GitHub advisory record do not mention exploitation in the wild, as of our check on 24 October 2026. That is an absence in two documents and says nothing about your app. Treat the flaw as a reason to upgrade and to add checks inside your handlers, and look at your access logs for requests to protected paths from signed-out visitors.
I upgraded Clerk. Do I still need checks inside my handlers?
Yes. The upgrade fixes this flaw. It does not change the design problem the advisory points at: a rule that lives only in middleware depends on the middleware matching the request the same way the framework routes it. Clerk's own documentation tells you to protect access as close to the resource as possible.
Does this affect the Expo mobile app Newly builds?
The advisory lists four packages: @clerk/nextjs, @clerk/nuxt, @clerk/astro and @clerk/shared. A mobile app talks to a backend, and the backend is where the check belongs. If your project also has a Next.js, Nuxt or Astro side, such as an admin panel or a web version, search its lockfile for those names.
What does 'deprecated' mean for createRouteMatcher?
In the Next.js reference, Clerk marks createRouteMatcher() as deprecated and logs a runtime warning. Its Astro and Nuxt references say the helper was removed in @clerk/astro v4 and @clerk/nuxt v3. Moving the check onto each resource is the replacement Clerk documents.
Can a scan tell me whether my routes are protected?
From outside, a scan can see which routes answer a signed-out request and which package versions your site serves. It cannot read your handler code or prove a check exists on a route it never reached. A route that answers a signed-out request is detected. A route that refuses is unknown until you hold the handler code or a test that shows the check.
Is my data exposed if I was on an affected version?
An affected version means a gap existed in route protection. Whether any request used it depends on your routes and your logs, and the advisory does not report exploitation. Look at which routes sat behind middleware only, and what they returned. If any returned private data, rotate keys that data could reveal and tell the people it concerns.
Sources
All sources opened on 24 October 2026.
- Middleware-based route protection bypass (GHSA-vqx2-fgx2-5wq9), Clerk, GitHub Security Advisories
- GHSA-vqx2-fgx2-5wq9 / CVE-2026-41248, GitHub Advisory Database
- clerk/javascript security advisories, Clerk, GitHub
- clerkMiddleware() for Next.js, Clerk docs
- auth() for the Next.js App Router, Clerk docs
- clerkMiddleware() for Astro, Clerk docs
- clerkMiddleware() for Nuxt, Clerk docs
- How to implement authentication in Next.js, Next.js docs
- Postmortem on Next.js Middleware bypass (CVE-2025-29927), Vercel
- A01:2025 Broken Access Control, OWASP Top 10
