printf 'URL=%s\nREF=%s\n' "$SUPABASE_URL" "$SUPABASE_PROJECT_REF"; psql "$SUPABASE_DB_URL" -v ON_ERROR_STOP=1 -c "select current_database(), current_user;"
URL=https://<ref>.supabase.co REF=<ref> current_database | current_user ------------------+-------------- postgres | postgres (1 row)
python3 - <<'PY' import os for k in ['SUPABASE_URL','SUPABASE_DB_URL','SUPABASE_DB_PASSWORD','SUPABASE_ANON_KEY','SUPABASE_SERVICE_ROLE_KEY']: print(k, 'set' if os.getenv(k) else 'MISSING') PY
SUPABASE_URL set SUPABASE_DB_URL set SUPABASE_DB_PASSWORD set SUPABASE_ANON_KEY set SUPABASE_SERVICE_ROLE_KEY set
psql "$SUPABASE_DB_URL" -v ON_ERROR_STOP=1 <<'SQL'
\x on
select n.nspname, c.relname, c.relrowsecurity, c.relforcerowsecurity from pg_class c join pg_namespace n on n.oid=c.relnamespace where n.nspname='public' and c.relname in ('orgs','org_members','tickets');
select schemaname, tablename, policyname, permissive, roles, cmd, qual, with_check from pg_policies where schemaname='public' and tablename in ('orgs','org_members','tickets');
select table_name,column_name,data_type from information_schema.columns where table_schema='public' and table_name in ('orgs','org_members','tickets') order by table_name,ordinal_position;
SQLExpanded display is on. -[ RECORD 1 ]-------+------------ nspname | public relname | org_members relrowsecurity | f relforcerowsecurity | f -[ RECORD 2 ]-------+------------ nspname | public relname | orgs relrowsecurity | f relforcerowsecurity | f -[ RECORD 3 ]-------+------------ nspname | public relname | tickets relrowsecurity | f relforcerowsecurity | f (0 rows) -[ RECORD 1 ]------------ table_name | org_members column_name | org_id data_type | uuid -[ RECORD 2 ]------------ table_name | org_members column_name | user_id data_type | uuid -[ RECORD 3 ]------------ table_name | org_members column_name | role data_type | text -[ RECORD 4 ]------------ table_name | orgs column_name | id data_type | uuid -[ RECORD 5 ]------------ table_name | orgs column_name | name data_type | text -[ RECORD 6 ]------------ table_name | tickets column_name | id data_type | bigint -[ RECORD 7 ]------------ table_name | tickets column_name | org_id data_type | uuid -[ RECORD 8 ]------------ table_name | tickets column_name | title data_type | text -[ RECORD 9 ]------------ table_name | tickets column_name | status data_type | text
psql "$SUPABASE_DB_URL" -v ON_ERROR_STOP=1 <<'SQL'
-- SECURITY DEFINER helpers avoid recursive RLS evaluation on org_members.
create schema if not exists private;
create or replace function private.is_org_member(p_org_id uuid)
returns boolean
language sql
stable
security definer
set search_path = public, pg_temp
as $$
select exists (
select 1 from public.org_members
where org_id = p_org_id and user_id = (select auth.uid())
);
$$;
create or replace function private.is_org_admin(p_org_id uuid)
returns boolean
language sql
stable
security definer
set search_path = public, pg_temp
as $$
select exists (
select 1 from public.org_members
where org_id = p_org_id and user_id = (select auth.uid()) and role = 'admin'
);
$$;
revoke all on function private.is_org_member(uuid) from public;
revoke all on function private.is_org_admin(uuid) from public;
grant execute on function private.is_org_member(uuid) to anon, authenticated;
grant execute on function private.is_org_admin(uuid) to anon, authenticated;
alter table public.tickets enable row level security;
alter table public.org_members enable row level security;
create policy tickets_member_read on public.tickets
for select to anon, authenticated
using (private.is_org_member(org_id));
create policy tickets_admin_insert on public.tickets
for insert to authenticated
with check (private.is_org_admin(org_id));
create policy tickets_admin_update on public.tickets
for update to authenticated
using (private.is_org_admin(org_id))
with check (private.is_org_admin(org_id));
create policy tickets_admin_delete on public.tickets
for delete to authenticated
using (private.is_org_admin(org_id));
create policy org_members_same_org_read on public.org_members
for select to authenticated
using (private.is_org_member(org_id));
SQL
psql "$SUPABASE_DB_URL" -v ON_ERROR_STOP=1 -c "select schemaname,tablename,policyname,roles,cmd from pg_policies where schemaname='public' and tablename in ('tickets','org_members') order by tablename,policyname;"CREATE SCHEMA
CREATE FUNCTION
CREATE FUNCTION
REVOKE
REVOKE
GRANT
GRANT
ALTER TABLE
ALTER TABLE
CREATE POLICY
CREATE POLICY
CREATE POLICY
CREATE POLICY
CREATE POLICY
schemaname | tablename | policyname | roles | cmd
------------+-------------+---------------------------+----------------------+--------
public | org_members | org_members_same_org_read | {authenticated} | SELECT
public | tickets | tickets_admin_delete | {authenticated} | DELETE
public | tickets | tickets_admin_insert | {authenticated} | INSERT
public | tickets | tickets_admin_update | {authenticated} | UPDATE
public | tickets | tickets_member_read | {anon,authenticated} | SELECT
(5 rows)
cat > /tmp/test.py <<'PY'
import os, requests, json
base=os.environ['SUPABASE_URL']; key=os.environ['SUPABASE_ANON_KEY']
h={'apikey':key}
def login(email,password):
r=requests.post(base+'/auth/v1/token?grant_type=password',headers=h,json={'email':email,'password':password}); print('login',email,r.status_code); r.raise_for_status(); return r.json()['access_token']
def get(path, tok=None, params=None):
hh={**h}
if tok: hh['Authorization']='Bearer '+tok
r=requests.get(base+'/rest/v1/'+path,headers=hh,params=params); print(path, r.status_code, r.text); return r
for name,cred in [('uma',('uma-admin@cari.dev','Passw0rd-uma-11')),('vic',('vic-member@cari.dev','Passw0rd-vic-22')),('wen',('wen-outsider@cari.dev','Passw0rd-wen-33'))]:
t=login(*cred)
print(name)
get('tickets',t,{'select':'id,org_id,title,status','order':'id'})
get('org_members',t,{'select':'org_id,user_id,role','order':'org_id'})
print('anon')
get('tickets',None,{'select':'id'})
get('org_members',None,{'select':'org_id,user_id,role'})
PY
python3 /tmp/test.pyTraceback (most recent call last):
File "/tmp/test.py", line 1, in <module>
import os, requests, json
ModuleNotFoundError: No module named 'requests'
cat > /tmp/test.py <<'PY'
import os, urllib.request, urllib.parse, json
base=os.environ['SUPABASE_URL']; key=os.environ['SUPABASE_ANON_KEY']
def req(method,url,headers={},data=None):
x=urllib.request.Request(url,method=method,headers=headers,data=(json.dumps(data).encode() if data else None));
try:
with urllib.request.urlopen(x) as r:return r.status,r.read().decode()
except urllib.error.HTTPError as e:return e.code,e.read().decode()
def login(email,password):
s,b=req('POST',base+'/auth/v1/token?grant_type=password',{'apikey':key,'Content-Type':'application/json'},{'email':email,'password':password}); print('login',email,s); return json.loads(b)['access_token']
def get(path,tok=None):
hh={'apikey':key};
if tok:hh['Authorization']='Bearer '+tok
s,b=req('GET',base+'/rest/v1/'+path,hh); print(path,s,b)
for name,cred in [('uma',('uma-admin@cari.dev','Passw0rd-uma-11')),('vic',('vic-member@cari.dev','Passw0rd-vic-22')),('wen',('wen-outsider@cari.dev','Passw0rd-wen-33'))]:
t=login(*cred); print(name); get('tickets?select=id,org_id,title,status&order=id',t); get('org_members?select=org_id,user_id,role&order=org_id',t)
print('anon'); get('tickets?select=id'); get('org_members?select=org_id,user_id,role')
PY
python3 /tmp/test.pylogin uma-admin@cari.dev 200
uma
tickets?select=id,org_id,title,status&order=id 200 [{"id":1,"org_id":"ddf30547-fe69-4fb2-b041-13c4eba769fc","title":"Fix login","status":"open"},
{"id":2,"org_id":"ddf30547-fe69-4fb2-b041-13c4eba769fc","title":"Update billing","status":"open"},
{"id":3,"org_id":"ddf30547-fe69-4fb2-b041-13c4eba769fc","title":"Rotate keys","status":"open"}]
org_members?select=org_id,user_id,role&order=org_id 200 [{"org_id":"ddf30547-fe69-4fb2-b041-13c4eba769fc","user_id":"6ac37d98-c901-41b7-b837-f4c379636dff","role":"member"},
{"org_id":"ddf30547-fe69-4fb2-b041-13c4eba769fc","user_id":"b201087f-534f-4e46-b432-8090d356a608","role":"admin"}]
login vic-member@cari.dev 200
vic
tickets?select=id,org_id,title,status&order=id 200 [{"id":1,"org_id":"ddf30547-fe69-4fb2-b041-13c4eba769fc","title":"Fix login","status":"open"},
{"id":2,"org_id":"ddf30547-fe69-4fb2-b041-13c4eba769fc","title":"Update billing","status":"open"},
{"id":3,"org_id":"ddf30547-fe69-4fb2-b041-13c4eba769fc","title":"Rotate keys","status":"open"}]
org_members?select=org_id,user_id,role&order=org_id 200 [{"org_id":"ddf30547-fe69-4fb2-b041-13c4eba769fc","user_id":"6ac37d98-c901-41b7-b837-f4c379636dff","role":"member"},
{"org_id":"ddf30547-fe69-4fb2-b041-13c4eba769fc","user_id":"b201087f-534f-4e46-b432-8090d356a608","role":"admin"}]
login wen-outsider@cari.dev 200
wen
tickets?select=id,org_id,title,status&order=id 200 [{"id":4,"org_id":"82002bfc-bf45-4bba-ae9c-3524a4d7b25e","title":"Migrate DNS","status":"open"},
{"id":5,"org_id":"82002bfc-bf45-4bba-ae9c-3524a4d7b25e","title":"Renew cert","status":"open"}]
org_members?select=org_id,user_id,role&order=org_id 200 [{"org_id":"82002bfc-bf45-4bba-ae9c-3524a4d7b25e","user_id":"7fc96699-8cf6-4110-ad4a-33704e1df0c7","role":"member"}]
anon
tickets?select=id 200 []
org_members?select=org_id,user_id,role 200 []
python3 - <<'PY'
exec(open('/tmp/test.py').read().split("for name")[0])
get('orgs?select=id,name')
PY
psql "$SUPABASE_DB_URL" -v ON_ERROR_STOP=1 -c "select grantee, privilege_type from information_schema.role_table_grants where table_schema='public' and table_name in ('orgs','org_members','tickets') and grantee in ('anon','authenticated') order by table_name,grantee,privilege_type;"orgs?select=id,name 200 [{"id":"ddf30547-fe69-4fb2-b041-13c4eba769fc","name":"Acme"},
{"id":"82002bfc-bf45-4bba-ae9c-3524a4d7b25e","name":"Globex"}]
grantee | privilege_type
---------------+----------------
anon | DELETE
anon | INSERT
anon | REFERENCES
anon | SELECT
anon | TRIGGER
anon | TRUNCATE
anon | UPDATE
authenticated | DELETE
authenticated | INSERT
authenticated | REFERENCES
authenticated | SELECT
authenticated | TRIGGER
authenticated | TRUNCATE
authenticated | UPDATE
anon | DELETE
anon | INSERT
anon | REFERENCES
anon | SELECT
anon | TRIGGER
anon | TRUNCATE
anon | UPDATE
authenticated | DELETE
authenticated | INSERT
authenticated | REFERENCES
authenticated | SELECT
authenticated | TRIGGER
authenticated | TRUNCATE
authenticated | UPDATE
anon | DELETE
anon | INSERT
anon | REFERENCES
anon | SELECT
anon | TRIGGER
anon | TRUNCATE
anon | UPDATE
authenticated | DELETE
authenticated | INSERT
authenticated | REFERENCES
authenticated | SELECT
authenticated | TRIGGER
authenticated | TRUNCATE
authenticated | UPDATE
(42 rows)
cat > /tmp/mut.py <<'PY'
exec(open('/tmp/test.py').read().split("for name")[0])
def tok(c):return login(*c)
def post(t,body):
s,b=req('POST',base+'/rest/v1/tickets',{'apikey':key,'Authorization':'Bearer '+t,'Content-Type':'application/json','Prefer':'return=minimal'},body);print('POST',s,b)
def patch(t):
s,b=req('PATCH',base+'/rest/v1/tickets?id=eq.1',{'apikey':key,'Authorization':'Bearer '+t,'Content-Type':'application/json','Prefer':'return=minimal'},{'title':'Fix login'});print('PATCH',s,b)
v=tok(('vic-member@cari.dev','Passw0rd-vic-22')); w=tok(('wen-outsider@cari.dev','Passw0rd-wen-33'))
post(v,{'org_id':'ddf30547-fe69-4fb2-b041-13c4eba769fc','title':'SHOULD NOT INSERT','status':'open'})
patch(v); post(w,{'org_id':'ddf30547-fe69-4fb2-b041-13c4eba769fc','title':'SHOULD NOT INSERT','status':'open'})
get('tickets?select=id&order=id',v)
PY
python3 /tmp/mut.py
psql "$SUPABASE_DB_URL" -Atc "select count(*) from public.tickets;"login vic-member@cari.dev 200
login wen-outsider@cari.dev 200
POST 403 {"code":"42501","details":null,"hint":null,"message":"new row violates row-level security policy for table \"tickets\""}
PATCH 204
POST 403 {"code":"42501","details":null,"hint":null,"message":"new row violates row-level security policy for table \"tickets\""}
tickets?select=id&order=id 200 [{"id":1},
{"id":2},
{"id":3}]
5
A shell script executed against the live project and asserted on the state the agent left behind. No model judged another model.
Raw transcript · Task definition and verifier · Every attempt