Policy Types
CLS, SLS, and RLS security policies in Unified Security
Unified Security provides three policy types that control data access at different levels of your infrastructure. Each targets a specific isolation boundary.
| Policy | Full Name | Controls |
|---|---|---|
| CLS | Connection Level Security | Which database or file path an actor connects to |
| SLS | Schema Level Security | Which schema an actor queries within a database |
| RLS | Row Level Security | Which rows an actor sees within a table |
You define these policies inside a policy definition and assign them to actors (tenants, tenant users, or org users).
CLS -- Connection Level Security
CLS dynamically parameterizes database connection strings or S3 file paths per actor. Use it when tenants have separate databases or separate file storage locations.
Two Modes
CLS operates in one of two modes -- never both at once.
Database mode uses connectionTemplate to parameterize the connection string:
File mode uses filePathTemplates to map table names to parameterized S3 paths:
Secret Parameters
Placeholders that end with @secret are treated as sensitive values. Semaphor stores them securely server-side and never exposes them to clients or in query logs.
When to Use CLS
- Separate databases per tenant
- Separate S3 folders per tenant
- Connection credentials that vary per actor
SLS -- Schema Level Security
SLS routes actors to specific database schemas within a shared database. Use it when tenants share a database instance but have isolated schemas.
Config Options
Provide at least one of the following fields:
| Field | Type | Purpose |
|---|---|---|
schema | string | Fixed schema name |
schemaTemplate | string | Parameterized schema with {{ placeholder }} syntax |
allowedSchemas | string[] | Explicit allowlist of permitted schemas |
defaultSchema | string | Fallback when no narrower selection applies |
Fixed schema -- routes an actor to a specific schema:
Parameterized schema -- resolves at runtime from assignment params:
Schema allowlist -- defines a boundary that lower-level assignments can select within:
Boundary Enforcement
Lower-level assignments and token overrides can only select schemas within the inherited boundary. An ALL_TENANTS assignment might set allowedSchemas to ["us_east", "us_west"]. A TENANT assignment can then narrow to "schema": "us_east", but cannot select "eu_central" because it falls outside the boundary.
When to Use SLS
- Shared database with per-tenant schemas
- Regional schema isolation within a single connection
- Schema-based access tiers (e.g.,
analyticsvs.raw_data)
RLS -- Row Level Security
RLS applies WHERE clause predicates to filter rows per actor. Each RLS config contains one or more rules, and each rule specifies which tables it applies to and what filter expression to inject.
Rule Structure
Matcher Types
Each rule uses a matcher to determine which tables the filter applies to.
ALL_TABLES_WITH_COLUMN -- applies the rule to any queried table that contains the specified column. This is the most common matcher for tenant isolation.
TABLE_LIST -- applies the rule to an explicit set of tables. Use schema and database qualifiers when table names are ambiguous.
SCHEMA -- applies the rule to all tables within a specific schema. Optionally filter to only tables that contain a given column.
Expression Syntax
Expressions are SQL predicates with {{ placeholder }} parameters. Semaphor injects them as WHERE clauses at query time.
| Expression | Param Value | Generated SQL |
|---|---|---|
tenant_id = {{ tenant_id }} | "acme" | tenant_id = 'acme' |
region IN {{ allowed_regions }} | ["us-east-1", "us-west-2"] | region IN ('us-east-1', 'us-west-2') |
department = {{ dept }} | "engineering" | department = 'engineering' |
Empty arrays fail closed
If an array parameter resolves to an empty list [], the rule generates 1=0 -- no rows are returned. This prevents accidental data exposure.
Combination Semantics
When multiple RLS rules apply to the same query, their predicates combine with AND (intersection). Lower-level assignments can add rules but cannot remove rules inherited from higher levels.
Combining Policy Types
The three policy types work together. Use the combination that matches your data architecture.
CLS + RLS
Separate databases per tenant with additional row-level filtering within each database.
SLS + RLS
Schema isolation combined with fine-grained row filtering.
CLS + SLS + RLS
Maximum isolation with all three layers -- separate connections, schema routing, and row-level predicates.
Policy Resolution Order
When multiple assignments exist for an actor, Semaphor resolves policies from broadest scope to narrowest. Each layer can only narrow access -- never widen it.
Resolution by Policy Type
| Policy | Resolution Strategy |
|---|---|
| CLS | Params merge (overlay). Lower layers supply param values but cannot change the connection template. |
| SLS | Lower layers select within the inherited boundary. Cannot reference schemas outside allowedSchemas. |
| RLS | Rules accumulate with AND (intersection). Lower layers add rules but cannot remove inherited ones. |
Persisted assignments (saved in the admin UI or via API) are the source of truth. Token-time parameters can supply missing param values or narrow further, but cannot override structural boundaries like connection templates, schema allowlists, or existing RLS rules.
Fail-closed design
If a required parameter is missing at resolution time, the query fails rather than executing with incomplete security context.
Next Steps
- Policy Definitions & Assignments -- create policy definitions and assign them to actors
- Token Integration -- pass runtime parameters at token generation time