Postgres RLS Recursion Hatasını JWT Custom Claim ile Çözmek
Basit bir GRANT satırı Postgres'te sonsuz RLS döngüsüne (42P17) yol açtı. user_roles tablosunu politika değerlendirmesinden çıkarıp JWT claim'ine taşıyan çözüm ve bedelleri.
Bir Supabase projesinde authenticated rolüne yeni bir alt rol eklenmesi, beklenmedik şekilde Postgres'te '42P17: infinite recursion detected in policy' hatasına yol açar. Sorun, hatalı yazılmış tek bir politikadan değil, RLS'in Postgres rol kalıtımıyla nasıl etkileştiğinden kaynaklanır: authenticated'a TO ile yazılmış herhangi bir politika, o role eklenen her alt role de uygulanır. Bu da user_roles tablosuna dolaylı olarak referans veren başka bir politika üzerinden döngü oluşmasına yeter; sorunu bir yerde temizlemek onu başka bir yere taşımaktan öteye geçmez.
Yazar üç çözümü de dener: SECURITY DEFINER fonksiyonuyla RLS'i atlatmak kısa vadede işe yarar ama denetlenebilirliği yok eden opak bir teknik borç bırakır; rol izolasyonu ise geniş paylaşılan tablolarda ölçeklenmez. Kalıcı çözüm, sorunu veritabanından tamamen çıkarmaktır: Supabase'in Custom Access Token Hook mekanizmasıyla kullanıcı rolü, girişte imzalı bir JWT claim'i olarak eklenir ve politikalar artık user_roles'u sorgulamak yerine auth.jwt()->>'user_role' değerini okur.
Sonuç, döngünün yapısal olarak imkansız hale gelmesidir çünkü izin tablosu artık izin değerlendirme sürecinin bir parçası değildir. Ancak bu çözüm yeni bir tek nokta arızası yaratır: hook devre dışı kalırsa veya yanlış tanımlanırsa claim JWT'den kaybolur ve tüm oturumlar erişimi reddedilir. Bu, sessizce süregelen bir recursion yerine anında görünür ve gürültülü bir kesinti anlamına gelir—yazarın tercih ettiği türden bir hata.
Genel çıkarım, mühendisler için nettir: authenticated üyeliğine yeni bir rol eklemek asla nötr bir işlem değildir ve izin tablolarının izin değerlendirme yolunun içinde bulunmaması gerekir; JWT, bir oturumun kendisi hakkında bildiklerini taşımak için tam olarak tasarlanmış yerdir.