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 RESTRICTIVE

Session Functions

FunctionDescription
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