Skip to content

[Security] Contacts POST route uses serviceClient, bypasses Supabase RLS #40

@eltociear

Description

@eltociear

Summary

src/app/api/contacts/route.ts POST handler uses getServiceClient() (Supabase service role key) to upsert contacts. This bypasses Row Level Security policies.

Location

// src/app/api/contacts/route.ts line ~57
const serviceClient = getServiceClient();
const { data, error } = await serviceClient
  .from("contacts")
  .upsert(
    { user_id: user.id, phone, name: name || null },
    { onConflict: "user_id,phone" }
  )

Impact

While the user_id is taken from the authenticated session (not user input), the use of serviceClient means:

  • Any RLS policies on the contacts table are completely bypassed
  • If the user.id were ever manipulated upstream, there would be no database-level protection
  • This is inconsistent with GET handler which correctly uses the regular Supabase client with RLS

This is the same pattern identified in #24 (phone_numbers) and #30 (providers).

Suggested Fix

Use the regular Supabase client instead of getServiceClient() for the upsert:

const supabase = await createServerSupabaseClient();
const { data, error } = await supabase
  .from("contacts")
  .upsert(
    { user_id: user.id, phone, name: name || null },
    { onConflict: "user_id,phone" }
  )

Severity

Medium — RLS bypass, but user_id comes from authenticated session.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions