env | grep -i supabase | sed 's/=.*KEY.*/=***REDACTED***/'
SUPABASE_DB_PASSWORD=<redacted:SUPABASE_DB_PASSWORD> 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 curl jq 2>&1; psql --version 2>&1
/usr/bin/psql /usr/bin/curl /usr/bin/jq psql (PostgreSQL) 18.4 (Debian 18.4-1.pgdg12+1)
psql "$SUPABASE_DB_URL" -c "\d public.notes" echo "---" psql "$SUPABASE_DB_URL" -c "SELECT relname, relrowsecurity, relforcerowsecurity FROM pg_class WHERE relname='notes';" echo "---" psql "$SUPABASE_DB_URL" -c "SELECT * FROM pg_policies WHERE tablename='notes';" echo "---" psql "$SUPABASE_DB_URL" -c "SELECT grantee, table_name, privilege_type FROM information_schema.role_table_grants WHERE table_name='notes';" echo "---" psql "$SUPABASE_DB_URL" -c "SELECT id, user_id, title FROM public.notes ORDER BY user_id;"
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)
---
schemaname | tablename | policyname | permissive | roles | cmd | qual | with_check
------------+-----------+------------+------------+-------+-----+------+------------
(0 rows)
---
grantee | table_name | privilege_type
---------------+------------+----------------
authenticated | notes | TRIGGER
authenticated | notes | REFERENCES
authenticated | notes | TRUNCATE
authenticated | notes | DELETE
authenticated | notes | UPDATE
authenticated | notes | SELECT
authenticated | notes | INSERT
anon | notes | TRIGGER
anon | notes | REFERENCES
anon | notes | TRUNCATE
anon | notes | DELETE
anon | notes | UPDATE
anon | notes | SELECT
anon | notes | INSERT
service_role | notes | TRIGGER
service_role | notes | REFERENCES
service_role | notes | TRUNCATE
service_role | notes | DELETE
service_role | notes | UPDATE
service_role | notes | SELECT
service_role | notes | INSERT
postgres | notes | TRIGGER
postgres | notes | REFERENCES
postgres | notes | TRUNCATE
postgres | notes | DELETE
postgres | notes | UPDATE
postgres | notes | SELECT
postgres | notes | INSERT
(28 rows)
---
id | user_id | title
----+--------------------------------------+--------------
3 | 0d2b1cef-2c25-4c64-ac8b-37088be1063c | Gym plan
4 | 0d2b1cef-2c25-4c64-ac8b-37088be1063c | Trip ideas
1 | a952f866-0095-4ab1-8d6e-d2dba6f11616 | Groceries
2 | a952f866-0095-4ab1-8d6e-d2dba6f11616 | Call plumber
(4 rows)
cat > /tmp/lockdown.sql << 'EOF'
BEGIN;
-- Enable and force RLS on notes
ALTER TABLE public.notes ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.notes FORCE ROW LEVEL SECURITY;
-- Drop any pre-existing policies with these names (idempotency)
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 own rows
CREATE POLICY notes_select_own ON public.notes
FOR SELECT
TO authenticated
USING (auth.uid() = user_id);
-- INSERT: only allowed to insert rows where user_id = own uid
CREATE POLICY notes_insert_own ON public.notes
FOR INSERT
TO authenticated
WITH CHECK (auth.uid() = user_id);
-- UPDATE: can only update own rows, and cannot change user_id to someone else's
CREATE POLICY notes_update_own ON public.notes
FOR UPDATE
TO authenticated
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
-- DELETE: only own rows
CREATE POLICY notes_delete_own ON public.notes
FOR DELETE
TO authenticated
USING (auth.uid() = user_id);
-- Tighten grants: remove anon access entirely, keep authenticated CRUD (RLS still restricts rows)
REVOKE ALL ON public.notes FROM anon;
REVOKE ALL ON public.notes FROM authenticated;
GRANT SELECT, INSERT, UPDATE, DELETE ON public.notes TO authenticated;
-- Sequence grant for identity column inserts (authenticated needs usage if not using default identity via serial)
-- notes.id is "generated by default as identity" so inserts don't require sequence privileges typically owned by table owner,
-- but grant just in case a sequence exists and is needed.
DO $$
DECLARE
seqname text;
BEGIN
SELECT pg_get_serial_sequence('public.notes', 'id') INTO seqname;
IF seqname IS NOT NULL THEN
EXECUTE format('GRANT USAGE, SELECT ON SEQUENCE %s TO authenticated', seqname);
END IF;
END $$;
COMMIT;
EOF
psql "$SUPABASE_DB_URL" -f /tmp/lockdown.sqlBEGIN ALTER TABLE ALTER TABLE DROP POLICY DROP POLICY DROP POLICY DROP POLICY CREATE POLICY CREATE POLICY CREATE POLICY CREATE POLICY REVOKE REVOKE GRANT DO COMMIT
psql:/tmp/lockdown.sql:8: NOTICE: policy "notes_select_own" for relation "public.notes" does not exist, skipping psql:/tmp/lockdown.sql:9: NOTICE: policy "notes_insert_own" for relation "public.notes" does not exist, skipping psql:/tmp/lockdown.sql:10: NOTICE: policy "notes_update_own" for relation "public.notes" does not exist, skipping psql:/tmp/lockdown.sql:11: NOTICE: policy "notes_delete_own" for relation "public.notes" does not exist, skipping
psql "$SUPABASE_DB_URL" -c "SELECT relname, relrowsecurity, relforcerowsecurity FROM pg_class WHERE relname='notes';" echo "---policies---" psql "$SUPABASE_DB_URL" -c "SELECT policyname, cmd, roles, qual, with_check FROM pg_policies WHERE tablename='notes';" echo "---grants---" psql "$SUPABASE_DB_URL" -c "SELECT grantee, privilege_type FROM information_schema.role_table_grants WHERE table_name='notes' ORDER BY grantee, privilege_type;"
relname | relrowsecurity | relforcerowsecurity
---------+----------------+---------------------
notes | t | t
(1 row)
---policies---
policyname | cmd | roles | qual | with_check
------------------+--------+-----------------+------------------------+------------------------
notes_select_own | SELECT | {authenticated} | (auth.uid() = user_id) |
notes_insert_own | INSERT | {authenticated} | | (auth.uid() = user_id)
notes_update_own | UPDATE | {authenticated} | (auth.uid() = user_id) | (auth.uid() = user_id)
notes_delete_own | DELETE | {authenticated} | (auth.uid() = user_id) |
(4 rows)
---grants---
grantee | privilege_type
---------------+----------------
authenticated | DELETE
authenticated | INSERT
authenticated | SELECT
authenticated | UPDATE
postgres | DELETE
postgres | INSERT
postgres | REFERENCES
postgres | SELECT
postgres | TRIGGER
postgres | TRUNCATE
postgres | UPDATE
service_role | DELETE
service_role | INSERT
service_role | REFERENCES
service_role | SELECT
service_role | TRIGGER
service_role | TRUNCATE
service_role | UPDATE
(18 rows)
set -e
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}"
echo "BOB_TOKEN len: ${#BOB_TOKEN}"
ALICE_UID=$(echo "$ALICE_TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq -r .sub)
BOB_UID=$(echo "$BOB_TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq -r .sub)
echo "alice uid=$ALICE_UID bob uid=$BOB_UID"
echo "$ALICE_TOKEN" > /tmp/alice.tok
echo "$BOB_TOKEN" > /tmp/bob.tok
echo "$ALICE_UID" > /tmp/alice.uid
echo "$BOB_UID" > /tmp/bob.uid
ALICE_TOKEN len: 791 BOB_TOKEN len: 788 alice uid=a952f866-0095-4ab1-8d6e-d2dba6f11616 bob uid=0d2b1cef-2c25-4c64-ac8b-37088be1063c
ALICE_TOKEN=$(cat /tmp/alice.tok); BOB_TOKEN=$(cat /tmp/bob.tok)
ALICE_UID=$(cat /tmp/alice.uid); BOB_UID=$(cat /tmp/bob.uid)
echo "=== 1. Anonymous SELECT (should return empty array) ==="
curl -s "$SUPABASE_URL/rest/v1/notes?select=*" \
-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $SUPABASE_ANON_KEY"
echo
echo "=== 2. Alice SELECT (should see only her 2 notes) ==="
curl -s "$SUPABASE_URL/rest/v1/notes?select=*" \
-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $ALICE_TOKEN"
echo
echo "=== 3. Bob SELECT (should see only his 2 notes) ==="
curl -s "$SUPABASE_URL/rest/v1/notes?select=*" \
-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $BOB_TOKEN"
echo
echo "=== 4. Alice tries to insert a note with Bob's user_id (should fail / insert 0 rows) ==="
curl -s -i -X POST "$SUPABASE_URL/rest/v1/notes" \
-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $ALICE_TOKEN" \
-H "Content-Type: application/json" -H "Prefer: return=representation" \
-d "{\"user_id\":\"$BOB_UID\",\"title\":\"Malicious\",\"body\":\"hack\"}"
echo
echo "=== 5. Alice inserts a note with her own user_id (should succeed) ==="
curl -s -i -X POST "$SUPABASE_URL/rest/v1/notes" \
-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $ALICE_TOKEN" \
-H "Content-Type: application/json" -H "Prefer: return=representation" \
-d "{\"user_id\":\"$ALICE_UID\",\"title\":\"Test insert\",\"body\":\"testing\"}"
echo
=== 1. Anonymous SELECT (should return empty array) ===
{"code":"42501","details":null,"hint":"Grant the required privileges to the current role with: GRANT SELECT ON public.notes TO anon;","message":"permission denied for table notes"}
=== 2. Alice SELECT (should see only her 2 notes) ===
[{"id":1,"user_id":"a952f866-0095-4ab1-8d6e-d2dba6f11616","title":"Groceries","body":"milk, eggs, coffee"},
{"id":2,"user_id":"a952f866-0095-4ab1-8d6e-d2dba6f11616","title":"Call plumber","body":"kitchen sink leaks"}]
=== 3. Bob SELECT (should see only his 2 notes) ===
[{"id":3,"user_id":"0d2b1cef-2c25-4c64-ac8b-37088be1063c","title":"Gym plan","body":"push pull legs"},
{"id":4,"user_id":"0d2b1cef-2c25-4c64-ac8b-37088be1063c","title":"Trip ideas","body":"mountains in october"}]
=== 4. Alice tries to insert a note with Bob's user_id (should fail / insert 0 rows) ===
HTTP/2 403
date: Fri, 24 Jul 2026 04:27:33 GMT
content-type: application/json; charset=utf-8
cf-ray: a2002c4fa99b3650-FRA
cf-cache-status: DYNAMIC
set-cookie: __cf_bm=0vOVbKctFnBzu1AtBoSZNoqKZkl6w9hMX0nCiJ5spvg-1784867253.7059355-1.0.1.1-eRZ6c2eyNfL12_B9PjdyA2PlTYj4pQ606WdtJLOwnhA4.Fj1n4LuxkE8E6zjCqtBsa3HHWbNA5E501VrOxU23kZPfCKnHmfiR_pSSZBvK4DeloeQj6_YvChuthbUCFI5; HttpOnly; SameSite=None; Secure; Path=/; Domain=supabase.co; Expires=Fri, 24 Jul 2026 04:57:33 GMT
server: cloudflare
strict-transport-security: max-age=31536000; includeSubDomains; preload
vary: Accept-Encoding
proxy-status: PostgREST; error=42501
x-content-type-options: nosniff
sb-gateway-version: 1
sb-project-ref: <ref>
sb-request-id: 019f9260-edcf-7f6b-ba03-a4b1bb075178
x-envoy-attempt-count: 1
x-envoy-upstream-service-time: 3
alt-svc: h3=":443"; ma=86400
{"code":"42501","details":null,"hint":null,"message":"new row violates row-level security policy for table \"notes\""}
=== 5. Alice inserts a note with her own user_id (should succeed) ===
HTTP/2 201
date: Fri, 24 Jul 2026 04:27:33 GMT
content-type: application/json; charset=utf-8
content-length: 98
content-range: */*
cf-ray: a2002c50ac511e45-FRA
cf-cache-status: DYNAMIC
server: cloudflare
strict-transport-security: max-age=31536000; includeSubDomains; preload
x-content-type-options: nosniff
content-profile: public
preference-applied: return=representation
sb-gateway-version: 1
sb-project-ref: <ref>
sb-request-id: 019f9260-ee71-7386-a472-8c40cca351df
x-envoy-attempt-count: 1
x-envoy-upstream-service-time: 4
set-cookie: __cf_bm=hu2haYTUYtJTJriNlem58LPGmn1mG3vT4mCZhaTdH7w-1784867253.8680847-1.0.1.1-cqc3h8C1vsBa_gl06_JJ9E_I1R8e2EGwpVPhoycIN95iM4PuXvZTqNTz7JPEcYQbASKRH0sBYdfZRRLr8rKCAKwV.SQgJBabubw5iCAxJF_79yUbKuMn1h2MJkQw9GB1; HttpOnly; SameSite=None; Secure; Path=/; Domain=supabase.co; Expires=Fri, 24 Jul 2026 04:57:33 GMT
alt-svc: h3=":443"; ma=86400
[{"id":6,"user_id":"a952f866-0095-4ab1-8d6e-d2dba6f11616","title":"Test insert","body":"testing"}]
ALICE_TOKEN=$(cat /tmp/alice.tok); BOB_TOKEN=$(cat /tmp/bob.tok)
ALICE_UID=$(cat /tmp/alice.uid); BOB_UID=$(cat /tmp/bob.uid)
echo "=== 6. Bob tries to update Alice's note (id=6) -> should affect 0 rows ==="
curl -s -i -X PATCH "$SUPABASE_URL/rest/v1/notes?id=eq.6" \
-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $BOB_TOKEN" \
-H "Content-Type: application/json" -H "Prefer: return=representation" \
-d '{"title":"Hacked by bob"}'
echo
echo "=== 7. Bob tries to delete Alice's note (id=6) -> should affect 0 rows ==="
curl -s -i -X DELETE "$SUPABASE_URL/rest/v1/notes?id=eq.6" \
-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $BOB_TOKEN" \
-H "Prefer: return=representation"
echo
echo "=== 8. Alice tries to reassign her own note's user_id to Bob (should fail due to WITH CHECK) ==="
curl -s -i -X PATCH "$SUPABASE_URL/rest/v1/notes?id=eq.6" \
-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $ALICE_TOKEN" \
-H "Content-Type: application/json" -H "Prefer: return=representation" \
-d "{\"user_id\":\"$BOB_UID\"}"
echo
echo "=== 9. Alice updates her own note title (should succeed) ==="
curl -s -i -X PATCH "$SUPABASE_URL/rest/v1/notes?id=eq.6" \
-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $ALICE_TOKEN" \
-H "Content-Type: application/json" -H "Prefer: return=representation" \
-d '{"title":"Test insert updated"}'
echo
echo "=== 10. Alice deletes her own test note (cleanup, should succeed) ==="
curl -s -i -X DELETE "$SUPABASE_URL/rest/v1/notes?id=eq.6" \
-H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $ALICE_TOKEN" \
-H "Prefer: return=representation"
echo
echo "=== 11. Anonymous INSERT attempt (should fail) ==="
curl -s -i -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_UID\",\"title\":\"anon inject\",\"body\":\"x\"}"
echo
=== 6. Bob tries to update Alice's note (id=6) -> should affect 0 rows ===
HTTP/2 200
date: Fri, 24 Jul 2026 04:27:44 GMT
content-type: application/json; charset=utf-8
content-length: 2
content-range: */*
cf-ray: a2002c91e8f4d20a-FRA
cf-cache-status: DYNAMIC
server: cloudflare
strict-transport-security: max-age=31536000; includeSubDomains; preload
x-content-type-options: nosniff
content-profile: public
preference-applied: return=representation
sb-gateway-version: 1
sb-project-ref: <ref>
sb-request-id: 019f9261-1739-7616-9824-6d143bbe5f83
x-envoy-attempt-count: 1
x-envoy-upstream-service-time: 3
set-cookie: __cf_bm=kDWMAYOfM46QY2V6iF6l2H40iUPNlI9y5btt46Uo5dk-1784867264.3070345-1.0.1.1-8X3aDnzm0zvl2845kmISYrTvfHbf03rupoaQXfqEpe0GsZ2VPHItvC7vofyBVdiuicVr7OjdztJAON7aUL3C_LY3nNjMZ2X3O085O03AJXY.sRyl5oSkH5PlenQ1l0e3; HttpOnly; SameSite=None; Secure; Path=/; Domain=supabase.co; Expires=Fri, 24 Jul 2026 04:57:44 GMT
alt-svc: h3=":443"; ma=86400
[]
=== 7. Bob tries to delete Alice's note (id=6) -> should affect 0 rows ===
HTTP/2 200
date: Fri, 24 Jul 2026 04:27:44 GMT
content-type: application/json; charset=utf-8
content-length: 2
content-range: */*
cf-ray: a2002c92fab5dc88-FRA
cf-cache-status: DYNAMIC
server: cloudflare
strict-transport-security: max-age=31536000; includeSubDomains; preload
x-content-type-options: nosniff
content-profile: public
preference-applied: return=representation
sb-gateway-version: 1
sb-project-ref: <ref>
sb-request-id: 019f9261-17dc-7fd1-90ef-dfdffe1b5a98
x-envoy-attempt-count: 1
x-envoy-upstream-service-time: 2
set-cookie: __cf_bm=1XbaosG8RRB7kocR8gFTS60MxZFtUJQrAUqv.ts97uc-1784867264.4724634-1.0.1.1-IXxriZnCsGqyRCLOsR_58XKSpVe9Xz1V2y.Qu7TCe0R6.vbOJhyv_7ZSbno7ABjqW2cUBc99tOEPCaEYSHNylujpI9KisXy62J8Lnn1cCagkm9ktqf_FF_9IiprymBTF; HttpOnly; SameSite=None; Secure; Path=/; Domain=supabase.co; Expires=Fri, 24 Jul 2026 04:57:44 GMT
alt-svc: h3=":443"; ma=86400
[]
=== 8. Alice tries to reassign her own note's user_id to Bob (should fail due to WITH CHECK) ===
HTTP/2 403
date: Fri, 24 Jul 2026 04:27:45 GMT
content-type: application/json; charset=utf-8
cf-ray: a2002c952bc0284d-FRA
cf-cache-status: DYNAMIC
set-cookie: __cf_bm=5FtptyP3ajJFoOU2NHlhGZAnUSRjlHEgEwXKJyhYP9s-1784867264.8211188-1.0.1.1-YAVY8BbmhetwjYV9IJ4zMUjfkGy0.FHySdcJ9OWoXgI.tqXDHCvUifSOHdwWxUke3w6UvlSrwfI_RC8pU1qBOq_UxdFD8Un3a4_VANRHWLHTXF5k2swEsM0DKoBGlhkE; HttpOnly; SameSite=None; Secure; Path=/; Domain=supabase.co; Expires=Fri, 24 Jul 2026 04:57:45 GMT
server: cloudflare
strict-transport-security: max-age=31536000; includeSubDomains; preload
vary: Accept-Encoding
proxy-status: PostgREST; error=42501
x-content-type-options: nosniff
sb-gateway-version: 1
sb-project-ref: <ref>
sb-request-id: 019f9261-1939-74c8-9466-a543770b5fb4
x-envoy-attempt-count: 1
x-envoy-upstream-service-time: 2
alt-svc: h3=":443"; ma=86400
{"code":"42501","details":null,"hint":null,"message":"new row violates row-level security policy for table \"notes\""}
=== 9. Alice updates her own note title (should succeed) ===
HTTP/2 200
date: Fri, 24 Jul 2026 04:27:45 GMT
content-type: application/json; charset=utf-8
content-range: 0-0/*
cf-ray: a2002c974f6b4db7-FRA
cf-cache-status: DYNAMIC
set-cookie: __cf_bm=Xvmp8aPe4DtDZkhpuMpF2TyQm1hje1lyvnXTfuyp5aM-1784867265.1617742-1.0.1.1-EQbCdFcb0.AuctAt59QsWt09Yba00ezg9DAAi7IGyqOyWPf96UKzjG2hA8M8mzLjAgL.srpwaQ8.auLo73zEysgpFBOwbQ1sDdHpifRUanLvCo8xbDt6rAEcWVT5Ecvx; HttpOnly; SameSite=None; Secure; Path=/; Domain=supabase.co; Expires=Fri, 24 Jul 2026 04:57:45 GMT
server: cloudflare
strict-transport-security: max-age=31536000; includeSubDomains; preload
vary: Accept-Encoding
x-content-type-options: nosniff
content-profile: public
preference-applied: return=representation
sb-gateway-version: 1
sb-project-ref: <ref>
sb-request-id: 019f9261-1a8f-77bb-842b-128c93f405ff
x-envoy-attempt-count: 1
x-envoy-upstream-service-time: 3
alt-svc: h3=":443"; ma=86400
[{"id":6,"user_id":"a952f866-0095-4ab1-8d6e-d2dba6f11616","title":"Test insert updated","bodyALICE_TOKEN=$(cat /tmp/alice.tok) echo "=== check note 6 deleted? ===" curl -s "$SUPABASE_URL/rest/v1/notes?id=eq.6&select=*" \ -H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $ALICE_TOKEN" echo echo "=== final DB state (via service role / psql) ===" psql "$SUPABASE_DB_URL" -c "SELECT id, user_id, title FROM public.notes ORDER BY id;"
=== check note 6 deleted? === [] === final DB state (via service role / psql) === id | user_id | title ----+--------------------------------------+-------------- 1 | a952f866-0095-4ab1-8d6e-d2dba6f11616 | Groceries 2 | a952f866-0095-4ab1-8d6e-d2dba6f11616 | Call plumber 3 | 0d2b1cef-2c25-4c64-ac8b-37088be1063c | Gym plan 4 | 0d2b1cef-2c25-4c64-ac8b-37088be1063c | Trip ideas (4 rows)
echo "=== service role can see all rows (bypasses RLS) ===" curl -s "$SUPABASE_URL/rest/v1/notes?select=*" \ -H "apikey: $SUPABASE_SERVICE_ROLE_KEY" -H "Authorization: Bearer $SUPABASE_SERVICE_ROLE_KEY" echo echo "=== anon key, no bearer override (default) ===" curl -s -i "$SUPABASE_URL/rest/v1/notes?select=*" -H "apikey: $SUPABASE_ANON_KEY"
=== service role can see all rows (bypasses RLS) ===
[{"id":1,"user_id":"a952f866-0095-4ab1-8d6e-d2dba6f11616","title":"Groceries","body":"milk, eggs, coffee"},
{"id":2,"user_id":"a952f866-0095-4ab1-8d6e-d2dba6f11616","title":"Call plumber","body":"kitchen sink leaks"},
{"id":3,"user_id":"0d2b1cef-2c25-4c64-ac8b-37088be1063c","title":"Gym plan","body":"push pull legs"},
{"id":4,"user_id":"0d2b1cef-2c25-4c64-ac8b-37088be1063c","title":"Trip ideas","body":"mountains in october"}]
=== anon key, no bearer override (default) ===
HTTP/2 401
date: Fri, 24 Jul 2026 04:27:55 GMT
content-type: application/json; charset=utf-8
cf-ray: a2002cd65ce6d271-FRA
cf-cache-status: DYNAMIC
set-cookie: __cf_bm=yef3l.N9a3g99chg.64ppy.7QU1httvbo3to7MG4A6E-1784867275.2557278-1.0.1.1-DvRSqdLoFSJZedL9imTA6yINdLUMIKUlHzioM0Z9a1kM6oUAGQtzh3lz_z410298qLfIu.o8WfV6rs_1mejHcp0SrC0SDFBbvoPhimxBTqhKpw5kQFosy.A8hV2esNxf; HttpOnly; SameSite=None; Secure; Path=/; Domain=supabase.co; Expires=Fri, 24 Jul 2026 04:57:55 GMT
server: cloudflare
strict-transport-security: max-age=31536000; includeSubDomains; preload
vary: Accept-Encoding
www-authenticate: Bearer
proxy-status: PostgREST; error=42501
x-content-type-options: nosniff
sb-gateway-version: 1
sb-project-ref: <ref>
sb-request-id: 019f9261-41fb-7e69-b9dd-7207be447c83
x-envoy-attempt-count: 1
x-envoy-upstream-service-time: 1
alt-svc: h3=":443"; ma=86400
{"code":"42501","details":null,"hint":"Grant the required privileges to the current role with: GRANT SELECT ON public.notes TO anon;","message":"permission denied for table notes"}rm -f /tmp/alice.tok /tmp/bob.tok /tmp/alice.uid /tmp/bob.uid /tmp/lockdown.sql echo cleaned up temp files
cleaned up temp files
10 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