psql "$SUPABASE_DB_URL" -c "\d public.tickets; \d public.org_members; \d public.orgs"
Table "public.tickets"
Column | Type | Collation | Nullable | Default
--------+--------+-----------+----------+----------------------------------
id | bigint | | not null | generated by default as identity
org_id | uuid | | not null |
title | text | | not null |
status | text | | not null | 'open'::text
Indexes:
"tickets_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
"tickets_org_id_fkey" FOREIGN KEY (org_id) REFERENCES orgs(id) ON DELETE CASCADE
psql "$SUPABASE_DB_URL" -c "\d public.org_members"
Table "public.org_members"
Column | Type | Collation | Nullable | Default
---------+------+-----------+----------+---------
org_id | uuid | | not null |
user_id | uuid | | not null |
role | text | | not null |
Indexes:
"org_members_pkey" PRIMARY KEY, btree (org_id, user_id)
Check constraints:
"org_members_role_check" CHECK (role = ANY (ARRAY['admin'::text, 'member'::text]))
Foreign-key constraints:
"org_members_org_id_fkey" FOREIGN KEY (org_id) REFERENCES orgs(id) ON DELETE CASCADE
"org_members_user_id_fkey" FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE
cat << 'EOF' > test_rls.sql
ALTER TABLE public.tickets ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Tickets read access" ON public.tickets
FOR SELECT TO authenticated
USING (
EXISTS (
SELECT 1 FROM public.org_members
WHERE org_members.org_id = tickets.org_id
AND org_members.user_id = auth.uid()
)
);
CREATE POLICY "Tickets insert access" ON public.tickets
FOR INSERT TO authenticated
WITH CHECK (
EXISTS (
SELECT 1 FROM public.org_members
WHERE org_members.org_id = tickets.org_id
AND org_members.user_id = auth.uid()
AND org_members.role = 'admin'
)
);
CREATE POLICY "Tickets update access" ON public.tickets
FOR UPDATE TO authenticated
USING (
EXISTS (
SELECT 1 FROM public.org_members
WHERE org_members.org_id = tickets.org_id
AND org_members.user_id = auth.uid()
AND org_members.role = 'admin'
)
);
CREATE POLICY "Tickets delete access" ON public.tickets
FOR DELETE TO authenticated
USING (
EXISTS (
SELECT 1 FROM public.org_members
WHERE org_members.org_id = tickets.org_id
AND org_members.user_id = auth.uid()
AND org_members.role = 'admin'
)
);
ALTER TABLE public.org_members ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Org members read access" ON public.org_members
FOR SELECT TO authenticated
USING (
user_id = auth.uid()
OR
EXISTS (
SELECT 1 FROM public.org_members AS om
WHERE om.org_id = org_members.org_id
AND om.user_id = auth.uid()
)
);
EOF
psql "$SUPABASE_DB_URL" -f test_rls.sql
ALTER TABLE CREATE POLICY CREATE POLICY CREATE POLICY CREATE POLICY ALTER TABLE CREATE POLICY
cat << 'EOF' > test_recursion.sh
#!/bin/bash
# Get JWT for una-admin
RES=$(curl -sX POST "${SUPABASE_URL}/auth/v1/token?grant_type=password" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Content-Type: application/json" \
-d '{"email":"uma-admin@cari.dev", "password":"Passw0rd-uma-11"}')
JWT=$(echo $RES | jq -r .access_token)
# Fetch org members
curl -sX GET "${SUPABASE_URL}/rest/v1/org_members" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Authorization: Bearer $JWT"
EOF
chmod +x test_recursion.sh
./test_recursion.sh
{"code":"42P17","details":null,"hint":null,"message":"infinite recursion detected in policy for relation \"org_members\""}cat << 'EOF' > fix_rls.sql DROP POLICY IF EXISTS "Org members read access" ON public.org_members; CREATE OR REPLACE FUNCTION public.get_my_orgs() RETURNS SETOF uuid LANGUAGE sql SECURITY DEFINER SET search_path = public AS $$ SELECT org_id FROM public.org_members WHERE user_id = auth.uid(); $$; -- Wait, creating a function with SECURITY DEFINER is good, but the table is owned by postgres. -- Let's test it. CREATE POLICY "Org members read access" ON public.org_members FOR SELECT TO authenticated USING ( org_id IN (SELECT public.get_my_orgs()) ); EOF psql "$SUPABASE_DB_URL" -f fix_rls.sql
DROP POLICY CREATE FUNCTION CREATE POLICY
./test_recursion.sh
[{"org_id":"dc8472b1-b2ba-44fd-badd-18395d8a93e9","user_id":"ae8d5de7-e483-4815-9f8b-7deca2a52d51","role":"admin"},
{"org_id":"dc8472b1-b2ba-44fd-badd-18395d8a93e9","user_id":"b0637731-71a1-4ef8-b84b-38866ac0a8c5","role":"member"}]cat << 'EOF' > test_tickets.sh
#!/bin/bash
authenticate() {
curl -sX POST "${SUPABASE_URL}/auth/v1/token?grant_type=password" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Content-Type: application/json" \
-d "{\"email\":\"$1\", \"password\":\"$2\"}" | jq -r .access_token
}
JWT_UMA=$(authenticate uma-admin@cari.dev Passw0rd-uma-11)
JWT_VIC=$(authenticate vic-member@cari.dev Passw0rd-vic-22)
JWT_WEN=$(authenticate wen-outsider@cari.dev Passw0rd-wen-33)
echo "Uma (Admin) tickets:"
curl -sX GET "${SUPABASE_URL}/rest/v1/tickets" -H "apikey: ${SUPABASE_ANON_KEY}" -H "Authorization: Bearer $JWT_UMA" | jq -r '.[].title'
echo "Vic (Member) tickets:"
curl -sX GET "${SUPABASE_URL}/rest/v1/tickets" -H "apikey: ${SUPABASE_ANON_KEY}" -H "Authorization: Bearer $JWT_VIC" | jq -r '.[].title'
echo "Wen (Outsider) tickets:"
curl -sX GET "${SUPABASE_URL}/rest/v1/tickets" -H "apikey: ${SUPABASE_ANON_KEY}" -H "Authorization: Bearer $JWT_WEN" | jq -r '.[].title'
echo "Anon tickets:"
curl -sX GET "${SUPABASE_URL}/rest/v1/tickets" -H "apikey: ${SUPABASE_ANON_KEY}" | jq -r '.[].title'
EOF
chmod +x test_tickets.sh
./test_tickets.sh
Uma (Admin) tickets: Fix login Update billing Rotate keys Vic (Member) tickets: Fix login Update billing Rotate keys Wen (Outsider) tickets: Migrate DNS Renew cert Anon tickets:
cat << 'EOF' > fix_tickets_rls.sql
DROP POLICY IF EXISTS "Tickets read access" ON public.tickets;
DROP POLICY IF EXISTS "Tickets insert access" ON public.tickets;
DROP POLICY IF EXISTS "Tickets update access" ON public.tickets;
DROP POLICY IF EXISTS "Tickets delete access" ON public.tickets;
CREATE POLICY "Tickets read access" ON public.tickets
FOR SELECT TO authenticated
USING (
org_id IN (SELECT public.get_my_orgs())
);
CREATE POLICY "Tickets insert access" ON public.tickets
FOR INSERT TO authenticated
WITH CHECK (
EXISTS (
SELECT 1 FROM public.org_members
WHERE org_members.org_id = tickets.org_id
AND org_members.user_id = auth.uid()
AND org_members.role = 'admin'
)
);
CREATE POLICY "Tickets update access" ON public.tickets
FOR UPDATE TO authenticated
USING (
EXISTS (
SELECT 1 FROM public.org_members
WHERE org_members.org_id = tickets.org_id
AND org_members.user_id = auth.uid()
AND org_members.role = 'admin'
)
);
CREATE POLICY "Tickets delete access" ON public.tickets
FOR DELETE TO authenticated
USING (
EXISTS (
SELECT 1 FROM public.org_members
WHERE org_members.org_id = tickets.org_id
AND org_members.user_id = auth.uid()
AND org_members.role = 'admin'
)
);
EOF
psql "$SUPABASE_DB_URL" -f fix_tickets_rls.sql
DROP POLICY DROP POLICY DROP POLICY DROP POLICY CREATE POLICY CREATE POLICY CREATE POLICY CREATE POLICY
psql "$SUPABASE_DB_URL" -c "ALTER TABLE public.orgs ENABLE ROW LEVEL SECURITY;"
ALTER TABLE
cat << 'EOF' > fix_orgs_rls.sql CREATE POLICY "Orgs read access" ON public.orgs FOR SELECT TO authenticated USING ( true -- The requirement is "the orgs table itself may remain readable; do not break legitimate access for org members." -- Let's make it readable for all authenticated, or we can make it readable for anonymous too? -- Wait, anonymous sees no tickets at all and org_members rows anonymous sees none. -- "the orgs table itself may remain readable; do not break legitimate access for org members." -- So public.orgs is readable. ); CREATE POLICY "Orgs anon read access" ON public.orgs FOR SELECT TO anon USING ( true ); EOF psql "$SUPABASE_DB_URL" -f fix_orgs_rls.sql
CREATE POLICY CREATE POLICY
cat << 'EOF' > test_org_members_edit.sh
#!/bin/bash
authenticate() {
curl -sX POST "${SUPABASE_URL}/auth/v1/token?grant_type=password" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Content-Type: application/json" \
-d "{\"email\":\"$1\", \"password\":\"$2\"}" | jq -r .access_token
}
JWT_UMA=$(authenticate uma-admin@cari.dev Passw0rd-uma-11)
JWT_VIC=$(authenticate vic-member@cari.dev Passw0rd-vic-22)
JWT_WEN=$(authenticate wen-outsider@cari.dev Passw0rd-wen-33)
echo "Uma (Admin) members:"
curl -sX GET "${SUPABASE_URL}/rest/v1/org_members" -H "apikey: ${SUPABASE_ANON_KEY}" -H "Authorization: Bearer $JWT_UMA" | jq -r '.[].user_id' | wc -l
echo "Vic (Member) members:"
curl -sX GET "${SUPABASE_URL}/rest/v1/org_members" -H "apikey: ${SUPABASE_ANON_KEY}" -H "Authorization: Bearer $JWT_VIC" | jq -r '.[].user_id' | wc -l
echo "Wen (Outsider) members:"
curl -sX GET "${SUPABASE_URL}/rest/v1/org_members" -H "apikey: ${SUPABASE_ANON_KEY}" -H "Authorization: Bearer $JWT_WEN" | jq -r '.[].user_id' | wc -l
echo "Anon members:"
curl -sX GET "${SUPABASE_URL}/rest/v1/org_members" -H "apikey: ${SUPABASE_ANON_KEY}" | jq 'length'
EOF
chmod +x test_org_members_edit.sh
./test_org_members_edit.sh
Uma (Admin) members: 2 Vic (Member) members: 2 Wen (Outsider) members: 1 Anon members: 0
psql "$SUPABASE_DB_URL" -c "\d public.get_my_orgs"
Did not find any relation named "public.get_my_orgs".
echo "Wait, psql \df public.get_my_orgs" && psql "$SUPABASE_DB_URL" -c "\df public.get_my_orgs"
Wait, psql \df public.get_my_orgs
List of functions
Schema | Name | Result data type | Argument data types | Type
--------+-------------+------------------+---------------------+------
public | get_my_orgs | SETOF uuid | | func
(1 row)
cat << 'EOF' > test_tickets_edit.sh
#!/bin/bash
authenticate() {
curl -sX POST "${SUPABASE_URL}/auth/v1/token?grant_type=password" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Content-Type: application/json" \
-d "{\"email\":\"$1\", \"password\":\"$2\"}" | jq -r .access_token
}
JWT_UMA=$(authenticate uma-admin@cari.dev Passw0rd-uma-11)
JWT_VIC=$(authenticate vic-member@cari.dev Passw0rd-vic-22)
echo "Uma inserting ticket:"
curl -i -sX POST "${SUPABASE_URL}/rest/v1/tickets" -H "apikey: ${SUPABASE_ANON_KEY}" -H "Authorization: Bearer $JWT_UMA" -H "Content-Type: application/json" -H "Prefer: return=representation" -d '{"org_id":"dc8472b1-b2ba-44fd-badd-18395d8a93e9", "title":"Uma test"}' | grep HTTP
echo "Vic inserting ticket:"
curl -i -sX POST "${SUPABASE_URL}/rest/v1/tickets" -H "apikey: ${SUPABASE_ANON_KEY}" -H "Authorization: Bearer $JWT_VIC" -H "Content-Type: application/json" -H "Prefer: return=representation" -d '{"org_id":"dc8472b1-b2ba-44fd-badd-18395d8a93e9", "title":"Vic test"}' | grep HTTP
EOF
chmod +x test_tickets_edit.sh
./test_tickets_edit.shUma inserting ticket: HTTP/2 201 Vic inserting ticket: HTTP/2 403
cat << 'EOF' > test_tickets_update.sh
#!/bin/bash
authenticate() {
curl -sX POST "${SUPABASE_URL}/auth/v1/token?grant_type=password" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Content-Type: application/json" \
-d "{\"email\":\"$1\", \"password\":\"$2\"}" | jq -r .access_token
}
JWT_UMA=$(authenticate uma-admin@cari.dev Passw0rd-uma-11)
JWT_VIC=$(authenticate vic-member@cari.dev Passw0rd-vic-22)
# Get Uma's inserted ticket id
ID=$(curl -sX GET "${SUPABASE_URL}/rest/v1/tickets?title=eq.Uma%20test" -H "apikey: ${SUPABASE_ANON_KEY}" -H "Authorization: Bearer $JWT_UMA" | jq -r '.[0].id')
echo "Uma updating ticket $ID:"
curl -i -sX PATCH "${SUPABASE_URL}/rest/v1/tickets?id=eq.${ID}" -H "apikey: ${SUPABASE_ANON_KEY}" -H "Authorization: Bearer $JWT_UMA" -H "Content-Type: application/json" -H "Prefer: return=representation" -d '{"title":"Uma test updated"}' | grep HTTP
echo "Vic updating ticket $ID:"
curl -i -sX PATCH "${SUPABASE_URL}/rest/v1/tickets?id=eq.${ID}" -H "apikey: ${SUPABASE_ANON_KEY}" -H "Authorization: Bearer $JWT_VIC" -H "Content-Type: application/json" -H "Prefer: return=representation" -d '{"title":"Vic test fails"}' | grep HTTP
echo "Uma deleting ticket $ID:"
curl -i -sX DELETE "${SUPABASE_URL}/rest/v1/tickets?id=eq.${ID}" -H "apikey: ${SUPABASE_ANON_KEY}" -H "Authorization: Bearer $JWT_UMA" -H "Prefer: return=representation" | grep HTTP
EOF
chmod +x test_tickets_update.sh
./test_tickets_update.shUma updating ticket 6: HTTP/2 200 Vic updating ticket 6: HTTP/2 200 Uma deleting ticket 6: HTTP/2 200
echo "Wait, Vic (member) was able to update? No, Vic shouldn't be able. Wait, if it didn't update any rows, it returns 200 with empty array (depending on Prefer: return=representation)." && curl -sX GET "${SUPABASE_URL}/rest/v1/tickets?title=eq.Uma%20test%20updated" -H "apikey: ${SUPABASE_ANON_KEY}" -H "Authorization: Bearer $JWT_VIC"Wait, Vic (member) was able to update? No, Vic shouldn't be able. Wait, if it didn't update any rows, it returns 200 with empty array (depending on Prefer: return=representation).
{"code":"PGRST301","details":null,"hint":null,"message":"Empty JWT is sent in Authorization header"}cat << 'EOF' > fix_test_vic_update.sh
#!/bin/bash
authenticate() {
curl -sX POST "${SUPABASE_URL}/auth/v1/token?grant_type=password" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Content-Type: application/json" \
-d "{\"email\":\"$1\", \"password\":\"$2\"}" | jq -r .access_token
}
JWT_UMA=$(authenticate uma-admin@cari.dev Passw0rd-uma-11)
JWT_VIC=$(authenticate vic-member@cari.dev Passw0rd-vic-22)
curl -sX POST "${SUPABASE_URL}/rest/v1/tickets" -H "apikey: ${SUPABASE_ANON_KEY}" -H "Authorization: Bearer $JWT_UMA" -H "Content-Type: application/json" -H "Prefer: return=representation" -d '{"org_id":"dc8472b1-b2ba-44fd-badd-18395d8a93e9", "title":"Uma test 2"}' > /dev/null
ID=$(curl -sX GET "${SUPABASE_URL}/rest/v1/tickets?title=eq.Uma%20test%202" -H "apikey: ${SUPABASE_ANON_KEY}" -H "Authorization: Bearer $JWT_UMA" | jq -r '.[0].id')
echo "Vic updating ticket $ID:"
curl -sX PATCH "${SUPABASE_URL}/rest/v1/tickets?id=eq.${ID}" -H "apikey: ${SUPABASE_ANON_KEY}" -H "Authorization: Bearer $JWT_VIC" -H "Content-Type: application/json" -H "Prefer: return=representation" -d '{"title":"Vic test fails"}'
EOF
chmod +x fix_test_vic_update.sh
./fix_test_vic_update.shVic updating ticket 8: []
curl -sX GET "${SUPABASE_URL}/rest/v1/tickets?title=eq.Vic%20test%20fails" -H "apikey: ${SUPABASE_ANON_KEY}" -H "Authorization: Bearer $JWT_UMA"{"code":"PGRST301","details":null,"hint":null,"message":"Empty JWT is sent in Authorization header"}cat << 'EOF' > fix_test_vic_update2.sh
#!/bin/bash
authenticate() {
curl -sX POST "${SUPABASE_URL}/auth/v1/token?grant_type=password" \
-H "apikey: ${SUPABASE_ANON_KEY}" \
-H "Content-Type: application/json" \
-d "{\"email\":\"$1\", \"password\":\"$2\"}" | jq -r .access_token
}
JWT_UMA=$(authenticate uma-admin@cari.dev Passw0rd-uma-11)
curl -sX GET "${SUPABASE_URL}/rest/v1/tickets?title=eq.Vic%20test%20fails" -H "apikey: ${SUPABASE_ANON_KEY}" -H "Authorization: Bearer $JWT_UMA"
EOF
chmod +x fix_test_vic_update2.sh
./fix_test_vic_update2.sh[]
psql "$SUPABASE_DB_URL" -c "ALTER TABLE public.tickets ENABLE ROW LEVEL SECURITY;"
ALTER TABLE
psql "$SUPABASE_DB_URL" -c "ALTER TABLE public.org_members ENABLE ROW LEVEL SECURITY;"
ALTER TABLE
cat << 'EOF' > test_anon_access.sh
#!/bin/bash
echo "Anon Orgs members:"
curl -sX GET "${SUPABASE_URL}/rest/v1/org_members" -H "apikey: ${SUPABASE_ANON_KEY}"
echo "Anon Tickets:"
curl -sX GET "${SUPABASE_URL}/rest/v1/tickets" -H "apikey: ${SUPABASE_ANON_KEY}"
echo "Anon Orgs:"
curl -sX GET "${SUPABASE_URL}/rest/v1/orgs" -H "apikey: ${SUPABASE_ANON_KEY}"
EOF
chmod +x test_anon_access.sh
./test_anon_access.shAnon Orgs members:
[]Anon Tickets:
[]Anon Orgs:
[{"id":"dc8472b1-b2ba-44fd-badd-18395d8a93e9","name":"Acme"},
{"id":"cbe8f079-6cf3-4943-b5a6-87d82a166f6b","name":"Globex"}]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