Tüm yazılar
Güvenlik olayı6 dk okuma

7.403 Lovable uygulaması kullanıcı verisi sızdırdı. Senin Supabase tablon da aralarında mı?

Translucent database cylinder with an open glowing drawer spilling envelopes and ID cards into the dark

Bu hafta Hamburg merkezli güvenlik firması Crissy Field, Alman iş gazetesi Handelsblatt ile birlikte Lovable ile geliştirilen uygulamalar hakkında bir araştırma yayımladı. Common Crawl arşivinden Aralık 2025 ile Eylül 2026 arasında geliştirilmiş uygulamaları kapsayan 159.041 Lovable projesi çektiler ve basit bir soru sordular: veritabanı, giriş yapmamış tamamen yabancı birine ne veriyor?

Cevap pek iç açıcı değildi. Startup Fortune'un haberine göre bu projelerin 34.545'i backend olarak Supabase kullanıyordu ve bunların 24.664'ü hiçbir kimlik doğrulama olmadan veritabanı kayıtları döndürdü. 7.403 sitede bu kayıtlar, 10.560 tabloya dağılmış e-posta adreslerini içeriyordu. RuntimeWire'ın aktardığına göre açığa çıkan veriler arasında pasaport numaraları, ödeme bilgileri ve 200.000'den fazla sohbet mesajı vardı. Bir hizmet sağlayıcının 1.000'den fazla pasaport kaydı okunabilir durumdaydı; bir yaşam tarzı forumu ise bu sohbet mesajlarıyla birlikte 80.000 kullanıcı profilini sızdırdı.

İşin içinde zero-day yoktu ve dramatik anlamda hiçbir şey "hacklenmedi". Araştırmacılar, her sitenin kendi herkese açık anahtarıyla salt okunur istekler gönderdi. Veri, isteyen herkes için orada duruyordu.

Supabase üzerinde, özellikle de bir yapay zeka geliştirme aracıyla herhangi bir şey yayına aldıysan, bu yazı kendi uygulamanı birkaç dakikada kontrol etmeni ve doğru şekilde düzeltmeni adım adım anlatıyor. Bu yalnızca Lovable'ın sorunu değil: Bolt, Cursor, v0 ve elle yazılmış uygulamalar da aynı hatayı yapıyor.

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.

Supabase Row Level Security: eksiksiz rehber

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.

Frontend kodunda API anahtarları: neyi yayınlamak güvenli

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 public

RLS 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.

Firebase Security Rules: veritabanını kilitle

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.

Bu kontrol listesini otomatik çalıştır

Tek bir ücretsiz tarama, uygulamanı tüm bu sorunlara karşı kontrol eder ve her bulgu için sana bir AI düzeltme prompt'u verir.

Uygulamamı ücretsiz tara