Skip to content

[Security] Auth middleware marks all /api/* paths as public routes #43

@eltociear

Description

@eltociear

Summary

src/lib/supabase/middleware.ts includes /api in the PUBLIC_ROUTES array. The isPublicRoute() function matches any path starting with /api/, effectively making ALL API endpoints publicly accessible at the middleware level.

Location

// src/lib/supabase/middleware.ts
const PUBLIC_ROUTES = [
  "/",
  "/login",
  "/register",
  // ...
  "/api",  // This makes ALL /api/* routes "public"
  // ...
];

function isPublicRoute(pathname: string): boolean {
  return PUBLIC_ROUTES.some(
    (route) => pathname === route || pathname.startsWith(route + "/")
  );
}

Impact

The middleware skips Supabase session refresh for ALL API routes. While each API route handler does its own supabase.auth.getUser() check, the middleware would normally:

  1. Refresh expired session tokens automatically
  2. Redirect unauthenticated users

By marking /api as public, the middleware provides no defense-in-depth for API routes. If any API handler forgets the auth check, it becomes an unauthenticated endpoint.

This is particularly concerning because:

  • /api/webhooks/* are intentionally public (receives from Twilio/Telnyx)
  • But /api/messages/*, /api/contacts/*, /api/campaigns/* should NOT be public at middleware level
  • The intent was likely to only expose /api/health and /api/webhooks/* as public

Suggested Fix

Replace the broad /api entry with specific public API paths:

const PUBLIC_ROUTES = [
  // ...
  "/api/health",
  "/api/webhooks",
  "/api/waitlist",
  // NOT "/api" — too broad
];

Severity

Medium — Defense-in-depth gap. Individual route handlers still check auth, but one missing check = full vulnerability.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions