Skip to main content
Aatvi AI
Aatvi AISoftware engineering
← Back to Insights
·8 min read

Bolt, v0, and Replit Apps: The Auth and RLS Mistakes We Keep Seeing

In apps generated by Bolt.new, v0.dev, and Replit Agent, authentication often creates a false sense of security while leaving database tables exposed.

AI Code RescueAuthenticationRow Level SecurityPostgreSQL
Three pillars of secure AI app authorization: strict RLS policies, server-verified sessions, and secure database views.
Protect AI-generated applications by validating user identity on the server, enforcing explicit RLS policies, and scoping views.

In AI-generated applications built with Bolt.new, v0.dev, and Replit Agent, authentication mechanisms frequently suffer from a fatal disconnect: the UI reflects a logged-in user while the database is completely open or improperly restricted. AI coding assistants frequently introduce four critical authorization flaws: permissive USING (true) RLS policies generated to bypass errors during prototyping; relying on insecure client-side supabase.auth.getSession() without server verification via supabase.auth.getUser(); database views that silently bypass RLS because they lack security_invoker = true; and Route Handlers lacking tenant organization scope.

When an AI model generates database schemas and authentication hooks, it prioritizes error-free execution over defensive authorization boundaries. If a query fails during prototyping because a policy blocks access, the AI often responds by widening permissions or removing filters altogether.

The short version

Securing authentication in an AI-generated app requires moving authorization enforcement from client components into PostgreSQL Row-Level Security and server-verified identity checks:

  1. Never deploy USING (true) policies on tables containing customer records; enforce explicit tenant predicates such as auth.uid() = user_id.
  2. Do not trust client-read sessions via getSession(); verify caller identity on the server using supabase.auth.getUser().
  3. Configure all PostgreSQL views with WITH (security_invoker = true) so they inherit the querying user's RLS rules rather than the view creator's permissions.
  4. Scope API Route Handlers and server mutations to explicit organization or tenant identifiers extracted directly from authenticated session claims.

Review our AI-generated code audit checklist and sample AI code rescue audit report to benchmark your database authorization posture.

1. The Permissive USING (true) RLS Antipattern

When AI coding assistants generate database initialization scripts, they frequently enable Row-Level Security but immediately neutralize it with permissive catch-all policies.

The vulnerability

During rapid prototyping, AI assistants often write policies like this:

-- DANGEROUS: Permissive policy allows any authenticated user to view all rows
CREATE POLICY "Allow authenticated users full access" 
ON public.organizations 
FOR ALL 
TO authenticated 
USING (true) 
WITH CHECK (true);

While this eliminates permission errors during initial user testing, USING (true) means that any user with a valid login token can read, modify, or delete every other user's organization data simply by requesting it via the client SDK or direct API call.

How to audit and fix

Run this diagnostic query in your PostgreSQL database to find all public tables with overly permissive policies:

SELECT 
  tablename, 
  policyname, 
  cmd, 
  qual, 
  with_check 
FROM pg_policies 
WHERE schemaname = 'public' 
  AND (qual = 'true' OR with_check = 'true');

Replace permissive rules with explicit tenant-scoped policies. For multi-tenant tables, ensure operations check that the authenticated user owns or belongs to the target row:

-- SECURE: Restrict row access to the authenticated user
ALTER TABLE public.user_profiles ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Users can manage their own profile" 
ON public.user_profiles 
FOR ALL 
TO authenticated 
USING (auth.uid() = user_id) 
WITH CHECK (auth.uid() = user_id);

For detailed policy syntax, refer to the PostgreSQL Row Security Policies Documentation.

2. Insecure Client Sessions vs Server getUser() Verification

A frequent architectural bug in AI-generated Next.js and Remix applications is trusting client-supplied session tokens in server-rendered components.

The vulnerability

AI code generators frequently write server handlers that read sessions directly:

// INSECURE: getSession() does not revalidate the JWT against the auth server
const { data: { session } } = await supabase.auth.getSession();
const userId = session?.user?.id; // Insecure: token signature may be expired or revoked

In serverless environments, getSession() simply decodes the JWT from local storage or cookies without validating whether the token has been revoked, altered, or issued to a banned account.

How to audit and fix

Always authenticate requests using supabase.auth.getUser(), which makes a server-side call to verify the token signature and account validity against the auth backend:

import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";

export async function POST(request: Request) {
  const cookieStore = await cookies();
  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return cookieStore.getAll();
        },
        setAll(cookiesToSet) {
          try {
            cookiesToSet.forEach(({ name, value, options }) =>
              cookieStore.set(name, value, options)
            );
          } catch {
            // Middleware handles session refresh in server components
          }
        },
      },
    }
  );

  // SECURE: getUser() verifies token integrity with the auth service
  const { data: { user }, error: authError } = await supabase.auth.getUser();

  if (authError || !user) {
    return Response.json({ error: "Unauthorized" }, { status: 401 });
  }

  // Safe to proceed with authenticated user ID
  return Response.json({ success: true, userId: user.id });
}

Consult the Supabase Server-Side Auth Guide for comprehensive implementation details across App Router layouts and route handlers.

3. Database Views Silently Bypassing Row-Level Security

AI tools often generate PostgreSQL views to join multiple tables for dashboard reporting or search interfaces.

The vulnerability

By default in PostgreSQL (prior to version 15, or without explicit flags), views execute with the permissions of the view creator (security_definer behavior). When a client queries a view that joins tables with RLS enabled, PostgreSQL checks permissions against the creator, completely bypassing the Row-Level Security policies intended to protect the underlying tables.

How to audit and fix

In PostgreSQL 15 and above, declare all client-facing views with WITH (security_invoker = true). This forces PostgreSQL to evaluate the querying user's active RLS policies when evaluating the view:

-- SECURE: Explicitly enforce querying user's RLS policies on the view
CREATE OR REPLACE VIEW public.customer_dashboard_overview
WITH (security_invoker = true)
AS 
SELECT 
  c.id AS customer_id,
  c.name,
  c.user_id,
  o.id AS order_id,
  o.total_amount
FROM public.customers c
JOIN public.orders o ON o.customer_id = c.id;

To find existing views that lack security_invoker, query the PostgreSQL catalog:

SELECT 
  c.relname AS view_name,
  c.reloptions
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'v' 
  AND n.nspname = 'public';

Ensure security_invoker=true is present in reloptions for all user-accessible views.

4. Multi-Tenant Tenancy Scope Omission in Server Mutations

When building B2B SaaS features, AI code generators routinely miss organization-level authorization checks in API endpoints.

The vulnerability

An AI generator creates an update endpoint for updating project settings. It accepts project_id in the URL or payload, verifies that the caller is logged in, but fails to check whether the authenticated user actually belongs to the organization that owns that project.

// INSECURE: User is authenticated, but tenancy is never checked
export async function PATCH(request: Request) {
  const { user } = await getAuthenticatedUser();
  const { projectId, newTitle } = await request.json();

  // Flaw: Updates project without verifying project belongs to user's tenant!
  await db.update(projects).set({ title: newTitle }).where(eq(projects.id, projectId));
  return Response.json({ success: true });
}

This vulnerability is classified as Broken Object Level Authorization (BOLA), the number one API vulnerability in the OWASP API Security Top 10.

How to audit and fix

Always scope update and delete mutations to the authenticated tenant. In PostgreSQL, enforce this with compound RLS checks:

-- Restrict updates to projects within organizations the user belongs to
CREATE POLICY "Users can update projects in their organization"
ON public.projects
FOR UPDATE
TO authenticated
USING (
  organization_id IN (
    SELECT organization_id 
    FROM public.organization_members 
    WHERE user_id = auth.uid()
  )
);

In server handlers, always validate that the resource belongs to the active tenant before executing mutations.

10-Minute PostgreSQL Security Audit Script

Before launching any AI-generated prototype to production, run this audit script against your database to verify foundational security invariants:

-- Check 1: Tables lacking Row-Level Security
SELECT tablename 
FROM pg_tables 
WHERE schemaname = 'public' 
  AND rowsecurity = false;

-- Check 2: Policies with trivial pass-through rules
SELECT tablename, policyname, qual 
FROM pg_policies 
WHERE schemaname = 'public' 
  AND qual = 'true';

-- Check 3: Public views lacking security_invoker
SELECT c.relname AS view_name
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'v' 
  AND n.nspname = 'public'
  AND (c.reloptions IS NULL OR NOT ('security_invoker=true' = ANY(c.reloptions)));

If any table or view returns results in checks 1 through 3, remediate the policies before deploying to production.

Where to go next

Securing authentication and authorization is the foundational step in preparing AI-generated applications for real users. For a broader overview of technical risks, read our guide on 12 critical security flaws in vibe-coded apps, or explore our runbook for fixing a broken vibe-coded app. If you are preparing to transition prototypes from tools like Lovable, review our checklist on Lovable apps moving to production, inspect our security auditing guide for Cursor-built codebase security reviews, walk through our hands-on Supabase RLS security audit walkthrough, or examine our synthetic teardown lab for multi-tenant SaaS code rescue.

When complex tenancy models or production launch deadlines require specialized hands-on engineering, Aatvi's AI Code Rescue engagement audits your database security, closes authorization bypasses, and establishes reliable CI verification gates.

Source notes

Start here: AI Code Rescue audit and stabilization.