Supahound is in early access. Sign up now; features marked Coming soon switch on as they ship. See what's included

Supahound

Cursor security guide

How to use Cursor with Supabase without leaking data

In Cursor, install Supabase's own plugin or add its server to .cursor/mcp.json, then limit it to one development project in read-only mode. The plugin's skills help, but they do not review your SQL. Keep service_role out of the connection, ask for RLS in the same migration as each table, read every policy before it runs, and grep the build for secret keys.

Last updated

How do I connect Supabase to Cursor safely?

Supabase's MCP guide names prompt injection as the main LLM-specific attack: text in your data that the agent treats as an instruction. Project scoping and read-only mode limit what an injected instruction can reach.

  1. Install the Supabase plugin from the Cursor Marketplace with Add to Cursor, then sign in with OAuth. Supabase created the plugin and Cursor verified it. It bundles the MCP server and Supabase's skills.
  2. Or add Supabase's hosted server, https://mcp.supabase.com/mcp, to .cursor/mcp.json as Supabase's MCP guide shows.
  3. Scope the server to one project, ideally a development project.
  4. Turn on read-only mode for routine work. Connect to production only when a task needs it.

Why should Cursor's agent never connect as service_role?

service_role has BYPASSRLS. An agent connected that way can read every table, and so can any instruction hidden in the rows it reads. The General Analysis demonstration showed exactly this: a support ticket told Cursor's agent to copy a private token table into the ticket thread, and it did.

  • Keep sb_secret_ and legacy service_role keys out of .cursor/mcp.json and out of every prompt.
  • Do not let an agent with write access read support tickets, comments or other text your users submit.
  • Use the MCP server's OAuth sign-in instead of pasting keys.

How do I keep secret keys out of a Cursor project's client code?

Only publishable keys (sb_publishable_) belong in the browser. Anything in a NEXT_PUBLIC_, VITE_ or EXPO_PUBLIC_ variable is exposed to the client. Run these two checks after every agent session that touched config.

# List files that put a secret-looking name behind a client prefix.
grep -rlE "(NEXT_PUBLIC_|VITE_|EXPO_PUBLIC_)[A-Z0-9_]*(SECRET|SERVICE_ROLE)" . --exclude-dir=node_modules 2>/dev/null

# Search the build output for a secret key before you deploy.
grep -rlE "sb_secret_|service_role" .next/static/ dist/ build/ 2>/dev/null
Both commands should print nothing.

What should I ask Cursor for when it writes a migration?

Tables created with SQL do not get RLS automatically, so say so in the prompt. Ask for RLS in the same migration as the table, one policy per command, and a with check on writes.

Write a migration for a documents table owned by a user.
Enable row level security in the same migration.
Add one policy per command for the authenticated role,
using (select auth.uid()) = owner_id, with a with check on insert and update.
Do not use the service_role key and do not create views.
A prompt you can paste into Cursor's agent.
create table public.documents (
  id uuid primary key default gen_random_uuid(),
  owner_id uuid not null references auth.users (id) on delete cascade,
  body text not null
);
alter table public.documents enable row level security;

create policy "documents: owner reads" on public.documents
  for select to authenticated
  using ((select auth.uid()) = owner_id);

create policy "documents: owner writes" on public.documents
  for insert to authenticated
  with check ((select auth.uid()) = owner_id);

create policy "documents: owner updates" on public.documents
  for update to authenticated
  using ((select auth.uid()) = owner_id)
  with check ((select auth.uid()) = owner_id);
What a correct result looks like.

How do I review the policies Cursor writes before I apply them?

  1. Read every create policy statement. Each one should name a role with to authenticated or to anon, never leave it open.
  2. Check that every update policy has both using and with check.
  3. Check that every new view is created with security_invoker = true.
  4. Apply the migration to a development project and run the query below. It must return zero rows.
  5. Ask the agent to fetch Supabase's advisors over MCP, or open Security Advisor in the dashboard, and fix every ERROR.
-- Every table in public without RLS. Expect zero rows.
select c.relname
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public' and c.relkind = 'r' and not c.relrowsecurity;

Frequently asked questions

Does the Supabase plugin for Cursor make my database safe?
It gives Cursor's agent Supabase's skills, including Postgres best practices, and access to advisors through MCP. It does not approve your policies or stop a bad migration. You still read each policy before it runs.
Was the General Analysis demo a breach of Cursor users?
No. It was a research demonstration with no reported customer breach. It showed what happens when an agent with service_role access reads text an attacker controls. Read-only mode, project scoping and a connection without service_role all shrink that risk.
What should I check before I deploy a Cursor-built app?
Run the RLS-off query and expect zero rows, grep the build for sb_secret_ and service_role, and confirm every view uses security_invoker. Supahound's free posture scan repeats the RLS and view checks daily after launch.

Find out if someone is abusing your app

Connect your Supabase org read-only. The free posture scan runs in minutes. No credit card.

Sources

Checked on . Products change; check each source for current details.