PostgreSQL Row-Level Security: English Vocabulary for Database Security Discussions
Learn the English vocabulary database engineers use when discussing PostgreSQL Row-Level Security, security policies, roles, and multi-tenant data isolation.
Row-Level Security is one of PostgreSQL’s most powerful features for multi-tenant applications, and discussions about it require specific vocabulary. Whether you are designing a multi-tenant schema, reviewing an RLS policy in a pull request, or explaining the security model to a new team member, this guide provides the precise English terms and phrases you need.
Core Vocabulary
Row-Level Security (RLS) A PostgreSQL feature that restricts which rows a database user can see or modify, based on configurable policies. RLS is enforced by the database itself, independent of application logic.
“We enabled row-level security on the accounts table — even if there’s a bug in the application layer, users can never access another tenant’s data at the database level.”
Policy A named rule attached to a table that defines which rows are accessible for a given SQL command and database role. Policies are the building blocks of RLS configuration.
“We have two policies on the orders table: a SELECT policy that filters by tenant ID, and a separate INSERT policy that also enforces tenant ID on writes.”
USING clause The part of a policy definition that filters rows for read operations — SELECT, UPDATE, and DELETE. A row is only visible or modifiable if it passes the USING condition.
“The USING clause references current_setting(‘app.current_tenant_id’), which we set at the start of each database session from the application layer.”
WITH CHECK clause The part of a policy definition that applies to data being written — INSERT and UPDATE. It validates that the data being added or changed conforms to the policy, not just that existing rows are visible.
“The WITH CHECK clause prevents a user from inserting a row with a different tenant_id than their own — even if they craft a direct INSERT statement.”
Role A PostgreSQL identity that policies target. In multi-tenant applications, a common pattern is using a dedicated application role with RLS policies, while admin roles use BYPASSRLS.
“We run all application database connections as the app_user role, which has RLS policies attached. The migration role uses BYPASSRLS so it can perform schema changes across all tenants.”
BYPASSRLS A PostgreSQL role attribute that causes the database to skip all row-level security checks for that role. Only superusers and roles explicitly granted BYPASSRLS can read unrestricted data.
“Never grant BYPASSRLS to the application role — that would silently disable all our tenant isolation. Only the migration and backup roles should have it.”
Permissive policy The default policy type, combined with OR logic. A row is accessible if any permissive policy allows it. Multiple permissive policies broaden the set of accessible rows.
“We added a permissive policy that also allows customer support agents to see tickets from any tenant — their role is in a special group that matches the additional permissive policy.”
Restrictive policy A policy type combined with AND logic. Every restrictive policy must pass for a row to be accessible, regardless of what permissive policies allow. Restrictive policies narrow access.
“We added a restrictive policy to block access to archived tenants — even if a permissive policy would normally allow access, the restrictive policy prevents it for archived tenant IDs.”
current_user A PostgreSQL function that returns the name of the currently active database role. It is commonly used inside RLS policy expressions to match rows to the authenticated user.
“Our per-user access table stores rows keyed by username — the RLS policy uses current_user to look up which resources the logged-in role is allowed to see.”
Key Collocations
- enable row-level security — “Run ALTER TABLE orders ENABLE ROW LEVEL SECURITY to activate RLS — policies already defined on the table will start being enforced.”
- create a policy — “We need to create a policy for each combination of command and role — SELECT, INSERT, UPDATE, and DELETE may need separate policies.”
- grant bypass RLS — “Only grant bypass RLS to roles that absolutely require it — document the reason and review it quarterly.”
- attach a policy to a table — “Policies are attached to a specific table — if you add a new tenant-scoped table, don’t forget to attach the appropriate policies.”
- filter by tenant ID — “The USING clause filters by tenant ID by comparing the tenant_id column to the current_setting value we inject at session start.”
- test policies as a non-superuser — “Always test policies as a non-superuser — superusers bypass RLS by default, so tests run as postgres will never catch policy gaps.”
Using This Vocabulary in Code Reviews
When reviewing RLS configurations, the most common questions take the form: “Is there a policy for writes, or only for reads?” The distinction between USING (reads) and WITH CHECK (writes) is a frequent gap in RLS implementations. If a table only has a USING clause, a malicious or buggy INSERT can still add rows with the wrong tenant ID.
The phrase “force row-level security” refers to ALTER TABLE … FORCE ROW LEVEL SECURITY, which makes RLS apply even to the table owner. This is important to mention in security reviews: “Did you force RLS on the table? Otherwise the schema owner role bypasses all policies.”
When discussing multi-tenant isolation guarantees, the phrase “at the database level” is important. It signals that the isolation is enforced by PostgreSQL itself, not by application code that could have bugs. This is often a selling point in security discussions: “Our tenant isolation is enforced at the database level, so application bugs can’t leak cross-tenant data.”
Practice Tip
Write an RLS policy in plain English before writing the SQL. For example: “Users should only see rows where the tenant_id column matches the app.current_tenant_id session variable. On writes, the tenant_id must also match.” Then translate your English description into a CREATE POLICY statement. This back-and-forth between natural language and SQL builds both your PostgreSQL skills and your ability to explain security policies to non-technical stakeholders.