Semaphor
Security

Migration Guide

Migrating from legacy security to Unified Security

This guide walks you through moving a connection from legacy token-based security (CLS, RCLS, SLS) to Unified Security. The migration is per-connection, reversible, and can be staged while your dashboards continue running.


Before You Start

A few things to know before migrating:

  • Migration is per-connection. Different connections in the same project can use different security modes. You do not need to migrate everything at once.
  • Legacy and unified coexist. A project can have some connections on legacy and others on unified with no conflicts.
  • You can stage your work. Create policy definitions and assignments while a connection is still in legacy mode. They have no runtime effect until you switch.
  • Switching is instant. Changing a connection from legacy to unified (or back) takes effect immediately.
  • No data is lost. Switching back to legacy preserves all your unified policy definitions and assignments. They remain ready if you switch again.

Terminology Mapping

Unified Security renames and reorganizes several legacy concepts. Use this table as a reference.

Legacy TermUnified TermWhat Changed
CLS (Connection Level Security)CLSSame concept. Policies are now defined in policy definitions, not token fields.
RCLS (Row/Column Level Security)RLS (Row Level Security)Renamed. Column-hiding is handled separately through CLS or database grants.
SLS (Schema Level Security)SLSSame concept, now defined in policy definitions with schema, schemaTemplate, or allowedSchemas.
TLS (Table Level Security)(Deprecated)No unified equivalent. Use SLS combined with database-level grants instead.
Token cls fieldPolicy Definition + Assignment CLS configPolicies are admin-configured, not passed per token request.
Token rcls fieldPolicy Definition + Assignment RLS configRules defined centrally with {{ placeholder }} params.
Token sls fieldPolicy Definition + Assignment SLS configSchema routing defined in policy definitions with optional parameterization.

TLS removal

If you rely on Table Level Security (TLS), replace it with a combination of SLS boundaries and database-level grants before migrating. Unified Security does not include a TLS policy type.


Step-by-Step Migration

Step 1: Audit existing policies

Before creating anything in Unified Security, inventory what you have today.

For each connection, note:

  • Which legacy policy types are in use (CLS, RCLS, SLS, TLS)
  • The policy names referenced in your token generation code
  • The parameter values passed per tenant or user
  • Whether policies vary by tenant, by user, or are shared across all tenants

This inventory becomes your blueprint for policy definitions and assignments.

Step 2: Create policy definitions

For each connection, create one or more policy definitions that mirror your existing policies.

CLS -- Convert your connection policy name and its parameterized connection string (or file paths) into a connectionTemplate or filePathTemplates config with {{ placeholder }} syntax.

RLS (formerly RCLS) -- Convert each row-level policy into an RLS rule with a matcher and expression. Choose the matcher type that fits:

  • ALL_TABLES_WITH_COLUMN for predicates that apply wherever a column exists
  • TABLE_LIST for predicates that apply to specific tables
  • SCHEMA for predicates scoped to all tables in a schema

SLS -- Convert your schema parameter into an SLS config with schema, schemaTemplate, or allowedSchemas.

You can combine all three policy types in a single policy definition, or create separate policy definitions per policy type -- whichever matches your organizational needs.

Policy definitions are safe to create

Creating policy definitions on a legacy-mode connection has no runtime effect. Take your time getting the configuration right.

Step 3: Create assignments

For each actor (tenant, tenant user, or organization user), create assignments that bind a policy definition to that actor with concrete parameter values.

  • Use ALL_TENANTS scope for shared baseline rules (e.g., a common RLS predicate that all tenants inherit).
  • Use TENANT scope for tenant-specific values (e.g., database name, schema, tenant identifier).
  • Use TENANT_USER scope to narrow access for specific users within a tenant.
  • Use ORG_USER scope for internal users who need scoped access.

Step 4: Preview and validate

Use the Resolution Preview in the admin UI to simulate security for different actors before going live.

  1. Navigate to Project Settings > Security.
  2. Select the connection and an actor.
  3. Review the resolved CLS, SLS, and RLS context.

Compare the resolved policies against your current token-based behavior. Verify that:

  • All required parameters resolve without errors
  • The effective CLS connection string or file paths match what your legacy token produces
  • RLS predicates match the WHERE clauses your RCLS policies generate
  • SLS routes to the correct schema

Step 5: Switch the connection to unified mode

In the connection settings, change Security Mode from legacy to unified.

The change takes effect immediately for all queries on that connection. Legacy token fields (cls, rcls, sls) will be rejected for this connection from this point forward.

Legacy token fields are rejected

Once a connection is in unified mode, token requests that include cls, rcls, or sls fields targeting that connection will return an error. Update your token generation code before or immediately after switching.

Step 6: Update token generation code

Remove the legacy cls, rcls, and sls fields from your token requests. Ensure actor identity fields are present so Unified Security can resolve the correct assignments.

Required actor fields:

Actor TypeRequired Fields
TenanttenantId
Tenant usertenantId + endUserId
Organization userorgUserId

If some parameter values are not admin-bound in assignments and need to be supplied at runtime, pass them in the securityParams field.


Before and After

Here is a common use case -- a tenant with connection-level and row-level security -- showing the token request before and after migration.

Legacy token request

Token request (legacy)
const response = await fetch(TOKEN_URL, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    dashboardId: DASHBOARD_ID,
    dashboardSecret: DASHBOARD_SECRET,
    tenantId: 'tenant_acme',
    endUserId: 'user_jane',
    cls: [{                                   
      name: 'store_sales_primary',            
      params: { tenant: 'acme_prod' },        
    }],                                       
    rcls: {                                   
      name: 'state_filter',                   
      params: { state: ['CA', 'NV'] },        
    },                                        
  }),
});

Unified token request

Token request (unified)
const response = await fetch(TOKEN_URL, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    dashboardId: DASHBOARD_ID,
    dashboardSecret: DASHBOARD_SECRET,
    tenantId: 'tenant_acme',                  
    endUserId: 'user_jane',                   
    // CLS and RLS are resolved from policy definitions + assignments.
    // Only pass securityParams that aren't admin-bound:
    securityParams: {                         
      state: ['CA', 'NV'],                    
    },                                        
  }),
});

The CLS connection template and tenant-specific database name are now configured in a policy definition and bound to Acme via an assignment. The state filter is passed as a security param because it varies per request.

If state values are also admin-bound in the assignment, you can remove the securityParams field entirely -- the token request only needs actor identity.


Rollback

If something goes wrong after switching to unified mode:

  1. Open the connection settings.
  2. Change Security Mode back to legacy.
  3. The change takes effect immediately.

Your unified policy definitions and assignments are preserved -- they are not deleted when you switch back. Legacy token fields (cls, rcls, sls) will work again for this connection.

Use a feature flag during migration

Keep your legacy token generation code behind a feature flag while migrating. This lets you switch between legacy and unified token payloads without a code deploy.


Migration Checklist

Use this checklist to track your progress for each connection:

  • Inventory existing CLS, RCLS, SLS, and TLS policies
  • Replace any TLS usage with SLS or database grants
  • Create unified policy definitions matching your legacy policies
  • Create assignments for all actors that need security enforcement
  • Preview resolved security for representative actors
  • Compare resolved policies against legacy behavior
  • Switch connection to unified mode
  • Update token generation code to remove legacy fields
  • Verify dashboards render correctly with unified security
  • Remove legacy token code (or keep behind a feature flag)

Next Steps