Pure-reference catalog of Postgres Row-Level Security (RLS) for tenant isolation. Covers enabling RLS (ALTER TABLE ... ENABLE ROW LEVEL SECURITY, default-deny semantics), CREATE POLICY syntax (USING vs WITH CHECK clauses, FOR SELECT/INSERT/UPDATE/DELETE/ALL, permissive vs restrictive, TO role_name), bypassing RLS (superuser / BYPASSRLS / table owner / FORCE ROW LEVEL SECURITY), tenant context patterns (current_user, current_setting, JWT claims via Supabase auth.uid() / auth.jwt()), performance discipline (wrapping auth functions in SELECT, index on policy-referenced columns), and anti-patterns. Use as the RLS-pattern reference for Postgres-backed tenant isolation. Consumed by tenant-leak-test-author, cross-tenant-data-leak-tests.
75
94%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Full CREATE POLICY grammar plus the clause semantics a tenant policy relies on.
CREATE POLICY policy_name ON table_name
[ AS { PERMISSIVE | RESTRICTIVE } ]
[ FOR { ALL | SELECT | INSERT | UPDATE | DELETE } ]
[ TO role_name [, ...] ]
[ USING ( using_expression ) ]
[ WITH CHECK ( check_expression ) ];| Clause | Controls |
|---|---|
USING | Which rows are visible (SELECT, UPDATE, DELETE) |
WITH CHECK | Which rows can be written (INSERT, UPDATE) |
If only USING is specified, it implicitly applies to both.
Per Postgres docs, the canonical own-data UPDATE policy uses both:
CREATE POLICY user_policy ON users
FOR UPDATE
USING (user_name = current_user)
WITH CHECK (
user_name = current_user AND
shell IN ('/bin/bash', '/bin/sh', '/bin/dash')
);USING ensures the user can only update their own row; WITH CHECK
ensures they can't update the row into a state that violates other invariants.
For SELECT/UPDATE divergence (a common tenant pattern: all users see all rows, but only modify their own):
CREATE POLICY user_sel_policy ON users
FOR SELECT
USING (true);
CREATE POLICY user_mod_policy ON users
FOR UPDATE
USING (user_name = current_user);| Type | Combination |
|---|---|
| PERMISSIVE (default) | Multiple policies combine with OR - any match grants access |
| RESTRICTIVE | Multiple policies combine with AND - all must allow |
Restrictive policies layer additional constraints on top of permissive ones. Per Postgres docs:
-- Permissive: anyone in role 'admin' or whose row matches
-- Restrictive: only allow when from local network
CREATE POLICY admin_local_only ON passwd
AS RESTRICTIVE TO admin
USING (pg_catalog.inet_client_addr() IS NULL);If omitted, the policy applies to all users (TO PUBLIC). For tenant
isolation, scope policies to the application role:
CREATE POLICY tenant_isolation ON documents
TO app_user
USING (tenant_id = current_setting('app.tenant_id')::uuid);CREATE POLICY syntax:
postgresql.org/docs/current/sql-createpolicy.html.