T09 · Insecure Skill Coding Practices
Error
- Location
- SKILL.md:60
- Finding
- Ineffective Clerk Route Protection Due to Filesystem Route-Group Matcher<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 60–65 **Vulnerability Type**: Authentication and route-protection bypass **Risk Level**: High ### Vulnerable Code ```typescript const isPrivateRoute = createRouteMatcher(["/(private)(.*)"]); export default clerkMiddleware(async (auth, request) => { if (isPrivateRoute(request)) { await auth.protect(); } }); ``` ### Technical Analysis The matcher attempts to protect `/(private)(.*)`. However, `(private)` is a Next.js filesystem route group, not a segment included in the public URL. For example, `app/(private)/dashboard/page.tsx` is served at `/dashboard`, not `/(private)/dashboard`. Consequently, requests to `/dashboard`, `/settings`, and other pages stored under the route group do not satisfy this matcher. The middleware therefore skips `auth.protect()`. The separate protected-layout example can mitigate this issue for pages only when developers implement it exactly. It does not make the documented proxy matcher effective, and it does not independently protect API handlers. ### Attack Path 1. A developer copies the documented proxy configuration and places sensitive pages under `app/(private)/`. 2. The developer assumes the proxy protects every route in that filesystem group. 3. An unauthenticated attacker sends a direct request to an actual public path such as `/dashboard`. 4. The matcher tests the request path against `/(private)(.*)`. 5. Because the request URL does not contain the route-group name, the matcher returns false. 6. `auth.protect()` is not called. 7. If the requested page or API lacks a separate server-side authorization check, the attacker accesses the protected functionality or data. ### Impact Assessment The flaw can permit unauthenticated access to routes that developers believe are protected. The exact scope depends on whether individual pages and API handlers perform independent authorization checks. Potential consequences include: - Unauthorized a ...[truncated 405 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Match the actual URL paths rather than filesystem route-group names. For example: ```typescript import { clerkMiddleware, createRouteMatcher, } from "@clerk/nextjs/server"; const isPrivateRoute = createRouteMatcher([ "/dashboard(.*)", "/settings(.*)", "/billing(.*)", ]); export default clerkMiddleware(async (auth, request) => { if (isPrivateRoute(request)) { await auth.protect(); } }); ``` For stronger default-deny behavior: 1. Define a narrow allowlist of genuinely public routes. 2. Protect every route not included in that allowlist. 3. Explicitly account for sign-in, sign-up, static assets, and public webhook endpoints. 4. Require authentication and authorization inside every sensitive API handler; do not rely exclusively on middleware. 5. Validate ownership or tenant membership for each accessed object. 6. Add integration tests that request every protected URL as an anonymous user and verify a redirect or `401/403` response. 7. Test actual paths such as `/dashboard`, not filesystem paths containing `(private)`. ]]>
