echo "URL: $SUPABASE_URL"; echo "REF: $SUPABASE_PROJECT_REF"; echo "DB_URL set: ${SUPABASE_DB_URL:+yes}"; which psql || echo "no psql"URL: https://<ref>.supabase.co REF: <ref> DB_URL set: yes /usr/bin/psql
psql "$SUPABASE_DB_URL" -c "\d public.notes" 2>&1; echo "---RLS---"; psql "$SUPABASE_DB_URL" -c "SELECT relrowsecurity, relforcerowsecurity FROM pg_class WHERE oid='public.notes'::regclass;" 2>&1; echo "---POLICIES---"; psql "$SUPABASE_DB_URL" -c "SELECT policyname, cmd, roles, qual, with_check FROM pg_policies WHERE schemaname='public' AND tablename='notes';" 2>&1; echo "---GRANTS---"; psql "$SUPABASE_DB_URL" -c "SELECT grantee, privilege_type FROM information_schema.role_table_grants WHERE table_schema='public' AND table_name='notes';" 2>&1
Table "public.notes"
Column | Type | Collation | Nullable | Default
---------+--------+-----------+----------+----------------------------------
id | bigint | | not null | generated by default as identity
user_id | uuid | | not null |
title | text | | not null |
body | text | | not null | ''::text
Indexes:
"notes_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
"notes_user_id_fkey" FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE
---RLS---
relrowsecurity | relforcerowsecurity
----------------+---------------------
f | f
(1 row)
---POLICIES---
policyname | cmd | roles | qual | with_check
------------+-----+-------+------+------------
(0 rows)
---GRANTS---
grantee | privilege_type
---------------+----------------
postgres | INSERT
postgres | SELECT
postgres | UPDATE
postgres | DELETE
postgres | TRUNCATE
postgres | REFERENCES
postgres | TRIGGER
anon | INSERT
anon | SELECT
anon | UPDATE
anon | DELETE
anon | TRUNCATE
anon | REFERENCES
anon | TRIGGER
authenticated | INSERT
authenticated | SELECT
authenticated | UPDATE
authenticated | DELETE
authenticated | TRUNCATE
authenticated | REFERENCES
authenticated | TRIGGER
service_role | INSERT
service_role | SELECT
service_role | UPDATE
service_role | DELETE
service_role | TRUNCATE
service_role | REFERENCES
service_role | TRIGGER
(28 rows)
echo "=== anon sees all rows (vulnerable) ==="; curl -s "$SUPABASE_URL/rest/v1/notes?select=id,user_id,title" -H "apikey: $SUPABASE_ANON_KEY" | head -c 1000; echo
=== anon sees all rows (vulnerable) ===
[{"id":1,"user_id":"9dc9142e-314f-4ab6-aa66-b38a05f42de0","title":"Groceries"},
{"id":2,"user_id":"9dc9142e-314f-4ab6-aa66-b38a05f42de0","title":"Call plumber"},
{"id":3,"user_id":"db26f1f9-f973-4159-80ed-5adb76135cad","title":"Gym plan"},
{"id":4,"user_id":"db26f1f9-f973-4159-80ed-5adb76135cad","title":"Trip ideas"}]
psql "$SUPABASE_DB_URL" <<'SQL' BEGIN; ALTER TABLE public.notes ENABLE ROW LEVEL SECURITY; DROP POLICY IF EXISTS notes_select_own ON public.notes; DROP POLICY IF EXISTS notes_insert_own ON public.notes; DROP POLICY IF EXISTS notes_update_own ON public.notes; DROP POLICY IF EXISTS notes_delete_own ON public.notes; -- SELECT: only rows owned by the current authenticated user CREATE POLICY notes_select_own ON public.notes FOR SELECT TO authenticated USING (user_id = (SELECT auth.uid())); -- INSERT: can only create rows with their own user_id CREATE POLICY notes_insert_own ON public.notes FOR INSERT TO authenticated WITH CHECK (user_id = (SELECT auth.uid())); -- UPDATE: can only update own rows, and cannot reassign to another user CREATE POLICY notes_update_own ON public.notes FOR UPDATE TO authenticated USING (user_id = (SELECT auth.uid())) WITH CHECK (user_id = (SELECT auth.uid())); -- DELETE: can only delete own rows CREATE POLICY notes_delete_own ON public.notes FOR DELETE TO authenticated USING (user_id = (SELECT auth.uid())); COMMIT; SQL echo "---verify---" psql "$SUPABASE_DB_URL" -c "SELECT relrowsecurity FROM pg_class WHERE oid='public.notes'::regclass;" psql "$SUPABASE_DB_URL" -c "SELECT policyname, cmd, roles::text, qual, with_check FROM pg_policies WHERE tablename='notes' ORDER BY policyname;"
BEGIN
ALTER TABLE
DROP POLICY
DROP POLICY
DROP POLICY
DROP POLICY
CREATE POLICY
CREATE POLICY
CREATE POLICY
CREATE POLICY
COMMIT
---verify---
relrowsecurity
----------------
t
(1 row)
policyname | cmd | roles | qual | with_check
------------------+--------+-----------------+-----------------------------------------+-----------------------------------------
notes_delete_own | DELETE | {authenticated} | (user_id = ( SELECT auth.uid() AS uid)) |
notes_insert_own | INSERT | {authenticated} | | (user_id = ( SELECT auth.uid() AS uid))
notes_select_own | SELECT | {authenticated} | (user_id = ( SELECT auth.uid() AS uid)) |
notes_update_own | UPDATE | {authenticated} | (user_id = ( SELECT auth.uid() AS uid)) | (user_id = ( SELECT auth.uid() AS uid))
(4 rows)
NOTICE: policy "notes_select_own" for relation "public.notes" does not exist, skipping NOTICE: policy "notes_insert_own" for relation "public.notes" does not exist, skipping NOTICE: policy "notes_update_own" for relation "public.notes" does not exist, skipping NOTICE: policy "notes_delete_own" for relation "public.notes" does not exist, skipping
login() {
curl -s "$SUPABASE_URL/auth/v1/token?grant_type=password" \
-H "apikey: $SUPABASE_ANON_KEY" -H "Content-Type: application/json" \
-d "{\"email\":\"$1\",\"password\":\"$2\"}" | python3 -c "import sys,json;print(json.load(sys.stdin)['access_token'])"
}
ALICE=$(login alice@cari.dev Passw0rd-alice-42)
BOB=$(login bob@cari.dev Passw0rd-bob-58)
echo "alice token: ${ALICE:0:15}..."; echo "bob token: ${BOB:0:15}..."
ALICE_ID="9dc9142e-314f-4ab6-aa66-b38a05f42de0"
BOB_ID="db26f1f9-f973-4159-80ed-5adb76135cad"
echo; echo "=== TEST 1: anon sees no rows ==="
curl -s "$SUPABASE_URL/rest/v1/notes?select=id,title" -H "apikey: $SUPABASE_ANON_KEY"
echo; echo; echo "=== TEST 2: alice reads only her own ==="
curl -s "$SUPABASE_URL/rest/v1/notes?select=id,user_id,title" -H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $ALICE"
echo; echo; echo "=== TEST 3: bob reads only his own ==="
curl -s "$SUPABASE_URL/rest/v1/notes?select=id,user_id,title" -H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $BOB"
echo; echo; echo "=== TEST 4: alice inserts own note (should succeed) ==="
curl -s -w "\nHTTP:%{http_code}\n" "$SUPABASE_URL/rest/v1/notes" -X POST \
-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $ALICE" \
-H "Content-Type: application/json" -H "Prefer: return=representation" \
-d "{\"user_id\":\"$ALICE_ID\",\"title\":\"__test_own\",\"body\":\"x\"}"
echo; echo "=== TEST 5: alice inserts as BOB (should fail) ==="
curl -s -w "\nHTTP:%{http_code}\n" "$SUPABASE_URL/rest/v1/notes" -X POST \
-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $ALICE" \
-H "Content-Type: application/json" -H "Prefer: return=representation" \
-d "{\"user_id\":\"$BOB_ID\",\"title\":\"__evil\",\"body\":\"x\"}"
echo; echo "=== TEST 6: alice updates bob's note id=3 (should affect 0 rows) ==="
curl -s -w "\nHTTP:%{http_code}\n" "$SUPABASE_URL/rest/v1/notes?id=eq.3" -X PATCH \
-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $ALICE" \
-H "Content-Type: application/json" -H "Prefer: return=representation" \
-d "{\"title\":\"hacked\"}"
echo; echo "=== TEST 7: alice deletes bob's note id=3 (should affect 0 rows) ==="
curl -s -w "\nHTTP:%{http_code}\n" "$SUPABASE_URL/rest/v1/notes?id=eq.3" -X DELETE \
-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $ALICE" \
-H "Prefer: return=representation"alice token: eyJhbGciOiJFUzI...
bob token: eyJhbGciOiJFUzI...
=== TEST 1: anon sees no rows ===
[]
=== TEST 2: alice reads only her own ===
[{"id":1,"user_id":"9dc9142e-314f-4ab6-aa66-b38a05f42de0","title":"Groceries"},
{"id":2,"user_id":"9dc9142e-314f-4ab6-aa66-b38a05f42de0","title":"Call plumber"}]
=== TEST 3: bob reads only his own ===
[{"id":3,"user_id":"db26f1f9-f973-4159-80ed-5adb76135cad","title":"Gym plan"},
{"id":4,"user_id":"db26f1f9-f973-4159-80ed-5adb76135cad","title":"Trip ideas"}]
=== TEST 4: alice inserts own note (should succeed) ===
[{"id":5,"user_id":"9dc9142e-314f-4ab6-aa66-b38a05f42de0","title":"__test_own","body":"x"}]
HTTP:201
=== TEST 5: alice inserts as BOB (should fail) ===
{"code":"42501","details":null,"hint":null,"message":"new row violates row-level security policy for table \"notes\""}
HTTP:403
=== TEST 6: alice updates bob's note id=3 (should affect 0 rows) ===
[]
HTTP:200
=== TEST 7: alice deletes bob's note id=3 (should affect 0 rows) ===
[]
HTTP:200
psql "$SUPABASE_DB_URL" -c "DELETE FROM public.notes WHERE title='__test_own';" echo "--- final data (should be original 4 rows, untouched) ---" psql "$SUPABASE_DB_URL" -c "SELECT id, user_id, title FROM public.notes ORDER BY id;"
DELETE 1 --- final data (should be original 4 rows, untouched) --- id | user_id | title ----+--------------------------------------+-------------- 1 | 9dc9142e-314f-4ab6-aa66-b38a05f42de0 | Groceries 2 | 9dc9142e-314f-4ab6-aa66-b38a05f42de0 | Call plumber 3 | db26f1f9-f973-4159-80ed-5adb76135cad | Gym plan 4 | db26f1f9-f973-4159-80ed-5adb76135cad | Trip ideas (4 rows)
5 earlier narration turn(s) are in the raw transcript.
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