SupabaseHigh

RLS Bypass: Unauthorized INSERT

Tables allow unauthenticated or cross-user inserts due to missing or overly permissive INSERT policies.

Last updated 2026-01-15

What Is This Vulnerability

An RLS bypass on INSERT occurs when a table's insert policy allows users to create rows they should not be able to. This typically happens when the WITH CHECK clause is missing, set to true, or does not verify that the inserting user is the owner of the new row. In some cases, the anonymous role is granted insert access to tables that should only be writable by authenticated users.

-- VULNERABLE: Anyone can insert rows for any user
CREATE POLICY "Allow inserts"
  ON messages FOR INSERT
  WITH CHECK (true);

This means an attacker can insert rows with arbitrary user_id values, impersonating other users.

Why It's Dangerous

Unauthorized INSERT access enables several attack vectors:

  • Data pollution: Injecting fake records into tables, corrupting application data integrity
  • Impersonation: Creating rows attributed to other users (e.g., fake messages, reviews, or orders)
  • Spam and abuse: Flooding tables with junk data, causing storage costs and degrading performance
  • Privilege escalation: Inserting rows into role or permission tables to grant attacker elevated access
  • Stored XSS: Injecting malicious content into user-facing fields that are rendered without sanitization

In a credit-based system, an attacker could insert fake transaction records. In a messaging app, they could send messages as any user.

How to Detect

Query the system catalog for INSERT policies and inspect their conditions:

SELECT tablename, policyname, cmd, with_check
FROM pg_policies
WHERE schemaname = 'public'
  AND cmd = 'INSERT';

Policies where with_check is true or does not reference auth.uid() are suspect. AuditYourApp tests this by attempting to insert a row as an unauthenticated user and as an authenticated user with a different user_id. If the insert succeeds, the policy is overly permissive.

You can also manually test using the Supabase client:

// Test as anonymous user
const { data, error } = await supabase
  .from('messages')
  .insert({ user_id: 'some-other-user-id', content: 'test' });
// If error is null, the table is vulnerable

How to Fix

Ensure INSERT policies verify that the new row belongs to the authenticated user:

-- Drop the permissive policy
DROP POLICY "Allow inserts" ON messages;

-- Properly scoped insert policy
CREATE POLICY "Users can insert own messages"
  ON messages FOR INSERT
  WITH CHECK (auth.uid() = user_id);

If the table should not accept any inserts from the client (e.g., system-generated records), do not create an INSERT policy at all. With RLS enabled and no INSERT policy, all client-side inserts will be denied. Use the service_role key in Edge Functions for server-side inserts.

-- For tables that should only be written by server-side code:
-- Simply enable RLS and do NOT add any INSERT policy
ALTER TABLE system_logs ENABLE ROW LEVEL SECURITY;
-- No INSERT policy = no client-side inserts allowed

Scan your app for this vulnerability

AuditYourApp automatically detects security misconfigurations in Supabase and Firebase projects. Get actionable remediation in minutes.

Run Free Scan