Skip to main content
Version: 1.0.0
Last Updated: 2026-01-11
Status: Active

Overview

This document defines the standard Row Level Security (RLS) patterns used across Encore OS. All business tables MUST have RLS enabled with appropriate policies.

Core Principles

1. SECURITY DEFINER Helper Functions

Never query RLS-protected tables directly in policies - this causes infinite recursion.

2. WITH CHECK on UPDATE Policies

Always include WITH CHECK clause to prevent tenant data movement.

3. Multi-Tenant Isolation

All policies MUST filter by organization_id (and site_id where applicable).

4. Defense in Depth

RLS is one layer - also include application-level checks in mutations.

Standard Patterns

Pattern A: Full CRUD (Business Entities)

For tables where users with org access can perform all operations.

Pattern B: Admin-Managed (Settings/Config)

For tables where only admins can write but all org users can read.

Pattern C: Audit/Log (Append-Only)

For tables that should never be modified after creation.
Tables using this pattern:
  • pf_audit_logs, pf_index_cleanup_audit
  • hr_document_access_logs, hr_payroll_audit_log, hr_ssn_access_log
  • fw_approval_history, fw_domain_events, fw_signature_audit_log
  • fm_asset_maintenance_history, fm_work_order_history

Pattern D: User-Owned (Personal Data)

For tables where users own their own records.
Tables using this pattern:
  • pf_profiles, pf_object_favorites, pf_notification_batches
  • hr_employee_preferences

Pattern E: Version/History (Immutable)

For tables storing historical versions - SELECT + INSERT only.
Tables using this pattern:
  • pf_document_versions, fw_form_versions, fw_workflow_versions
  • gr_policy_versions, lo_knowledge_article_versions

Pattern F: Service-Only (System Tables)

For tables only accessible via service_role key.
Tables using this pattern:
  • pf_integration_credentials

Helper Functions by Module

CE admin behavior: ce_is_org_admin is SECURITY DEFINER and returns true when the caller is an org admin or has any granted permission key matching ce.%.admin. Use this permission-based admin check for CE workflows that delegate admin responsibilities via module permissions. Use role-only *_is_org_admin checks when delegated module-admin permissions are not part of the access model. Note: Parameter order can vary by helper (for example pf_is_org_admin(user_id, org_id) vs most module helpers (...org_id, ...user_id)) - always check function signature.

Verification Queries

Check Tables Without RLS

Check Policy Coverage

Check for Recursion Risks

Check UPDATE Policies Without WITH CHECK


Migration Checklist

When adding RLS to a new table:
  • Enable RLS: ALTER TABLE {table} ENABLE ROW LEVEL SECURITY;
  • Add SELECT policy with org access check
  • Add INSERT policy with org access check
  • Add UPDATE policy with BOTH USING AND WITH CHECK
  • Add DELETE policy (usually admin-only)
  • Use SECURITY DEFINER helper functions (never direct queries)
  • Add RLS tests in tests/rls/

See Also