env | grep -i supabase | sed 's/=.*KEY.*/=<redacted>/; s/\(PASSWORD=\).*/\1<redacted>/'
SUPABASE_DB_PASSWORD=<redacted> SUPABASE_DB_URL=<redacted:SUPABASE_DB_URL> SUPABASE_URL=https://<ref>.supabase.co SUPABASE_SERVICE_ROLE_KEY=<redacted:SUPABASE_SERVICE_ROLE_KEY> SUPABASE_PROJECT_REF=<ref> SUPABASE_ANON_KEY=<redacted:SUPABASE_ANON_KEY>
which psql; psql --version 2>/dev/null; which curl jq
/usr/bin/psql psql (PostgreSQL) 18.4 (Debian 18.4-1.pgdg12+1) /usr/bin/curl /usr/bin/jq
psql "$SUPABASE_DB_URL" -c "\d public.notes" -c "SELECT relname, relrowsecurity, relforcerowsecurity FROM pg_class WHERE relnamespace='public'::regnamespace AND relname='notes';" -c "SELECT polname, polcmd, polroles::regrole[], polqual, polwithcheck FROM pg_policy WHERE polrelid='public.notes'::regclass;"
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
relname | relrowsecurity | relforcerowsecurity
---------+----------------+---------------------
notes | f | f
(1 row)
polname | polcmd | polroles | polqual | polwithcheck
---------+--------+----------+---------+--------------
(0 rows)
curl -s "$SUPABASE_URL/rest/v1/notes?select=id,user_id,title" -H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $SUPABASE_ANON_KEY" | jq .
[
{
"id": 1,
"user_id": "e4a5940a-fb3a-4564-bf53-6b458d59611d",
"title": "Groceries"
},
{
"id": 2,
"user_id": "e4a5940a-fb3a-4564-bf53-6b458d59611d",
"title": "Call plumber"
},
{
"id": 3,
"user_id": "35c6b1c1-957a-4733-87ea-cc9280ca92cb",
"title": "Gym plan"
},
{
"id": 4,
"user_id": "35c6b1c1-957a-4733-87ea-cc9280ca92cb",
"title": "Trip ideas"
}
]
psql "$SUPABASE_DB_URL" -v ON_ERROR_STOP=1 <<'SQL' BEGIN; ALTER TABLE public.notes ENABLE ROW LEVEL SECURITY; CREATE POLICY notes_select_own ON public.notes FOR SELECT TO authenticated USING (user_id = (SELECT auth.uid())); CREATE POLICY notes_insert_own ON public.notes FOR INSERT TO authenticated WITH CHECK (user_id = (SELECT auth.uid())); 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())); CREATE POLICY notes_delete_own ON public.notes FOR DELETE TO authenticated USING (user_id = (SELECT auth.uid())); COMMIT; SELECT polname, polcmd, polroles::regrole[], polqual, polwithcheck FROM pg_policy WHERE polrelid='public.notes'::regclass ORDER BY polname; SELECT relname, relrowsecurity FROM pg_class WHERE relname='notes' AND relnamespace='public'::regnamespace; SQL
BEGIN
ALTER TABLE
CREATE POLICY
CREATE POLICY
CREATE POLICY
CREATE POLICY
COMMIT
polname | polcmd | polroles | polqual | polwithcheck
------------------+--------+-----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------psql "$SUPABASE_DB_URL" -x -c "SELECT polname, polcmd, polroles::regrole[] AS roles, pg_get_expr(polqual, polrelid) AS using_expr, pg_get_expr(polwithcheck, polrelid) AS check_expr FROM pg_policy WHERE polrelid='public.notes'::regclass ORDER BY polname;" -c "SELECT relrowsecurity AS rls_enabled, relforcerowsecurity AS rls_forced FROM pg_class WHERE relname='notes' AND relnamespace='public'::regnamespace;"
-[ RECORD 1 ]---------------------------------------
polname | notes_delete_own
polcmd | d
roles | {authenticated}
using_expr | (user_id = ( SELECT auth.uid() AS uid))
check_expr |
-[ RECORD 2 ]---------------------------------------
polname | notes_insert_own
polcmd | a
roles | {authenticated}
using_expr |
check_expr | (user_id = ( SELECT auth.uid() AS uid))
-[ RECORD 3 ]---------------------------------------
polname | notes_select_own
polcmd | r
roles | {authenticated}
using_expr | (user_id = ( SELECT auth.uid() AS uid))
check_expr |
-[ RECORD 4 ]---------------------------------------
polname | notes_update_own
polcmd | w
roles | {authenticated}
using_expr | (user_id = ( SELECT auth.uid() AS uid))
check_expr | (user_id = ( SELECT auth.uid() AS uid))
-[ RECORD 1 ]--
rls_enabled | t
rls_forced | f
echo "=== ANON read ==="; curl -s -w "\nHTTP %{http_code}\n" "$SUPABASE_URL/rest/v1/notes?select=id,user_id,title" -H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $SUPABASE_ANON_KEY"=== ANON read === [] HTTP 200
cd /tmp
ALICE_TOKEN=$(curl -s -X POST "$SUPABASE_URL/auth/v1/token?grant_type=password" -H "apikey: $SUPABASE_ANON_KEY" -H "Content-Type: application/json" -d '{"email":"alice@cari.dev","password":"Passw0rd-alice-42"}' | jq -r .access_token)
BOB_TOKEN=$(curl -s -X POST "$SUPABASE_URL/auth/v1/token?grant_type=password" -H "apikey: $SUPABASE_ANON_KEY" -H "Content-Type: application/json" -d '{"email":"bob@cari.dev","password":"Passw0rd-bob-58"}' | jq -r .access_token)
echo "alice token len: ${#ALICE_TOKEN}, bob token len: ${#BOB_TOKEN}"
echo "$ALICE_TOKEN" > /tmp/alice_token; echo "$BOB_TOKEN" > /tmp/bob_token
ALICE_ID=$(echo "$ALICE_TOKEN" | cut -d. -f2 | tr '_-' '/+' | { read p; pad=$(( (4 - ${#p} % 4) % 4 )); echo "$p$(printf '=%.0s' $(seq $pad))"; } | base64 -d 2>/dev/null | jq -r .sub)
BOB_ID=$(echo "$BOB_TOKEN" | cut -d. -f2 | tr '_-' '/+' | { read p; pad=$(( (4 - ${#p} % 4) % 4 )); echo "$p$(printf '=%.0s' $(seq $pad))"; } | base64 -d 2>/dev/null | jq -r .sub)
echo "alice id: $ALICE_ID"; echo "bob id: $BOB_ID"
echo "$ALICE_ID" > /tmp/alice_id; echo "$BOB_ID" > /tmp/bob_idalice token len: 791, bob token len: 788 alice id: e4a5940a-fb3a-4564-bf53-6b458d59611d bob id: 35c6b1c1-957a-4733-87ea-cc9280ca92cb
ALICE_TOKEN=$(cat /tmp/alice_token); BOB_TOKEN=$(cat /tmp/bob_token)
ALICE_ID=$(cat /tmp/alice_id); BOB_ID=$(cat /tmp/bob_id)
H_ALICE=(-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $ALICE_TOKEN")
H_BOB=(-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $BOB_TOKEN")
echo "=== T1: Alice reads all notes -> should see ONLY her 2 ==="
curl -s "$SUPABASE_URL/rest/v1/notes?select=id,user_id,title" "${H_ALICE[@]}" | jq -c .
echo "=== T2: Alice reads bob's note id=3 -> should be [] ==="
curl -s "$SUPABASE_URL/rest/v1/notes?id=eq.3&select=id,title" "${H_ALICE[@]}" | jq -c .
echo "=== T3: Alice inserts note with HER user_id -> should succeed ==="
curl -s -w "\nHTTP %{http_code}\n" -X POST "$SUPABASE_URL/rest/v1/notes?select=id,user_id,title" "${H_ALICE[@]}" -H "Content-Type: application/json" -H "Prefer: return=representation" \
-d "{\"user_id\":\"$ALICE_ID\",\"title\":\"tmp-rls-test\",\"body\":\"will be deleted\"}" | tee /tmp/t3.out
echo "=== T4: Alice inserts note with BOB's user_id -> must FAIL ==="
curl -s -w "\nHTTP %{http_code}\n" -X POST "$SUPABASE_URL/rest/v1/notes" "${H_ALICE[@]}" -H "Content-Type: application/json" \
-d "{\"user_id\":\"$BOB_ID\",\"title\":\"evil\",\"body\":\"spoofed\"}"
echo "=== T5: Alice inserts note with NO user_id -> must FAIL (NOT NULL + policy) ==="
curl -s -w "\nHTTP %{http_code}\n" -X POST "$SUPABASE_URL/rest/v1/notes" "${H_ALICE[@]}" -H "Content-Type: application/json" \
-d '{"title":"no-owner","body":"x"}'
echo "=== T6: ANON tries to insert with alice user_id -> must FAIL ==="
curl -s -w "\nHTTP %{http_code}\n" -X POST "$SUPABASE_URL/rest/v1/notes" -H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $SUPABASE_ANON_KEY" -H "Content-Type: application/json" \
-d "{\"user_id\":\"$ALICE_ID\",\"title\":\"anon\",\"body\":\"x\"}"=== T1: Alice reads all notes -> should see ONLY her 2 ===
[{"id":1,"user_id":"e4a5940a-fb3a-4564-bf53-6b458d59611d","title":"Groceries"},{"id":2,"user_id":"e4a5940a-fb3a-4564-bf53-6b458d59611d","title":"Call plumber"}]
=== T2: Alice reads bob's note id=3 -> should be [] ===
[]
=== T3: Alice inserts note with HER user_id -> should succeed ===
[{"id":5,"user_id":"e4a5940a-fb3a-4564-bf53-6b458d59611d","title":"tmp-rls-test"}]
HTTP 201
=== T4: Alice inserts note with BOB's user_id -> must FAIL ===
{"code":"42501","details":null,"hint":null,"message":"new row violates row-level security policy for table \"notes\""}
HTTP 403
=== T5: Alice inserts note with NO user_id -> must FAIL (NOT NULL + policy) ===
{"code":"42501","details":null,"hint":null,"message":"new row violates row-level security policy for table \"notes\""}
HTTP 403
=== T6: ANON tries to insert with alice user_id -> must FAIL ===
{"code":"42501","details":null,"hint":null,"message":"new row violates row-level security policy for table \"notes\""}
HTTP 401
ALICE_TOKEN=$(cat /tmp/alice_token); BOB_TOKEN=$(cat /tmp/bob_token)
ALICE_ID=$(cat /tmp/alice_id); BOB_ID=$(cat /tmp/bob_id)
H_ALICE=(-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $ALICE_TOKEN")
H_BOB=(-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $BOB_TOKEN")
echo "=== T7: Alice updates HER note id=1 (title change) -> should return the row ==="
curl -s -X PATCH "$SUPABASE_URL/rest/v1/notes?id=eq.1&select=id,title" "${H_ALICE[@]}" -H "Content-Type: application/json" -H "Prefer: return=representation" -d '{"title":"Groceries"}' | jq -c .
echo "=== T8: Alice tries to UPDATE bob's note id=3 -> must affect 0 rows ==="
curl -s -w "\nHTTP %{http_code}\n" -X PATCH "$SUPABASE_URL/rest/v1/notes?id=eq.3&select=id" "${H_ALICE[@]}" -H "Content-Type: application/json" -H "Prefer: return=representation" -d '{"title":"hacked"}'
echo "=== T9: Alice tries to REASSIGN her note id=1 to bob (user_id change) -> must FAIL ==="
curl -s -w "\nHTTP %{http_code}\n" -X PATCH "$SUPABASE_URL/rest/v1/notes?id=eq.1" "${H_ALICE[@]}" -H "Content-Type: application/json" -d "{\"user_id\":\"$BOB_ID\"}"
echo "=== T10: Alice tries to DELETE bob's note id=4 -> must affect 0 rows ==="
curl -s -w "\nHTTP %{http_code}\n" -X DELETE "$SUPABASE_URL/rest/v1/notes?id=eq.4&select=id" "${H_ALICE[@]}" -H "Prefer: return=representation"
echo "=== T11: Bob reads -> only his 2 ==="
curl -s "$SUPABASE_URL/rest/v1/notes?select=id,user_id,title" "${H_BOB[@]}" | jq -c .
echo "=== T12: ANON delete attempt -> 401 ==="
curl -s -w "\nHTTP %{http_code}\n" -X DELETE "$SUPABASE_URL/rest/v1/notes?id=eq.1" -H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $SUPABASE_ANON_KEY"
echo "=== CLEANUP: Alice deletes her temp note id=5 -> should succeed ==="
curl -s -w "\nHTTP %{http_code}\n" -X DELETE "$SUPABASE_URL/rest/v1/notes?id=eq.5&select=id" "${H_ALICE[@]}" -H "Prefer: return=representation"=== T7: Alice updates HER note id=1 (title change) -> should return the row ===
[{"id":1,"title":"Groceries"}]
=== T8: Alice tries to UPDATE bob's note id=3 -> must affect 0 rows ===
[]
HTTP 200
=== T9: Alice tries to REASSIGN her note id=1 to bob (user_id change) -> must FAIL ===
{"code":"42501","details":null,"hint":null,"message":"new row violates row-level security policy for table \"notes\""}
HTTP 403
=== T10: Alice tries to DELETE bob's note id=4 -> must affect 0 rows ===
[]
HTTP 200
=== T11: Bob reads -> only his 2 ===
[{"id":3,"user_id":"35c6b1c1-957a-4733-87ea-cc9280ca92cb","title":"Gym plan"},{"id":4,"user_id":"35c6b1c1-957a-4733-87ea-cc9280ca92cb","title":"Trip ideas"}]
=== T12: ANON delete attempt -> 401 ===
HTTP 204
=== CLEANUP: Alice deletes her temp note id=5 -> should succeed ===
[{"id":5}]
HTTP 200
psql "$SUPABASE_DB_URL" -c "SELECT id, user_id, title FROM public.notes ORDER BY id;" echo "=== ANON final check ==="; curl -s "$SUPABASE_URL/rest/v1/notes?select=id" -H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $SUPABASE_ANON_KEY" | jq -c . rm -f /tmp/alice_token /tmp/bob_token /tmp/alice_id /tmp/bob_id /tmp/t3.out
id | user_id | title ----+--------------------------------------+-------------- 1 | e4a5940a-fb3a-4564-bf53-6b458d59611d | Groceries 2 | e4a5940a-fb3a-4564-bf53-6b458d59611d | Call plumber 3 | 35c6b1c1-957a-4733-87ea-cc9280ca92cb | Gym plan 4 | 35c6b1c1-957a-4733-87ea-cc9280ca92cb | Trip ideas (4 rows) === ANON final check === []
6 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