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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions