Row-level security PostgreSQL untuk SaaS membantu menegakkan isolasi tenant di database. Pelajari kapan menggunakannya, cara menulis kebijakan, dan yang harus dihindari.

Di aplikasi SaaS, bug keamanan paling berbahaya sering muncul setelah Anda melakukan scale. Anda mulai dengan aturan sederhana seperti “pengguna hanya bisa melihat data tenant mereka,” lalu Anda cepat menambahkan endpoint baru, menambahkan query reporting, atau memperkenalkan join yang diam-diam melewatkan pengecekan.
Otorisasi hanya di level aplikasi mudah pecah karena aturannya tersebar. Satu controller memeriksa tenant_id, controller lain memeriksa membership, job latar lupa, dan jalur “admin export” tetap "sementara" selama berbulan-bulan. Bahkan tim yang hati-hati bisa melewatkan satu titik.
PostgreSQL row-level security (RLS) memecahkan masalah spesifik: ia membuat database menegakkan baris mana yang terlihat untuk permintaan tertentu. Model mentalnya sederhana: setiap SELECT, UPDATE, dan DELETE otomatis difilter oleh kebijakan, seperti setiap permintaan difilter oleh middleware otentikasi.
Bagian "baris" itu penting. RLS tidak melindungi semuanya:
Contoh konkret: Anda menambahkan endpoint yang menampilkan daftar projects dengan join ke invoices untuk dashboard. Dengan otorisasi yang hanya di aplikasi, mudah untuk memfilter projects berdasarkan tenant tetapi lupa memfilter invoices, atau melakukan join pada key yang melewati tenant. Dengan RLS, kedua tabel bisa menegakkan isolasi tenant, sehingga query gagal aman daripada membocorkan data.
Trade-off-nya nyata. Anda menulis lebih sedikit kode otorisasi berulang dan mengurangi jumlah tempat yang bisa bocor. Tapi Anda juga mengambil pekerjaan baru: Anda harus merancang kebijakan dengan hati-hati, mengetesnya lebih awal, dan menerima bahwa sebuah kebijakan bisa memblokir query yang Anda harapkan bekerja.
RLS terasa seperti kerja tambahan sampai aplikasi Anda melewati beberapa endpoint. Jika Anda punya batas tenant yang ketat dan banyak jalur query (layar daftar, pencarian, ekspor, alat admin), menaruh aturan di database berarti Anda tidak harus ingat menambahkan filter yang sama di mana-mana.
RLS cocok kuat ketika aturan itu membosankan dan universal: “pengguna hanya bisa melihat baris untuk tenant mereka” atau “pengguna hanya bisa melihat projects yang mereka menjadi anggotanya.” Dalam pengaturan seperti itu, kebijakan mengurangi kesalahan karena setiap SELECT, UPDATE, dan DELETE melewati pintu yang sama, bahkan ketika sebuah query ditambahkan kemudian.
Ini juga membantu pada aplikasi yang banyak baca (read-heavy) di mana logika penyaringan konsisten. Jika API Anda punya 15 cara berbeda memuat invoices (berdasarkan status, tanggal, customer, pencarian), RLS memungkinkan Anda berhenti mengimplementasikan penyaringan tenant pada setiap query dan fokus pada fitur.
RLS menambah sakit ketika aturannya bukan berbasis baris. Aturan per-field seperti "Anda bisa melihat gaji tapi bukan bonus" atau "mask kolom ini kecuali Anda HR" sering berubah menjadi SQL yang canggung dan pengecualian yang sulit dipelihara.
Ini juga kurang cocok untuk pelaporan berat yang memang membutuhkan akses luas. Tim sering membuat role bypass untuk "hanya pekerjaan ini saja," dan di situlah kesalahan menumpuk.
Sebelum berkomitmen, putuskan apakah Anda ingin database menjadi penjaga gerbang akhir. Jika ya, rencanakan disiplin: uji perilaku database (bukan hanya respons API), perlakukan migrasi sebagai perubahan keamanan, hindari bypass cepat, tentukan bagaimana job latar mengautentikasi, dan jaga kebijakan tetap kecil dan dapat diulang.
Jika Anda menggunakan tooling yang menghasilkan backend, itu bisa mempercepat delivery, tapi tidak menghilangkan kebutuhan untuk peran yang jelas, tes, dan model tenant sederhana. (Misalnya, Koder.ai menggunakan Go dan PostgreSQL untuk backend yang digenerasi, dan Anda tetap ingin merancang RLS dengan sengaja daripada “menaburnya nanti.”)
RLS paling mudah ketika skema Anda sudah jelas menunjukkan siapa yang memiliki apa. Jika Anda mulai dengan model kabur dan mencoba “memperbaiki di kebijakan,” biasanya Anda mendapatkan query lambat dan bug yang membingungkan.
Pilih satu kunci tenant (seperti org_id) dan gunakan secara konsisten. Sebagian besar tabel yang dimiliki tenant seharusnya memilikinya, bahkan jika mereka juga mereferensikan tabel lain yang memilikinya. Ini menghindari join di dalam kebijakan dan menjaga pemeriksaan USING tetap sederhana.
Aturan praktis: jika sebuah baris harus hilang ketika pelanggan membatalkan, besar kemungkinan baris itu perlu org_id.
Kebijakan RLS biasanya menjawab satu pertanyaan: “Apakah pengguna ini anggota org ini, dan apa yang bisa mereka lakukan?” Itu sulit disimpulkan dari kolom ad-hoc.
Jaga tabel inti tetap kecil dan sederhana:
users (satu baris per orang)orgs (satu baris per tenant)org_memberships (user_id, org_id, role, status)project_memberships untuk akses per-projectDengan itu, kebijakan Anda bisa memeriksa membership dengan satu lookup yang terindeks.
Tidak semuanya perlu org_id. Tabel referensi seperti countries, product categories, atau plan types sering dibagikan ke semua tenant. Jadikan mereka read-only untuk sebagian besar role, dan jangan kaitkan ke satu org.
Data milik tenant (projects, invoices, tickets) sebaiknya menghindari menarik detail tenant-spesifik melalui tabel bersama. Jaga tabel bersama minimal dan stabil.
Foreign key tetap bekerja dengan RLS, tapi penghapusan bisa mengejutkan jika role yang melakukan delete tidak bisa “melihat” baris dependent. Rencanakan cascade dengan hati-hati dan uji alur delete nyata.
Index kolom yang difilter oleh kebijakan Anda, khususnya org_id dan kunci membership. Sebuah kebijakan yang terlihat seperti WHERE org_id = ... tidak boleh menjadi full-table scan ketika tabel mencapai jutaan baris.
RLS adalah saklar per-tabel. Setelah diaktifkan, PostgreSQL berhenti mempercayai kode aplikasi Anda untuk mengingat filter tenant. Setiap SELECT, UPDATE, dan DELETE difilter oleh kebijakan, dan setiap INSERT dan UPDATE divalidasi oleh kebijakan.
Perubahan mental terbesar: dengan RLS aktif, query yang dulu mengembalikan data bisa mulai mengembalikan nol baris tanpa error. Itu PostgreSQL melakukan kontrol akses.
Kebijakan adalah aturan kecil yang terikat ke tabel. Mereka menggunakan dua pemeriksaan:
USING adalah filter baca. Jika sebuah baris tidak cocok dengan USING, baris itu tak terlihat untuk SELECT, dan tidak bisa ditarget oleh UPDATE atau DELETE.WITH CHECK adalah gerbang tulis. Ia menentukan baris baru atau yang diubah apa yang diizinkan untuk INSERT atau UPDATE.Pola SaaS umum: USING memastikan Anda hanya melihat baris dari tenant Anda, dan WITH CHECK memastikan Anda tidak bisa memasukkan baris ke tenant lain dengan menebak tenant ID.
Ketika Anda menambahkan lebih banyak kebijakan nanti, hal ini penting:
PERMISSIVE (default): sebuah baris diizinkan jika ada kebijakan yang mengizinkannya.RESTRICTIVE: sebuah baris diizinkan hanya jika semua kebijakan restrictive mengizinkannya (di atas perilaku permissive).Jika Anda berencana melapisi aturan seperti kecocokan tenant plus pemeriksaan peran plus membership project, kebijakan restrictive bisa membuat maksud lebih jelas, tapi mereka juga membuatnya lebih mudah mengunci diri keluar jika Anda lupa satu kondisi.
RLS perlu nilai “siapa yang memanggil” yang dapat diandalkan. Opsi umum:
app.user_id dan app.tenant_id).SET ROLE ... per permintaan), yang bisa bekerja tetapi menambah overhead operasional.Pilih satu pendekatan dan terapkan di mana-mana. Mencampur sumber identitas antar layanan adalah jalan cepat menuju bug yang membingungkan.
Gunakan konvensi yang dapat diprediksi sehingga dump skema dan log tetap terbaca. Misalnya: {table}__{action}__{rule}, seperti projects__select__tenant_match.
Jika Anda baru di RLS, mulai dengan satu tabel dan bukti kecil. Tujuannya bukan cakupan sempurna. Tujuannya membuat database menolak akses lintas-tenant bahkan ketika ada bug di aplikasi.
Asumsikan tabel projects sederhana. Pertama, tambahkan tenant_id dengan cara yang tidak merusak penulisan.
ALTER TABLE projects ADD COLUMN tenant_id uuid;
-- Backfill existing rows (example: everyone belongs to a default tenant)
UPDATE projects SET tenant_id = '11111111-1111-1111-1111-111111111111'::uuid
WHERE tenant_id IS NULL;
ALTER TABLE projects ALTER COLUMN tenant_id SET NOT NULL;
Selanjutnya, pisahkan kepemilikan dari akses. Polanya umum: satu role memiliki tabel (app_owner), role lain digunakan oleh API (app_user). Role API seharusnya bukan pemilik tabel, atau ia bisa melewati kebijakan.
ALTER TABLE projects OWNER TO app_owner;
REVOKE ALL ON projects FROM PUBLIC;
GRANT SELECT, INSERT, UPDATE, DELETE ON projects TO app_user;
Sekarang tentukan bagaimana permintaan memberi tahu Postgres tenant yang dilayani. Salah satu cara sederhana adalah setting yang bersifat request-scoped. Aplikasi Anda menyetelnya segera setelah membuka transaksi.
-- inside the same transaction as the request
SELECT set_config('app.current_tenant', '22222222-2222-2222-2222-222222222222', true);
Aktifkan RLS dan mulai dengan akses baca.
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
CREATE POLICY projects_tenant_select
ON projects
FOR SELECT
TO app_user
USING (tenant_id = current_setting('app.current_tenant')::uuid);
Buktikan bekerja dengan mencoba dua tenant berbeda dan memeriksa bahwa jumlah baris berubah.
Kebijakan baca tidak melindungi tulis. Tambahkan WITH CHECK sehingga insert dan update tidak bisa menyelundupkan baris ke tenant yang salah.
CREATE POLICY projects_tenant_write
ON projects
FOR INSERT, UPDATE
TO app_user
WITH CHECK (tenant_id = current_setting('app.current_tenant')::uuid);
Cara cepat untuk memverifikasi perilaku (termasuk kegagalan) adalah menyimpan skrip SQL kecil yang bisa Anda jalankan ulang setelah setiap migrasi:
BEGIN; SET LOCAL ROLE app_user;SELECT set_config('app.current_tenant', '\u003ctenant A\u003e', true); SELECT count(*) FROM projects;INSERT INTO projects(id, tenant_id, name) VALUES (gen_random_uuid(), '\u003ctenant B\u003e', 'bad'); (seharusnya gagal)UPDATE projects SET tenant_id = '\u003ctenant B\u003e' WHERE ...; (seharusnya gagal)ROLLBACK;Jika Anda bisa menjalankan skrip itu dan mendapatkan hasil yang sama setiap kali, Anda punya baseline andal sebelum memperluas RLS ke tabel lain.
Kebanyakan tim mengadopsi RLS setelah bosan mengulang pemeriksaan otorisasi yang sama di setiap query. Kabar baiknya: bentuk kebijakan yang Anda butuhkan biasanya konsisten.
Beberapa tabel secara alami dimiliki satu pengguna (notes, API tokens). Lainnya milik tenant di mana akses bergantung pada membership. Perlakukan ini sebagai pola berbeda.
Untuk data milik owner, kebijakan sering memeriksa created_by = app_user_id(). Untuk data tenant, kebijakan sering memeriksa apakah pengguna memiliki baris membership untuk org.
Cara praktis menjaga kebijakan terbaca adalah memusatkan identitas dalam helper SQL kecil dan menggunakannya kembali:
-- Example helpers
create function app_user_id() returns uuid
language sql stable as $$
select current_setting('app.user_id', true)::uuid
$$;
create function app_is_admin() returns boolean
language sql stable as $$
select current_setting('app.is_admin', true) = 'true'
$$;
Baca sering lebih luas daripada tulis. Misalnya, setiap anggota org bisa SELECT projects, tetapi hanya editor yang bisa UPDATE, dan hanya owner yang bisa DELETE.
Jaga eksplisit: satu kebijakan untuk SELECT (membership), satu kebijakan untuk INSERT/UPDATE dengan WITH CHECK (peran), dan satu untuk DELETE (seringkali lebih ketat dari update).
Hindari “matikan RLS untuk admin.” Sebaiknya tambahkan jalur escape di dalam kebijakan, seperti app_is_admin(), sehingga Anda tidak sengaja memberi akses penuh ke role service bersama.
Jika Anda menggunakan deleted_at atau status, masukkan itu ke dalam kebijakan SELECT (deleted_at is null). Kalau tidak, seseorang bisa “menghidupkan kembali” baris dengan membalik flag yang aplikasi anggap final.
WITH CHECK ramahINSERT ... ON CONFLICT DO UPDATE harus memenuhi WITH CHECK untuk baris setelah penulisan. Jika kebijakan Anda membutuhkan created_by = app_user_id(), pastikan upsert mengatur created_by saat insert dan tidak menimpanya saat update.
Jika Anda menghasilkan kode backend, pola-pola ini layak dijadikan template internal sehingga tabel baru mulai dengan default aman daripada lembar kosong.
RLS hebat sampai satu detail kecil membuatnya terlihat seperti PostgreSQL “secara acak” menyembunyikan atau menampilkan data. Kesalahan di bawah ini membuang waktu paling banyak.
Perangkap pertama adalah lupa WITH CHECK pada insert dan update. USING mengontrol apa yang bisa Anda lihat, bukan apa yang boleh Anda buat. Tanpa WITH CHECK, bug aplikasi bisa menulis baris ke tenant yang salah, dan Anda mungkin tidak menyadarinya karena pengguna yang sama tidak bisa membacanya kembali.
Kebocoran umum lainnya adalah "leaky join." Anda memfilter projects dengan benar, lalu join ke invoices, notes, atau files yang tidak dilindungi dengan cara yang sama. Perbaikannya ketat tapi sederhana: setiap tabel yang bisa mengungkapkan data tenant membutuhkan kebijakan sendiri, dan view tidak boleh bergantung hanya pada satu tabel yang aman.
Polanya kegagalan umum muncul cepat:
WITH CHECK.Kebijakan yang merujuk tabel yang sama (langsung atau melalui view) dapat menciptakan kejutan rekursi. Sebuah kebijakan mungkin memeriksa membership dengan menanyai view yang membaca tabel yang dilindungi lagi, menyebabkan error, query lambat, atau kebijakan yang tidak pernah cocok.
Setup role juga sumber kebingungan. Pemilik tabel dan role elevated bisa melewati RLS, sehingga tes Anda lulus sementara pengguna nyata gagal (atau sebaliknya). Selalu uji dengan role ber-privilege rendah yang digunakan aplikasi Anda.
Berhati-hatilah dengan fungsi SECURITY DEFINER. Mereka berjalan dengan hak pemilik fungsi, jadi helper seperti current_tenant_id() mungkin aman, tapi fungsi "convenience" yang membaca data bisa tanpa sengaja membaca lintas-tenant kecuali Anda merancangnya untuk menghormati RLS.
Juga setel search_path yang aman di dalam fungsi security definer. Jika tidak, fungsi bisa mengambil objek lain dengan nama yang sama, dan logika kebijakan Anda bisa diam-diam menunjuk ke hal yang salah bergantung pada state session.
Bug RLS biasanya karena konteks yang hilang, bukan "SQL buruk." Sebuah kebijakan bisa benar di atas kertas tapi tetap gagal karena session role berbeda dari yang Anda kira, atau karena permintaan tidak pernah menyetel tenant dan user yang diharapkan kebijakan.
Cara andal mereproduksi laporan produksi adalah meniru setup session yang sama secara lokal dan menjalankan query yang sama. Itu biasanya berarti:
SET ROLE app_user; (atau role API nyata)SELECT set_config('app.tenant_id', 't_123', true); dan SELECT set_config('app.user_id', 'u_456', true);SELECT current_user, current_setting('app.tenant_id', true), current_setting('app.user_id', true);Ketika Anda tidak yakin kebijakan mana yang diterapkan, periksa katalog daripada menebak. pg_policies menunjukkan setiap kebijakan, perintah, dan ekspresi USING dan WITH CHECK. Padukan itu dengan pg_class untuk mengonfirmasi RLS diaktifkan di tabel dan tidak dilewati.
Masalah performa bisa terlihat seperti masalah otorisasi. Sebuah kebijakan yang melakukan join ke tabel membership atau memanggil fungsi mungkin benar tapi lambat ketika tabel tumbuh. Gunakan EXPLAIN (ANALYZE, BUFFERS) pada query yang direproduksi dan cari sequential scans, nested loops tak terduga, atau filter yang diterapkan terlambat. Index yang hilang pada (tenant_id, user_id) dan tabel membership sering menjadi penyebab.
Juga membantu untuk mencatat tiga nilai per permintaan di lapisan aplikasi: tenant ID, user ID, dan role database yang digunakan untuk permintaan. Ketika itu tidak cocok dengan yang Anda kira, RLS akan berperilaku "salah" karena inputnya salah.
Untuk tes, simpan beberapa tenant seed dan buat kegagalan menjadi eksplisit. Suite kecil biasanya mencakup: “Tenant A tidak bisa membaca Tenant B,” “user tanpa membership tidak bisa melihat project,” “owner bisa update, viewer tidak bisa,” “insert diblok kecuali tenant_id cocok dengan konteks,” dan “override admin hanya berlaku di tempat yang dimaksudkan.”
Perlakukan RLS seperti sabuk pengaman, bukan toggle fitur. Kekurangan kecil berubah menjadi “semua orang bisa melihat data semua orang” atau “semua mengembalikan nol baris.”
Pastikan desain tabel dan aturan kebijakan cocok dengan model tenant Anda.
tenant_id). Jika tidak, tuliskan alasannya (mis. tabel referensi global).FORCE ROW LEVEL SECURITY pada tabel tersebut.USING. Tulis harus punya WITH CHECK agar insert dan update tidak bisa memindahkan baris ke tenant lain.tenant_id atau join melalui tabel membership, tambahkan index yang sesuai.Skenario sanity sederhana: pengguna tenant A bisa membaca invoice mereka sendiri, bisa memasukkan invoice hanya untuk tenant A, dan tidak bisa mengubah invoice untuk mengganti tenant_id.
RLS sekuat role yang dipakai aplikasi Anda.
bypassrls.Bayangkan aplikasi B2B di mana perusahaan (org) punya projects, dan projects punya tasks. Pengguna bisa menjadi anggota beberapa org, dan seorang pengguna bisa menjadi anggota beberapa project tapi tidak semua. Ini cocok untuk RLS karena database dapat menegakkan isolasi tenant bahkan jika endpoint API lupa filter.
Model sederhana: orgs, users, org_memberships (org_id, user_id, role), projects (id, org_id), project_memberships (project_id, user_id), tasks (id, project_id, org_id, ...). org_id di tasks itu disengaja. Ia menjaga kebijakan sederhana dan mengurangi kejutan saat join.
Kebocoran klasik terjadi ketika tasks hanya punya project_id, dan kebijakan Anda memeriksa akses melalui join ke projects. Satu kesalahan (kebijakan permissive di projects, join yang menghilangkan kondisi, atau view yang mengubah konteks) bisa mengekspos tasks dari org lain.
Jalur migrasi yang lebih aman menghindari memutus traffic produksi:
org_id ke tasks, tambahkan tabel membership).tasks.org_id dari projects.org_id, lalu tambahkan NOT NULL.Akses support biasanya terbaik ditangani dengan role break-glass yang sempit, bukan dengan menonaktifkan RLS. Jaga terpisah dari akun support normal dan buat eksplisit saat dipakai.
Dokumentasikan aturan sehingga kebijakan tidak menyimpang: variabel session mana yang harus disetel (user_id, org_id), tabel mana yang harus membawa org_id, apa arti “member”, dan beberapa contoh SQL yang seharusnya mengembalikan 0 baris bila dijalankan sebagai org yang salah.
RLS paling mudah dipakai ketika Anda memperlakukannya seperti perubahan produk. Rollout secara bertahap, buktikan perilaku dengan tes, dan simpan catatan jelas mengapa setiap kebijakan ada.
Rencana rollout yang biasanya berhasil:
projects) dan kunci itu.Setelah tabel pertama stabil, buat perubahan kebijakan menjadi sesuatu yang disengaja. Tambahkan langkah review kebijakan pada migrasi, dan sertakan catatan singkat tentang maksud (siapa yang harus mengakses apa dan mengapa) plus pembaruan tes yang sesuai. Ini mencegah "tambah OR lagi" yang perlahan-lahan berubah jadi lubang.
Jika Anda bergerak cepat, alat seperti Koder.ai (koder.ai) dapat membantu menghasilkan titik awal Go + PostgreSQL lewat chat, lalu Anda bisa melapisi kebijakan RLS dan tes dengan disiplin yang sama seperti backend yang dibuat tangan.
Akhirnya, jaga pengaman selama rollout. Ambil snapshot sebelum migrasi kebijakan, latih rollback sampai terasa biasa, dan pertahankan jalur break-glass kecil untuk support yang tidak mematikan RLS di seluruh sistem.
RLS membuat PostgreSQL menegakkan baris mana yang terlihat atau dapat ditulis untuk sebuah permintaan, sehingga isolasi tenant tidak bergantung pada setiap endpoint yang selalu mengingat filter WHERE tenant_id = .... Keuntungan utamanya adalah mengurangi bug “satu cek yang terlewat” ketika aplikasi Anda tumbuh dan query bertambah.
Biasanya sepadan ketika aturan akses konsisten dan berbasis baris, misalnya isolasi tenant atau akses berdasarkan membership, dan Anda punya banyak jalur query (pencarian, ekspor, layar admin, job latar). Tidak terlalu cocok jika aturan kebanyakan per-kolom, penuh pengecualian, atau ketika reporting lebar memang butuh pembacaan lintas-tenant.
Gunakan RLS untuk visibilitas baris dan pengendalian tulis dasar, lalu gunakan alat lain untuk hal lainnya. Privasi kolom biasanya perlu views dan hak akses kolom, dan aturan bisnis kompleks (mis. kepemilikan billing atau alur approval) tetap lebih cocok di logika aplikasi atau constraint basis data yang dirancang dengan hati-hati.
Buat role ber-privilege rendah untuk API (bukan pemilik tabel), aktifkan RLS, lalu tambahkan policy SELECT dan policy INSERT/UPDATE dengan WITH CHECK. Setel variabel session per permintaan (mis. app.current_tenant) dan verifikasi bahwa berpindah nilai itu mengubah baris yang bisa Anda lihat dan tulis.
Default yang umum adalah variabel session per permintaan, disetel di awal transaksi, seperti app.tenant_id dan app.user_id. Kunci utamanya adalah konsistensi: setiap jalur kode (web request, job, script) harus menyetel nilai yang sama yang diharapkan oleh policy, atau Anda akan mendapat perilaku "nol baris" yang membingungkan.
USING mengontrol baris yang sudah ada agar terlihat dan bisa ditarget untuk SELECT, UPDATE, dan DELETE. WITH CHECK mengontrol baris baru atau yang diubah yang diperbolehkan selama INSERT dan , sehingga mencegah "menulis ke tenant lain" meskipun aplikasi mengirim yang salah.
Karena jika Anda hanya menambahkan USING, endpoint bermasalah masih bisa INSERT atau UPDATE baris ke tenant yang salah, dan Anda mungkin tidak menyadarinya karena pengguna yang sama tidak bisa membaca baris itu kembali. Selalu pasangkan aturan baca tenant dengan WITH CHECK yang cocok untuk tulis agar data buruk tidak bisa dibuat sejak awal.
Hindari join di dalam policy dengan menaruh kunci tenant (mis. org_id) langsung di tabel yang dimiliki tenant, bahkan jika tabel itu juga mereferensikan tabel lain yang memilikinya. Tambahkan tabel membership eksplisit (org_memberships, opsional project_memberships) sehingga policy bisa melakukan satu lookup terindeks daripada inferensi rumit.
Pertama, reproduksi konteks session yang sama seperti aplikasi Anda dengan menyetel role dan setting session yang sama, lalu jalankan query yang sama. Selanjutnya, pastikan RLS aktif dan periksa pg_policies untuk melihat ekspresi USING dan WITH CHECK yang diterapkan, karena kegagalan RLS sering disebabkan oleh konteks identitas yang hilang, bukan SQL yang salah.
Iya — perlakukan kode yang digenerasi sebagai titik awal, bukan sebagai sistem keamanan lengkap. Jika Anda menggunakan Koder.ai untuk menghasilkan backend Go + PostgreSQL, Anda tetap harus mendefinisikan model tenant, menyetel identitas session secara konsisten, dan menambahkan policy serta tes dengan sengaja supaya tabel baru tidak dikirim tanpa proteksi yang benar.
UPDATEtenant_id