CtrlK
BlogDocsLog inGet started
Tessl Logo

testland/rls-reference

Pure-reference catalog of row-level security for tenant isolation, Postgres-first. 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()), and performance discipline (wrapping auth functions in SELECT, index on policy-referenced columns). Row/tenant isolation on the non-Postgres engines - MySQL / MariaDB invoker views, CockroachDB native RLS, Vitess vindex sharding, SQL Server security policies - lives in references/other-engines.md. Use as the RLS-pattern reference for tenant isolation on any of these engines. Consumed by cross-tenant-data-leak-tests.

75

Quality

94%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files

sqlserver.mdreferences/

SQL Server - CREATE SECURITY POLICY with predicate functions

SQL Server has native RLS since SQL Server 2016 (13.x), also available on Azure SQL Database, Azure SQL Managed Instance, and Microsoft Fabric Warehouse.

Predicate types

PredicateEffect
FILTERSilently filters rows for SELECT, UPDATE, DELETE - the app sees an empty result, not an error
BLOCKExplicitly blocks writes (AFTER INSERT, AFTER UPDATE, BEFORE UPDATE, BEFORE DELETE) that violate the predicate

The predicate is an inline table-valued function created WITH SCHEMABINDING. SCHEMABINDING = ON (the default) means users querying the target table need no permission on the predicate function or its helper tables. The clauses material to isolation are ADD [FILTER | BLOCK] PREDICATE tvf(cols) ON table and WITH (STATE = ON); the CREATE SECURITY POLICY reference has the full grammar.

Tenant context via SESSION_CONTEXT

For shared connection pools, carry the tenant ID per connection with SESSION_CONTEXT():

CREATE FUNCTION Security.fn_tenant_predicate(@TenantId int)
    RETURNS TABLE
    WITH SCHEMABINDING
AS
    RETURN SELECT 1 AS result
    WHERE CAST(SESSION_CONTEXT(N'TenantId') AS int) = @TenantId;
GO

CREATE SECURITY POLICY Security.TenantFilter
    ADD FILTER PREDICATE Security.fn_tenant_predicate(TenantId) ON dbo.Documents,
    ADD BLOCK PREDICATE Security.fn_tenant_predicate(TenantId) ON dbo.Documents AFTER INSERT
    WITH (STATE = ON);
GO

EXEC sp_set_session_context @key = N'TenantId', @value = 42, @read_only = 1;

@read_only = 1 locks the SESSION_CONTEXT value until the connection returns to the pool - critical for pool reuse safety.

Alternative tenant context - USER_NAME()

For low-tenant-count deployments where each tenant maps to a SQL login:

CREATE FUNCTION Security.tvf_securitypredicate(@TenantRep AS nvarchar(50))
    RETURNS TABLE
    WITH SCHEMABINDING
AS
    RETURN SELECT 1 AS result
    WHERE @TenantRep = USER_NAME() OR USER_NAME() = 'GlobalAdmin';
GO

Bypass risks

RiskWhy
db_owner / sysadminPolicy applies but these roles can alter/drop it; audit all policy changes
DBCC SHOW_STATISTICSReports statistics on unfiltered data; restrict access to table owners
Change Data Capture (CDC)Leaks full rows to db_owner and the CDC gating role regardless of policy
Change TrackingLeaks primary keys of filtered rows to users with VIEW CHANGE TRACKING
Indexed viewsCannot be created on tables with a security policy
FILESTREAMIncompatible with RLS
Predicate relying on SET optionsSET DATEFORMAT/SET LANGUAGE can cause inconsistent filtering; use explicit CONVERT with a style parameter

Sources: SQL Server RLS learn.microsoft.com/en-us/sql/relational-databases/security/row-level-security; CREATE SECURITY POLICY learn.microsoft.com/en-us/sql/t-sql/statements/create-security-policy-transact-sql.

SKILL.md

tile.json