The Supabase service_role key and AI agents: why it skips every policy

An AI agent holding a Supabase secret or service_role key reads and writes past every row-level security policy. Here is what the key is, a local test of the mechanism, and what to hand the agent instead.

The short answer

Don’t give an AI agent your Supabase service_role key or a new-style secret key. Supabase’s secret keys authorize requests through the built-in service_role Postgres role, which has the BYPASSRLS attribute, so it skips every Row Level Security policy on every table.1 Your policies are the thing that limits what a client can reach. With that key, the agent meets none of them.

Give the agent its own narrow Postgres role or a user-scoped token instead, and keep the secret key in server code you wrote and reviewed.

Deedglass reads what Postgres actually committed, not what the agent reported. In our demo a hidden trigger turned 1 row into 301; a person countersigned the undo and all 301 rows came back.

What the key actually is

Supabase hands out two kinds of key. Publishable keys (and the legacy anon key) are meant to be public: anyone can read one, so it only reaches what Row Level Security allows. Secret keys (and the legacy JWT service_role key) are the opposite. Supabase’s docs describe them as giving full access to a project’s data, and say never to put one in a browser, a shipped application or source control.1

Two details matter for agents:

  • A secret key bypasses RLS only when the request carries no user access token. If a request also carries a signed-in user’s token, that user’s policies apply.2 A backend script or an agent tool that sends just the key gets the full bypass.
  • Supabase blocks secret keys in browsers. It matches on the User-Agent header and answers 401.1 That check protects web pages. It does nothing for a coding agent, an MCP server or a script running in a terminal, which is exactly where agents run.

Supabase is retiring the legacy anon and service_role keys by the end of 2026, and creating new keys doesn’t revoke the old ones: they stay valid until you disable them.1 So a project can have both a new secret key and an old service_role JWT live at the same time, and an agent may hold either.

The mechanism in plain Postgres

Nothing here is special to Supabase. In PostgreSQL, superusers and roles with BYPASSRLS always bypass row security, and table owners do too unless the table has FORCE ROW LEVEL SECURITY.3 We ran this on a local PostgreSQL 18.6 to see it, with one table, one policy and two roles:

Code A database command in SQL. The text around it says what it does.

create role app_user nologin;
create role svc nologin bypassrls;
create table notes(id int primary key, owner text, body text);
insert into notes values (1,'alice','a1'),(2,'bob','b1'),(3,'bob','b2');
alter table notes enable row level security;
create policy own on notes using (owner = current_setting('app.uid', true));
grant select on notes to app_user, svc;

set app.uid = 'alice';
set role app_user;
select 'table as app_user' as q, count(*) from notes;         -- the policy applies
reset role;
set role svc;
select 'table as bypassrls role' as q, count(*) from notes;   -- BYPASSRLS

The two results, as psql printed them:

Code What the screen or file shows, copied as is.

         q         | count
-------------------+-------
 table as app_user |     1

            q            | count
-------------------------+-------
 table as bypassrls role |     3

Same table, same session setting, same grant. The role with BYPASSRLS saw all three rows. That is what an agent holding the secret key sees on every table you protected with a policy.

Where agents pick up the key

The key rarely gets handed to an agent on purpose. It arrives by convenience:

How it happens Why it’s easy to miss
SUPABASE_SERVICE_ROLE_KEY sits in .env, and a coding agent reads the file or runs code that loads it The agent never prints the key; it just uses it
An MCP server or tool is configured with the secret key “so it works” Every tool call now runs past RLS
A seed or admin script in the repo uses the key, and the agent runs it to “fix” data The script was written for a person running it once
Edge Function code reads the key from the environment Supabase’s docs advise against reading the key from the environment inside an Edge Function1

The July 2025 Supabase MCP demonstration used this exact shape: an assistant connected with a service_role credential read a table it should never have reached. Our write-up of that demo goes through the primary sources.

What to give the agent instead

Pick the narrowest option that still does the job:

  1. The publishable key plus a real user’s session. The agent acts as that user, and that user’s policies apply. Good for agents acting on behalf of one person.
  2. A dedicated Postgres role. Create a role for the agent with only the grants it needs, no BYPASSRLS, and policies written for it. Supabase’s own docs warn never to share login credentials for any role with the bypass RLS privilege.2 Our post on safe Postgres access for agents shows a tested role, row-level security and limits.
  3. A development project or branch. If the agent truly needs broad access, give it broad access to data that doesn’t matter. The Supabase MCP checklist covers project scoping and read-only mode.
  4. A small server endpoint you wrote. If one task needs elevated access, put that task behind your own function, which holds the key, checks the input and does one thing. The agent calls the function, never the key.

Also check the tables themselves. Supabase’s docs say a table in an exposed schema without RLS is readable and writable by any role with a grant on it, and ask you to enable RLS on every table in an exposed schema.2 An agent with only the publishable key still reaches a table you forgot.

If the key already leaked

Supabase’s own order of work is worth following: fix the cause of the leak first, then create a new secret key, replace the old one everywhere, and only then retire the compromised key. Deleting a secret key can’t be undone; deactivating legacy keys can be reversed if you find a client you missed. Supabase also advises a separate secret key for each backend component, so one leak doesn’t force you to rotate everything.1

For an agent, “the cause” usually means its environment: the .env it can read, the MCP config that holds the key, or the shell history it can search. Remove the key from those places before rotating, or the agent picks up the new one.

A checklist

  • No secret or service_role key in any file the agent can read (.env, MCP config, shell profile).
  • No MCP server or agent tool configured with a secret key.
  • Legacy service_role JWT deactivated once nothing uses it.
  • One secret key per backend component, named for it.
  • RLS on every table in an exposed schema.
  • The agent’s own role has no BYPASSRLS and doesn’t own the tables it touches (owners bypass policies unless forced).3
  • Production data reached only through a narrow role or your own endpoint.

To see whether your agents can read a database URL from .env or their client config, paste the agent’s MCP config into the check page or run npx deedglass scan (how the grade works); the scan Action runs the same scan on pull requests. To try the checks in your browser without installing anything, open the playground.

Questions people ask

Is the Supabase service_role key the same as the secret key?

They do the same job. The JWT service_role key is the legacy form, and the new secret keys are short strings that Supabase recommends instead. Both authorize through the service_role Postgres role.1

Does Row Level Security protect me if an agent has the secret key?

No. service_role has BYPASSRLS, so policies never apply to a request made with the secret key alone.1

Is a publishable key safe to give an agent?

Safer, not safe by itself. It reaches only what your policies allow, so the policies and the tables without RLS decide what the agent can do.2

Written with AI assistance and checked against our claims register.

The takeaway

A Supabase secret or service_role key acts as a BYPASSRLS role: in our local test it read 3 of 3 rows where the policy allowed 1. Give agents a narrow role.

Read nextIs Supabase MCP safe? A 10-point setup checklist, and what read-only mode doesn't stop Try it: install in 7 s

Related on this site

More posts

Sources

  1. 1Supabase docs: Understanding API keys · supabase.com · checked 26 October 2026
  2. 2Supabase docs: Row Level Security · supabase.com · checked 26 October 2026
  3. 3PostgreSQL docs: Row Security Policies · postgresql.org · checked 26 October 2026

Published 26 October 2026, sources last checked 26 October 2026. If something here is out of date, write to hello@deedglass.com.

Try it on your own setup

npx deedglass scan

Grades only your own setup. Nothing is uploaded.

Pricing