Semaphor
Security

Policy Definitions & Assignments

Creating and managing security policies with policy definitions and assignments

Policy definitions and assignments are the two building blocks for configuring Unified Security. A policy definition is a reusable policy template attached to a connection. An assignment binds that policy definition to an actor and fills in the concrete parameter values.

Policy Definition (template)  +  Assignment (actor + params)  =  Enforced policy

Policy Definitions

A policy definition is a connection-scoped template. It describes what security rules to apply, but not who they apply to.

Each policy definition can include any combination of:

  • CLS config -- connection string or file path template with placeholders
  • SLS config -- schema routing rules
  • RLS config -- row-level filter rules with expressions

At least one of these must be present. A policy definition without any config is invalid.

Placeholders

Policy definitions use {{ placeholder }} syntax for values that differ per actor. Placeholders are resolved at runtime from assignment params or token securityParams.

Policy definition with placeholders
{
  "name": "Tenant Data Access",
  "clsConfig": {
    "connectionTemplate": "postgresql+psycopg2://app:{{ password@secret }}@db.host:5432/{{ tenantDatabase }}"
  },
  "rlsConfig": {
    "rules": [
      {
        "name": "tenant_isolation",
        "matcher": { "type": "ALL_TABLES_WITH_COLUMN", "column": "tenant_id" },
        "expression": "tenant_id = {{ tenant_id }}"
      }
    ]
  }
}

This policy definition has three placeholders: password@secret, tenantDatabase, and tenant_id. Their values come from assignments.

Key Properties

PropertyDescription
nameUnique name within the connection (e.g., "Tenant Data Access")
connectionIdThe connection this policy definition applies to
clsConfigConnection template or file path templates with params
slsConfigSchema routing config
rlsConfigRow-level security rules

Policy definitions are inert

A policy definition has no effect on queries until it is assigned to an actor. You can create and edit policy definitions on a connection without impacting any running dashboards.

One Policy Definition, Many Assignments

A single policy definition can be assigned to multiple actors. For example, a "Tenant Data Access" policy definition might be assigned to every tenant in your project, each with different parameter values for tenantDatabase and tenant_id.


Assignments

An assignment binds a policy definition to a specific actor scope and provides the concrete parameter values for that actor.

Assignment example
{
  "definitionId": "def_tenant_data_access",
  "scopeType": "TENANT",
  "tenantId": "tenant_acme",
  "params": {
    "tenantDatabase": "acme_prod",
    "tenant_id": "acme",
    "password": "s3cur3-p4ss"
  }
}

This assignment says: "Apply the Tenant Data Access policy definition to the Acme tenant, using database acme_prod, filtering rows where tenant_id = 'acme', and connecting with the given password."

Uniqueness

Each (policy definition, scope type, actor) combination allows at most one assignment. You cannot assign the same policy definition to the same tenant twice. To update parameter values, edit the existing assignment.


Scope Types

The scope type determines which actor the assignment targets.

ScopeRequired FieldsDescriptionUse Case
ALL_TENANTS(none)Shared baseline for every tenantDefault RLS rules that all tenants inherit
TENANTtenantIdTargets a specific tenantTenant-specific database, schema, or params
TENANT_USERtenantUserIdTargets an end user within a tenantNarrow access for specific users within a tenant
ORG_USERorgUserIdTargets an organization userInternal employee with scoped data access

Choosing the Right Scope

  • Use ALL_TENANTS for rules that every tenant should share, like a common RLS predicate.
  • Use TENANT when tenants have different databases, schemas, or parameter values.
  • Use TENANT_USER when specific users within a tenant need additional restrictions.
  • Use ORG_USER for internal users who need access to customer data with specific constraints.

Inheritance and Merging

When a tenant user accesses data, Semaphor collects assignments from multiple scope levels and merges them in priority order.

Merge Order

For tenant-scoped actors (TENANT and TENANT_USER), assignments merge from broadest to narrowest:

1. ALL_TENANTS  (baseline)
2. TENANT       (narrow the baseline)
3. TENANT_USER  (narrow within tenant)

Each layer can only restrict access -- never widen it. If the ALL_TENANTS layer sets an RLS rule, lower layers cannot remove it.

Merge Rules by Policy Type

PolicyMerge Behavior
CLSLower layers overlay parameter values onto the inherited connection or file path structure. The template itself (connection string shape) is inherited, not replaced.
SLSLower layers select a schema within the inherited boundary. If an allowlist is set at a higher layer, lower layers can only pick schemas from that list.
RLSRules from all layers combine with AND (intersection). Lower layers can add new rules but cannot remove inherited ones.

Example: Three-Layer Merge

ALL_TENANTS policy definition
RLS: tenant_id = {{ tenant_id }}

    ↓ assigns tenant_id = "acme"

TENANT assignment for Acme Corp
SLS: schema = "acme_data"

    ↓ adds user-level restriction

TENANT_USER assignment for jane@acme.com
RLS: department = {{ department }}, department = "sales"

The effective policy for jane@acme.com:

  • SLS routes to schema acme_data
  • RLS applies tenant_id = 'acme' AND department = 'sales'

Organization Users

ORG_USER assignments are independent -- they do not participate in the tenant inheritance chain. Each org user assignment stands alone.


Parameter Sources

Parameters that fill policy definition placeholders come from three sources, applied in merge order:

1. Policy definition defaults

Parameters embedded in the policy definition config itself. These serve as defaults when no assignment or runtime param overrides them.

Policy definition with default params
{
  "rlsConfig": {
    "rules": [
      {
        "expression": "status = {{ status }}",
        "params": { "status": "active" }
      }
    ]
  }
}

2. Assignment-bound params

Concrete values stored in the assignment. These are the authoritative boundary for the actor.

Assignment params
{
  "params": {
    "tenant_id": "acme",
    "tenantDatabase": "acme_prod"
  }
}

3. Runtime / token securityParams

Supplied at token generation time via the securityParams field. These can fill unresolved placeholders or narrow within the existing boundary, but cannot override persisted assignment values for RLS-referenced parameters.

Token generation with securityParams
const token = await generateToken({
  dashboardId: 'd_xxx',
  dashboardSecret: 'secret',
  securityParams: {
    department: 'sales'  // fills an unresolved placeholder
  }
});

securityParams cannot widen access

Persisted assignment params are the authoritative security boundary. Runtime securityParams supplied at token time can fill missing placeholders or narrow further, but they cannot override values that are already bound by persisted assignments. Attempting to override a persisted RLS parameter will cause a resolution error.


Unassigned Actors

If an actor has no matching assignments on a unified-mode connection, no Unified Security policy is applied. The actor uses the base connection without restrictions.

This is intentional: assignments are policy attachments, not access grants. An admin explicitly opts actors into policies by creating assignments. The absence of an assignment means "no Unified Security enforcement" -- the connection behaves as if it were in legacy mode for that actor.

Audit your assignments

Before switching a connection to unified mode, verify that all actors who need security restrictions have assignments. Use the Resolution Preview to check any actor's effective policy.


Admin Workflow

Managing Policy Definitions

  1. Navigate to Project Settings > Security.
  2. Open the Policy Definitions tab.
  3. Click Create Policy Definition and select a connection.
  4. Configure the CLS, SLS, and/or RLS settings with placeholder syntax.
  5. Save the policy definition.

Each policy definition targets a specific connection. You can create multiple policy definitions per connection for different policy patterns.

Managing Assignments

  1. Open the Assignments tab.
  2. Click Create Assignment.
  3. Select a policy definition, choose a scope type, and pick the target actor.
  4. Fill in the parameter values for the policy definition's placeholders.
  5. Save the assignment.

Resolution Preview

The Preview panel lets you test the effective security for any actor before going live. Select an actor and connection to see the resolved CLS, SLS, and RLS context -- exactly what will be enforced at query time.

Use this to verify:

  • All required parameters resolve correctly
  • The inheritance chain produces the expected result
  • No actors are missing assignments they need

Next Steps

  • Policy Types -- CLS, SLS, and RLS configuration details, matcher types, and combination semantics
  • Token Integration -- supply runtime parameters and policy overlays through embed tokens
  • Unified Security API -- programmatic management of policy definitions and assignments