CockroachDB 25.2 adds row policies with explicit enablement and bypass rules
Row-level security moves row filtering and write checks into the database. Its useful guarantees depend on policy composition, the executing role and which operations actually apply the policies.
A shared table gives an application one place to store many users' records, but every query must preserve the intended access boundary. A forgotten predicate can turn a valid table-level grant into access to another user's rows. CockroachDB 25.2 adds row-level security so the database can apply that boundary when processing supported reads and writes.
The May 12 release notes introduce row-level security as database-enforced policies over individual rows. This is a substantial addition for shared-table designs. It also requires a precise account of activation, role identity and bypass behavior before the phrase tenant isolation becomes meaningful.
I favor CockroachDB's serializable default because the database accepts responsibility for a consistency property that applications otherwise have to coordinate themselves. Row policies appeal to me for a related reason. Repeated access predicates become a database rule, although the rule remains dependent on the identity under which a query runs.
Table grants and row policies answer separate questions
The RLS overview says ordinary SQL privileges still determine whether a role may access the table at all. Policies then restrict which rows that role can read or modify. A policy does not replace the grant that allows the operation.
When RLS is enabled and no policy applies to the role and statement, the documented behavior is default denial. This makes policy absence significant. Enabling the feature is not the same as granting every existing table reader a row subset automatically.
The resulting model has two questions for each operation. The role needs permission to perform that kind of operation on the table, and the applicable policy needs to admit the relevant rows. Treating one answer as a substitute for the other can produce either unexpected denial or a mistaken claim about what protects the data.
Reading an existing row differs from validating a new row
The CREATE POLICY reference separates USING from WITH CHECK. USING decides which existing rows are visible or available for modification. WITH CHECK validates a row being inserted or updated. A false or null visibility result excludes a row, while a false or null write check rejects the operation.
That distinction is essential for an update that changes an ownership column. A role may be allowed to locate a row under its old owner value, while the new row would assign it to another owner. A read filter alone does not express the complete intended rule for that transition.
The documented policy structure can make both sides explicit. This illustrative schema uses the current database role as the row owner:
CREATE TABLE user_records (
id INT PRIMARY KEY,
owner_name TEXT NOT NULL,
body TEXT
);
ALTER TABLE user_records ENABLE ROW LEVEL SECURITY;
CREATE POLICY own_records ON user_records
FOR ALL TO PUBLIC
USING (owner_name = CURRENT_USER)
WITH CHECK (owner_name = CURRENT_USER);
The example deliberately binds the row to the database execution identity. A nonexempt role still needs the appropriate table grants. If an application connects every request as the same role, this expression does not invent a separate end-user identity for each request. Its security meaning depends on who can choose that identity. A client allowed to supply arbitrary ownership context cannot be assumed to represent the tenant named by that context. The policy must consume identity information whose provenance matches the application's trust model, or it merely checks a claim the requester can change.
Activation and ownership need separate treatment
The RLS enablement documentation says policies apply only after explicit ENABLE ROW LEVEL SECURITY. It lists administrators, table owners and roles with BYPASSRLS as exemptions, with an owner exception when force mode is enabled.
The ALTER TABLE reference supplies FORCE ROW LEVEL SECURITY to subject the owner to policies. It also warns that a role carrying BYPASSRLS can bypass policies even with force mode enabled. Force mode therefore changes the owner's normal exemption without establishing that every privileged identity is restricted.
This matters for verification because a table owner is often the identity that creates a demonstration schema. A successful unrestricted read under that identity is not evidence that ordinary roles receive unrestricted access. Conversely, a test under an exempt account cannot establish that a restrictive policy works for the application role.
An access review needs the executing identity as part of its evidence. The schema and policy text describe what rules exist, while role attributes determine whether that operation is subject to them. Omitting the latter leaves the meaning of the observed result ambiguous.
Permissive policies combine differently from restrictive policies
The policy reference defines permissive policies using OR and restrictive policies using AND. At least one permissive policy must allow access, and applicable restrictive policies then constrain that access. Adding a restrictive policy alone does not establish a new route into the table.
This affects how exceptions should be represented. A second permissive policy can broaden the admitted set because either policy may suffice. A restrictive policy can narrow access for an applicable role without adding another permission path. Both can be useful, but their effect depends on the rest of the applicable policy set.
The practical consequence follows from that composition. Reviewing a policy in isolation can miss an access path granted by another permissive policy. The relevant object is the complete set applicable to the role and statement, including whether a policy targets all roles through PUBLIC.
I find explicit composition more useful than describing a policy as restrictive in ordinary English. A clause that looks narrow can still participate in an OR with a broader rule. The database's declared policy type determines how those expressions combine.
Policy maintenance has a defined command scope
The ALTER POLICY reference permits changing a policy's name, targeted roles and expressions. It does not permit changing whether the policy is permissive or restrictive, or changing its command category. Those changes require dropping the policy and creating a replacement.
This is useful information for schema tooling. A proposed alteration has to identify whether it modifies an expression or changes the policy's kind. Assuming every property is alterable can produce a failed migration even when the replacement rule is valid.
The DROP POLICY reference removes the named policy from its table. Combined with the documented default-deny behavior, removal of the last applicable permitting policy has a different meaning from disabling RLS on the table. One leaves enforcement active without that permission path, while the other stops applying row policies.
Those operations should consequently carry distinct intent in a migration. Removing a policy does not mean that the table should become generally visible. A maintenance procedure needs to preserve the desired enforcement state while changing the rule set.
Inspection can expose the complete rule set
The SHOW POLICIES reference lists policies for a selected table, including command, type, roles and expressions. It also documents pg_policies for examining policies across the system. These provide concrete metadata for evaluating composition.
For the illustrative table, the inspection command is small. It reports the declared policies, without substituting for an access attempt under a nonexempt role:
SHOW POLICIES FOR user_records;
The distinction is useful during review. Metadata can establish that the intended expressions and targets exist. A query executed as a specific role establishes how an operation behaves under that identity. Both kinds of evidence are needed to connect the declared rule to the access boundary an application expects.
The ALTER ROLE reference separately documents BYPASSRLS and NOBYPASSRLS. That capability belongs in the identity model alongside table privileges. A role exempt from row filtering has authority beyond what a policy inspection alone will show.
Row security is not an export filter for every subsystem
The RLS overview explicitly says changefeed messages are not filtered by row policies. It also states that backup and restore do not take those policies into account. These paths have their own purpose and authority, so a filtered interactive query does not prove that an exported or replicated dataset contains only the same rows.
This defines a practical boundary for the new capability. Shared-table application queries can use database-enforced row rules, while administrative and data-movement paths still need an authorization design appropriate to their behavior. Version 25.2 provides the policy mechanism. The executing roles and the operations allowed to move data determine where that mechanism applies.