Row-Level Security
PostgreSQL-inspired fine-grained access control. Policies define which rows a user or role can see and modify, enforced transparently by the query engine.
Enable RLS
ENABLE ROW LEVEL SECURITY ON :Project -- Deny-by-default: without policies, no rows visible
Create Policies
Syntax: CREATE POLICY name ON target FOR op [TO role] USING (predicate). The TO role clause is optional - omitting it applies the policy to all users.
-- Users see only their own data
CREATE POLICY own_data ON :Project FOR SELECT TO reader
USING (n.owner = current_user())
-- Policy without a role (applies to everyone)
CREATE POLICY public_read ON :Post FOR SELECT
USING (n.published = true)
-- Multi-tenant isolation
CREATE POLICY tenant ON :Project FOR ALL TO authenticated
USING (n.tenant_id = session('tenant_id'))
-- Restrictive policy (AND logic)
CREATE POLICY no_secret ON :Document FOR SELECT TO reader
USING (n.classification != 'SECRET') AS RESTRICTIVESession Functions
| Function | Description |
|---|---|
| current_user() | The authenticated user's UUID |
| current_username() | The authenticated login username |
| session('key') | Custom session variable (e.g., tenant_id) |
Looking Beyond the Row
A predicate can also look into another collection or along a relationship:EXISTS DOCUMENT IN app_crm.organization_user_roles WHERE user_id = current_user() AND organization_id = d.organization_id AND role = 'admin'or EXISTS ((:User {id: current_user()})-[:ADMIN_OF]->(:Organization {id: n.organization_id})), where d.field / n.field is the row being judged. That is how “only an admin of this row’s organisation may edit it” becomes a policy with the membership kept elsewhere. Parenthesise a lookup to combine it with other conditions; a missing field or a malformed form denies; lookups are not themselves subject to RLS. See the Cypher reference for the full rules.
Write Enforcement
SET operations check the USING predicate per-node before applying changes. UPSERT DOCUMENT checks the USING predicate per-document. Write policies bypass the RBAC role check - any user whose data satisfies the predicate can write.
Behavior
Deny-by-default: when RLS is enabled, no matching policy means the operation is denied. Policies and the enabled/disabled state persist across server restarts (snapshot v5).
Sync Pairs
When a sync rule links a label to a collection, a single policy on the label automatically applies to the paired collection too.
Where Policies Apply
Collection policies run on the REST document API (get, query, scan, put, delete and batch) and on the Cypher document statements. A document the caller may not see is 404 over REST and simply absent from MATCH DOCUMENT and the graph-document join; a write a policy refuses is 403. Rows are filtered before limit, so a page is never short of rows the caller may see. Upserting an existing document must pass USING on the row as it is and WITH CHECK on its replacement; DELETE DOCUMENT (key and WHERE forms) removes only what the caller may see and delete; UPSERT DOCUMENT ... WHERE ... SET leaves rows its UPDATE policies do not allow untouched. Label SELECT policies filter every Cypher read of nodes: MATCH, traversals, counts, full-text search and OPTIONAL MATCH.
Who is asking is settled once per request. Server admins bypass policies, app admins bypass them inside their own app, and everyone else is judged by every role they hold. FORCE ROW LEVEL SECURITY overrides both bypasses. TO authenticated matches every signed-in principal and TO PUBLIC everyone.
Management
ALTER POLICY name ON :Label USING (predicate) [WITH CHECK (predicate)] -- the predicate you leave out is kept DROP POLICY name ON :Label SHOW POLICIES SHOW POLICIES ON :Label ENABLE / DISABLE / FORCE ROW LEVEL SECURITY ON :Label SIMULATE POLICY AS alice WITH ROLE reader ON :Project -- Collection policies: ON COLLECTION name wherever a label policy takes ON :Label ALTER POLICY things_update ON COLLECTION app_crm.things WITH CHECK (...) DROP POLICY things_update ON COLLECTION app_crm.things SHOW POLICIES ON COLLECTION app_crm.things