Rls
Rls protocol schemas
Row-Level Security (RLS) Protocol
Implements fine-grained record-level access control inspired by PostgreSQL RLS
and Salesforce Criteria-Based Sharing Rules.
Overview
Row-Level Security (RLS) allows you to control which rows users can access
in database tables based on their identity and role. Unlike object-level
permissions (CRUD), RLS provides record-level filtering.
Use Cases
- Multi-Tenant Data Isolation
-
Users only see records from their organization
-
using: "tenant_id = current_user.tenant_id"
- Ownership-Based Access
-
Users only see records they own
-
using: "owner_id = current_user.id"
- Department-Based Access
-
Users only see records from their department
-
using: "department = current_user.department"
- Regional Access Control
-
Sales reps only see accounts in their territory
-
using: "region IN (current_user.assigned_regions)"
- Time-Based Access
-
Users can only access active records
-
using: "status = 'active' AND expiry_date > NOW()"
PostgreSQL RLS Comparison
PostgreSQL RLS Example:
CREATE POLICY tenant_isolation ON accounts
FOR SELECT
USING (tenant_id = current_setting('app.current_tenant_id')::uuid);
CREATE POLICY account_insert ON accounts
FOR INSERT
WITH CHECK (tenant_id = current_setting('app.current_tenant_id')::uuid);ObjectStack RLS Equivalent:
\{
name: 'tenant_isolation',
object: 'account',
operation: 'select',
using: 'tenant_id = current_user.tenant_id'
\}Salesforce Sharing Rules Comparison
Salesforce uses "Sharing Rules" and "Role Hierarchy" for record-level access.
ObjectStack RLS provides similar functionality with more flexibility.
Salesforce:
-
Criteria-Based Sharing: Share records matching criteria with users/roles
-
Owner-Based Sharing: Share records based on owner's role
-
Manual Sharing: Individual record sharing
ObjectStack RLS:
-
More flexible formula-based conditions
-
Direct SQL-like syntax
-
Supports complex logic with AND/OR/NOT
Best Practices
-
Always Define SELECT Policy: Control what users can view
-
Define INSERT/UPDATE CHECK Policies: Prevent data leakage
-
Use Role-Based Policies: Apply different rules to different roles
-
Test Thoroughly: RLS can have complex interactions
-
Monitor Performance: Complex RLS policies can impact query performance
Security Considerations
-
Defense in Depth: RLS is one layer; use with object permissions
-
Default Deny: If no policy matches, access is denied
-
Policy Precedence: More permissive policy wins (OR logic)
-
Context Variables: Ensure current_user context is always set
@see https://www.postgresql.org/docs/current/ddl-rowsecurity.html
@see https://help.salesforce.com/s/articleView?id=sf.security_sharing_rules.htm
Source: packages/spec/src/security/rls.zod.ts
TypeScript Usage
import { RLSAuditConfig, RLSAuditEvent, RLSConfig, RLSEvaluationResult, RLSOperation, RLSUserContext, RowLevelSecurityPolicy } from '@objectstack/spec/security';
import type { RLSAuditConfig, RLSAuditEvent, RLSConfig, RLSEvaluationResult, RLSOperation, RLSUserContext, RowLevelSecurityPolicy } from '@objectstack/spec/security';
// Validate data
const result = RLSAuditConfig.parse(data);RLSAuditConfig
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| enabled | boolean | ✅ | Enable RLS audit logging |
| logLevel | Enum<'all' | 'denied_only' | 'granted_only' | 'none'> | ✅ | Which evaluations to log |
| destination | Enum<'system_log' | 'audit_trail' | 'external'> | ✅ | Audit log destination |
| sampleRate | number | ✅ | Sampling rate (0-1) for high-traffic environments |
| retentionDays | integer | ✅ | Audit log retention period in days |
| includeRowData | boolean | ✅ | Include row data in audit logs (security-sensitive) |
| alertOnDenied | boolean | ✅ | Send alerts when access is denied |
RLSAuditEvent
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| timestamp | string | ✅ | ISO 8601 timestamp of the evaluation |
| userId | string | ✅ | User ID whose access was evaluated |
| operation | Enum<'select' | 'insert' | 'update' | 'delete'> | ✅ | Database operation being performed |
| object | string | ✅ | Target object name |
| policyName | string | ✅ | Name of the RLS policy evaluated |
| granted | boolean | ✅ | Whether access was granted |
| evaluationDurationMs | number | ✅ | Policy evaluation duration in milliseconds |
| matchedCondition | string | optional | Which USING/CHECK clause matched |
| rowCount | number | optional | Number of rows affected |
| metadata | Record<string, any> | optional | Additional audit event metadata |
RLSConfig
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| enabled | boolean | ✅ | Enable RLS enforcement globally |
| defaultPolicy | Enum<'deny' | 'allow'> | ✅ | Default action when no policies match |
| allowSuperuserBypass | boolean | ✅ | Allow superusers to bypass RLS |
| bypassRoles | string[] | optional | Roles that bypass RLS (see all data) |
| logEvaluations | boolean | ✅ | Log RLS policy evaluations for debugging |
| cacheResults | boolean | ✅ | Cache RLS evaluation results |
| cacheTtlSeconds | integer | ✅ | Cache TTL in seconds |
| prefetchUserContext | boolean | ✅ | Pre-fetch user context for performance |
| audit | Object | optional | RLS audit logging configuration |
RLSEvaluationResult
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| policyName | string | ✅ | Policy name |
| granted | boolean | ✅ | Whether access was granted |
| durationMs | number | optional | Evaluation duration in milliseconds |
| error | string | optional | Error message if evaluation failed |
| usingResult | boolean | optional | USING clause evaluation result |
| checkResult | boolean | optional | CHECK clause evaluation result |
RLSOperation
Allowed Values
selectinsertupdatedeleteall
RLSUserContext
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| id | string | ✅ | User ID |
string | optional | User email | |
| tenantId | string | optional | Tenant/Organization ID |
| role | string | string[] | optional | User role(s) |
| department | string | optional | User department |
| attributes | Record<string, any> | optional | Additional custom user attributes |
RowLevelSecurityPolicy
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| name | string | ✅ | Policy unique identifier (snake_case) |
| label | string | optional | Human-readable policy label |
| description | string | optional | Policy description and business justification |
| object | string | ✅ | Target object name |
| operation | Enum<'select' | 'insert' | 'update' | 'delete' | 'all'> | ✅ | Database operation this policy applies to |
| using | string | optional | Filter condition for SELECT/UPDATE/DELETE (PostgreSQL SQL WHERE clause syntax with parameterized context variables). Optional for INSERT-only policies. |
| check | string | optional | Validation condition for INSERT/UPDATE (defaults to USING clause if not specified - enforced at application level) |
| roles | string[] | optional | Roles this policy applies to (omit for all roles) |
| enabled | boolean | ✅ | Whether this policy is active |
| priority | integer | ✅ | Policy evaluation priority (higher = evaluated first) |
| tags | string[] | optional | Policy categorization tags |