25 mistakes AI coding tools still make in production.
The exact methodology we use on every engagement, freely available. Six categories, twenty-five concrete vulnerabilities: why they happen, how to test for them, how to fix them.
Row-Level Security (RLS) and data access control
RLS disabled on a tableCritical
Why it happens
Supabase disables RLS by default on new tables. A vibe-coder who doesn't know this detail exposes the entire table to anyone holding the anon key, which is public by design.
How to test
SELECT tablename FROM pg_tables WHERE schemaname = 'public' AND rowsecurity = false;
How to fix
ALTER TABLE table_name ENABLE ROW LEVEL SECURITY; then write explicit policies.
Typical severity
Critical
Overly permissive RLS policy (USING (true))Critical
Why it happens
During development, a USING (true) makes permission errors disappear. Convenient to move fast, forgotten before launch.
How to test
Manually audit every policy in the Supabase dashboard, or via SELECT * FROM pg_policies;
How to fix
Replace it with a condition tied to auth.uid(), for example USING (auth.uid() = user_id).
Typical severity
Critical
Policy checked for SELECT but not for UPDATE or DELETEHigh
Why it happens
AI generators often create a correct read policy but forget to duplicate the same logic for the other operations. INSERT, UPDATE, and DELETE each need their own policy.
How to test
Create two test accounts, attempt an UPDATE or DELETE query from account A on a row owned by account B, using the anon key.
How to fix
One policy per operation (FOR SELECT, FOR UPDATE, FOR DELETE, FOR INSERT), never a single loosely-scoped FOR ALL policy.
Typical severity
High
Foreign key relationships bypassing policiesHigh
Why it happens
A protected table references an unprotected table. An attacker goes through the weaker table to reach the protected table's data via a join.
How to test
Map every foreign key, and check RLS on each related table, not just the main one.
How to fix
RLS enabled and consistent across the entire schema, not reviewed table by table in isolation.
Typical severity
High
Poorly scoped RPC functions (SECURITY DEFINER)Critical
Why it happens
PostgreSQL functions exposed through the Supabase API with SECURITY DEFINER run with the creator's privileges, not the caller's. This bypasses RLS if written carelessly.
How to test
List every exposed RPC function and check whether it properly filters by the calling user.
How to fix
Add an explicit auth.uid() check inside the function; never assume RLS applies automatically.
Typical severity
Critical
Exposed secrets and keys
service_role key used on the clientCritical
Why it happens
The service_role key bypasses RLS entirely. A developer in a hurry uses it to make things work during testing, and it's left in the final code.
How to test
Grep the source code and the shipped JS bundle for "service_role" or the Supabase JWT key pattern.
How to fix
The service_role key should only ever exist server-side (Edge Functions, API routes), never in a variable exposed to the browser.
Typical severity
Critical
Third-party API keys exposed in the client bundleCritical
Why it happens
Confusion between public (NEXT_PUBLIC_*) and private environment variables. A wrong prefix exposes a secret key to the browser.
How to test
Inspect the final JS bundle for key patterns (sk_live_, sk-, and similar).
How to fix
Every secret key must stay free of the NEXT_PUBLIC_ prefix, called only from the server.
Typical severity
Critical
Secrets committed to Git historyHigh
Why it happens
A .env file added by mistake in an early commit, then deleted, but it stays in Git history and remains retrievable.
How to test
Scan the full history with gitleaks or trufflehog, not just the current code.
How to fix
Immediately rotate any key found (change the key, not just remove it from the code), and get .gitignore right from the start.
Typical severity
High to critical depending on the key
Environment variables missing from .env.exampleLow
Why it happens
Without clear documentation of which variables are public versus private, every new environment repeats the same mistakes.
How to test
Check for consistency between .env.example, the project documentation, and actual usage in the code.
How to fix
Explicitly document every variable with a "PUBLIC" or "SECRET, SERVER ONLY" comment.
Typical severity
Low, but an aggravating factor for other vulnerabilities
Storage buckets
Storage bucket public by defaultHigh
Why it happens
Supabase Storage offers public buckets to simplify displaying images. A developer picks "public" to make things work fast, including for sensitive documents.
How to test
List every bucket and its public or private status, then attempt direct URL access with no authentication.
How to fix
Private buckets by default, plus a signed access policy (createSignedUrl) for any content not meant for the general public.
Typical severity
High
Predictable file URLsMedium
Why it happens
Naming uploaded files with an auto-incremented ID or the original, non-random filename makes it possible to guess or enumerate other users' files.
How to test
Upload a file, observe the naming pattern, try incrementing or guessing other URLs.
How to fix
Random UUID filenames, never tied to a guessable internal ID.
Typical severity
Medium to high depending on content sensitivity
Missing bucket policies despite a "private" bucketHigh
Why it happens
Marking a bucket as private in the interface isn't enough. Without an RLS policy on storage.objects, access can remain poorly controlled depending on configuration.
How to test
Check RLS policies specifically on the storage schema, not just on business tables.
How to fix
Explicit policies on storage.objects tied to auth.uid() or the file owner.
Typical severity
High
Authentication and session management
No rate limiting on loginHigh
Why it happens
AI-generated templates rarely implement anti-brute-force protection by default. It's invisible until someone attacks it.
How to test
Script a series of rapid login attempts and observe whether a lockout or delay kicks in.
How to fix
Rate limiting at the middleware level or through a dedicated service (Supabase Auth offers options, otherwise Upstash or Redis).
Typical severity
High
Insecure password resetHigh
Why it happens
Reset token too short, non-expiring, or reusable multiple times.
How to test
Generate a reset link, verify its actual expiration, and try reusing it after it's been used once.
How to fix
Single-use token, short expiration (15 to 30 minutes), invalidated after use.
Typical severity
High
Roles and permissions checked only on the clientCritical
Why it happens
Hiding an "Admin" button in the interface gives the illusion of security, but the underlying API remains directly accessible.
How to test
Call admin endpoints or actions directly via an HTTP client using a non-admin account, bypassing the interface entirely.
How to fix
Every role check must be duplicated server-side (API route, Edge Function, or RLS policy), never only as a React conditional.
Typical severity
Critical
Session or JWT passed in the URLHigh
Why it happens
A debugging or implementation shortcut: a token passed as a query parameter instead of a secure header or cookie.
How to test
Watch network requests during authentication and look for tokens in query parameters.
How to fix
Tokens only in an Authorization header or an httpOnly, secure cookie, never in a URL.
Typical severity
High
No email verification before account activationMedium
Why it happens
Simplifies onboarding during development, forgotten in the production configuration.
How to test
Create an account with an unverified email and try accessing the product's features.
How to fix
Enable mandatory email confirmation in the authentication settings before public launch.
Typical severity
Medium
API surface and input validation
Endpoints or Edge Functions with no authentication checkCritical
Why it happens
An endpoint created for an internal test, never protected, forgotten in production.
How to test
List every deployed endpoint and function, and test each one with no authentication header.
How to fix
Systematic authentication middleware, with an explicit check at the start of every function.
Typical severity
Critical
No server-side input validationHigh
Why it happens
Validation is done on the React side (forms) but never duplicated on the API side. An attacker bypasses the interface and sends data directly.
How to test
Send malformed or unexpected payloads directly to the API (wrong types, missing fields, out-of-range values).
How to fix
Systematic server-side validation with a library like Zod, never blind trust in incoming data.
Typical severity
High
Overly permissive CORS configurationMedium
Why it happens
Access-Control-Allow-Origin set to a wildcard quickly resolves CORS errors during development, and is left that way in production.
How to test
Inspect the response headers of the API endpoints.
How to fix
An explicit allow-list of authorized domains in production.
Typical severity
Medium
Insecure direct object references (IDOR) on REST endpointsCritical
Why it happens
An endpoint like /api/orders/123 doesn't check that the logged-in user actually owns order 123, only that the ID exists.
How to test
Access another user's resources by simply changing the ID in the URL or request.
How to fix
Systematic ownership checks on every request, never relying on the resource ID alone.
Typical severity
Critical
Third-party integrations and AI-specific risks
Webhooks with no signature verificationHigh
Why it happens
A quickly built webhook handler processes any request it receives at the URL without checking that it genuinely came from the provider.
How to test
Send a forged request to the webhook endpoint with no valid signature and observe whether it gets processed.
How to fix
Systematically verify the cryptographic signature provided by the third-party service before any processing.
Typical severity
High to critical, direct financial impact if it's Stripe
Prompt injection on AI featuresMedium
Why it happens
User input is directly concatenated into a prompt sent to an LLM, letting the user manipulate the system instructions.
How to test
Try injecting instructions like "ignore the previous instructions and..." into fields that feed an AI prompt.
How to fix
A clear separation between system instructions and user input, input validation and filtering, and limits on what actions the LLM can trigger automatically.
Typical severity
Medium to high depending on what actions the LLM can access
LLM API key called from the clientHigh
Why it happens
To prototype quickly, the AI API call is made directly from the front-end component instead of through a server route. This exposes the key and allows uncontrolled usage.
How to test
Inspect the browser's network requests while using an AI feature and look for direct calls to third-party AI APIs.
How to fix
Every AI generation call goes through a server route that holds the key, with per-user usage limits.
Typical severity
High, financial risk if the key is abused
Outdated or vulnerable npm dependenciesVaries by vulnerability
Why it happens
AI tools generate working code but don't proactively update dependencies, some of which carry known vulnerabilities.
How to test
Run npm audit or pnpm audit regularly, and compare against the known vulnerability database.
How to fix
Regular updates, npm audit fix, and continuous monitoring through a tool like Dependabot.
Typical severity
Varies by vulnerability, from low to critical
Just spotted one of these in your own project?
This list is our testing methodology, condensed. A full review goes further: manual access, prioritization by business impact, and a report you can hand straight to your team.