Asıl sorun neydi
Supabase, Postgres tablolarını otomatik oluşturulan bir REST API üzerinden sunar. Frontend'in bu API ile anon key (publishable key olarak da bilinir) kullanarak konuşur. Bu anahtarın herkese açık olması zaten amaçlanmıştır. JavaScript paketinle birlikte gönderilir ve herkes onu DevTools'ta okuyabilir.
Her tabloda Row Level Security (RLS) makul politikalarla açık olduğu sürece bu tasarımda sorun yoktur. Belirli bir isteğin hangi satırları görebileceğine RLS karar verir. TheCyberSecGuru'nun belirttiği gibi, Supabase'in kendi dokümantasyonundan alıntıyla: "RLS olmadan, anon key'ine sahip herkes tüm verilere erişebilir."
Yani hata zinciri şöyle işliyor:
- Bir yapay zeka aracı (ya da yorgun bir insan) create table profiles (...) üretir.
- Kimse enable row level security eklemez.
- Uygulama testlerde kusursuz çalışır, çünkü hata tam olarak fazla serbest olmasıdır.
- Paketinden anon key'i kopyalayan herkes o tabloda select * çalıştırabilir.
RLS'in nasıl çalıştığına dair arka planı merak ediyorsan Supabase Row Level Security rehberimiz temelleri anlatıyor. Aşağıda test etmeye ve düzeltmeye odaklanacağız.
Uygulamanı nasıl kontrol edersin (5 dakika)
1. Araştırmacıların yaptığı gibi dışarıdan bak. Canlı siteni aç, DevTools'u aç ve yüklenen JavaScript içinde supabase.co ara. Proje URL'ini ve hemen yanında anon key'i bulacaksın. Ardından bir terminalden, gizli saydığın bir tablodan bir satır iste:
curl "https://YOUR-PROJECT.supabase.co/rest/v1/profiles?select=*&limit=1" \
-H "apikey: YOUR_ANON_KEY" \
-H "Authorization: Bearer YOUR_ANON_KEY"Yanıt sana ne söylüyor:
- Boş bir dizi [] ya da yetki hatası: iyi, anonim kullanıcılar o tabloyu okuyamıyor.
- Gerçek satırlar: o 7.403 siteyle aynı sorunu yaşıyorsun.
Kullanıcı verisi tutan her tablo için tekrarla: profiles, users, messages, orders, bookings, leads, waitlist ve benzerleri. Bekleme listesi ve iletişim formu tabloları kolayca unutulur ve neredeyse her zaman e-posta içerir.
Paketin içindeyken bulduğun anahtarın service_role key değil anon key olduğunu da kontrol et. Service role, RLS'i tamamen atlar. Frontend'inde duruyorsa sızmış say ve hemen rotate et: paketindeki her şey her ziyaretçi ve sayfadaki her üçüncü taraf script tarafından okunabilir, Polymarket kullanıcılarının öğrendiği gibi, ve botlar şimdiden sızan anahtarların peşinde. Uygulamanın gizli anahtarlarını kesinti olmadan rotate etmek üzerine yazımız rotasyonu anlatıyor, istemci tarafı JavaScript'te açığa çıkan API anahtarları ise paketlere başka nelerin sızma eğiliminde olduğunu ele alıyor.
2. İçeriden kontrol et. Supabase SQL editöründe public şemasındaki her tabloyu ve RLS'in açık olup olmadığını listele:
select tablename, rowsecurity
from pg_tables
where schemaname = 'public'
order by rowsecurity, tablename;rowsecurity = false olan her satır anon key ile okunabilir (ve muhtemelen yazılabilir). Supabase panelindeki Security Advisor da bunları işaretler. Uyarılarını geçiştirmek yerine oku.
3. "RLS açık ama yine de herkese açık" durumlarını ara. RLS'in etkin olması bitiş çizgisi değil. Politikalarını listele:
select tablename, policyname, roles, cmd, qual
from pg_policies
where schemaname = 'public';anon ya da public için geçerli bir select politikasında qual = true olmasına dikkat et. Bu, "herkes her şeyi okuyabilir" diyen bir politikadır ve RLS'in hiç olmamasıyla aynıdır. Yapay zeka araçları bir hatayı ortadan kaldırmak için bazen tam olarak bunu üretir.
Nasıl düzeltirsin
Her tabloda RLS'i aç:
alter table public.profiles enable row level security;
alter table public.messages enable row level security;
-- repeat for every table in publicRLS açık ve hiç politika yokken tablo kilitlidir: anon key hiçbir şey alamaz. Bu güvenli bir varsayılandır. Şimdi yalnızca gerçekten ihtiyaç duyduğun erişimi ekle.
Dar kapsamlı politikalar yaz. Tipik bir "kullanıcılar yalnızca kendi satırını görüp düzenleyebilir" kurulumu:
create policy "Read own profile"
on public.profiles for select
to authenticated
using ((select auth.uid()) = user_id);
create policy "Update own profile"
on public.profiles for update
to authenticated
using ((select auth.uid()) = user_id)
with check ((select auth.uid()) = user_id);Akılda tutulması gerekenler:
- Veri gerçekten herkese açık değilse (blog yazıları, ürün listeleri) kapsamı public yerine authenticated ile sınırla.
- Okuma ve yazmayı ayır. Bir tablo herkese açık okunmak için güvenli olabilir ama insert işlemlerinin yine de sıkı kısıtlanması gerekebilir.
- Herkese açık tablolar yine de sütun sızdırır. Bir profiles tablosu görünen adlar için herkese açıksa, aynı tablodaki email ya da phone sütunu da herkese açıktır. Gizli alanları ayrı bir tabloya taşı ya da yalnızca güvenli sütunları içeren bir view sun.
- View'lar RLS'i atlayabilir. Postgres'te view'lar varsayılan olarak sahibinin yetkileriyle çalışır. View'ları security_invoker = true ile oluştur ki çağıranın RLS'i uygulansın.
- Storage ayrı bir konu. Herkese açık bucket'lar dosyaları URL'e sahip herkese sunar. Yüklenen kimlik belgeleri, faturalar ve gizli kullanıcıların avatarları, storage politikalarıyla korunan özel bucket'larda durmalı.
Bunu sonradan yapılan bir temizlik değil, varsayılan hâline getir. Araştırmacıların örneklemi on aylık yeni uygulamaları kapsıyor; yani bu her yeni tabloda yeniden yaşanıyor. Unutulmasını zorlaştır:
- Yapay zeka aracının proje talimatlarına bir kural ekle: public içindeki her create table'ın ardından enable row level security ve açık politikalar gelmeli.
- Merge etmeden önce üretilen migration'larda karşılığında enable row level security olmayan create table olup olmadığını incele.
- Dış kontrolü yalnızca lansmanda değil, her şema değişikliğinden sonra yeniden çalıştır.
Supabase yerine Firebase kullanıyorsan aynı türden hata orada da açık güvenlik kuralları biçiminde var. Bkz. Firebase Security Rules: veritabanını açık bırakmayı bırak.
Açığa çıkmış veri bulduysan
Politikayı düzeltmek gelecekteki okumaları durdurur ama geçmiştekileri geri almaz. Neyin ne kadar süre erişilebilir olduğu konusunda kendine karşı dürüst ol. Supabase API loglarında tanımadığın IP'lerden gelen olağandışı toplu okumaları kontrol et, okunabilir tablolarda saklanan tüm gizli anahtarları rotate et ve e-posta, kimlik belgesi ya da ödeme bilgisi gibi kişisel veriler açığa çıktıysa bildirim yükümlülüklerini araştır. AB'de bu muhtemelen GDPR ihlal kurallarının geçerli olduğu anlamına gelir.
Hızlı kontrol listesi
İşi bitti saymadan önce şunlardan emin ol:
- public içindeki her tabloda rowsecurity = true.
- Gizli veri tutan tablolarda hiçbir select politikası using (true) kullanmıyor.
- Politikalar authenticated ile sınırlı ve auth.uid()'ye bağlı.
- Gizli sütunlar herkese açık okunabilen tablolarda durmuyor.
- View'lar security_invoker = true kullanıyor.
- Frontend'inde yalnızca anon key var, asla service_role yok.
- Gizli dosyalar özel storage bucket'larında.
- Her şema değişikliğinden sonra dışarıdan yeniden test ediyorsun.
Uygulamanı dışarıdan kontrol et
Yapay zekayla geliştirilen uygulamalar için güvenlik kontrol listemiz, lansmandan önce kontrol etmeye değer diğer şeyleri kapsıyor. Bu araştırmanın rahatsız edici yanı, yöntemin ne kadar sıradan olduğu: herkese açık anahtar, salt okunur istek, veri geri geliyor. Bir saldırganın gördüğü manzara tam olarak bu ve düzenli olarak kontrol etmen gereken de bu.
AppSafe canlı uygulamanı dışarıdan tarar. Doğrulanmış tam tarama, açık Supabase ve Firebase veritabanları için salt okunur kontroller içerir ve JavaScript paketlerinde açığa çıkan API anahtarlarını arar; zamanlanmış taramalar ise bir deploy'dan sonra bir şey değiştiğinde seni uyarabilir. Bir raporun neye benzediğini merak mı ediyorsun? Örnek rapora göz at.
Uygulamanı şimdi ücretsiz tara ve bir yabancının neler görebildiğini başkası senden önce öğrenmeden öğren.
