Skip to main content
Iru Identity is multi-tenant: many organizations share the same service. The most important property of that design is that one tenant can never see or touch another’s data. Iru enforces this in the database engine itself, not just in application code - so even a bug in a query cannot cross the boundary.

Isolation enforced by the database

Every table that holds tenant data carries a tenant_id and is protected by PostgreSQL row-level security (RLS):
  • The table has RLS enabled and forced (FORCE ROW LEVEL SECURITY), so the policy applies to every role - there is no privileged path around it.
  • A single policy governs both reads and writes: rows are visible (USING) and writable (WITH CHECK) only when their tenant_id matches the tenant of the current request.
-- The shape every tenant table shares
ALTER TABLE app.<table> ENABLE ROW LEVEL SECURITY;
ALTER TABLE app.<table> FORCE  ROW LEVEL SECURITY;

CREATE POLICY tenant_access_policy ON app.<table> FOR ALL
  USING      (tenant_id = current_setting('iru.tenant_id'))
  WITH CHECK (tenant_id = current_setting('iru.tenant_id'));
This is applied uniformly and at scale - hundreds of tables, each with forced RLS and an identical read-and-write policy - not just to a privileged few.
Because the boundary lives in the database, a query that forgets to filter by tenant simply returns nothing from another tenant - the engine refuses to show or change rows outside the active tenant.

The tenant context travels with every request

  1. The tenant is resolved from the request’s subdomain in middleware. If it can’t be resolved, the request is rejected before it ever reaches a handler.
  2. For each database transaction, Iru sets the tenant - along with the acting user and a correlation/trace id - as a transaction-scoped setting (SET LOCAL). Because it’s scoped to the transaction, it cannot leak to the next request that reuses a pooled connection.
  3. The RLS policy reads that exact setting, so the security boundary is the same value the application set, established transactionally on every connection.

Least privilege, so audit history can’t be rewritten

The database role the application runs as can read and write live data, but it holds only read access to the audit schema. Even if application SQL were somehow abused, it has no grant to modify or delete audit history - the trail is protected at the privilege layer, independent of any application logic. Referential integrity is tenant-scoped too: foreign keys are composite (tenant_id + id), so a record can only ever reference another record in the same tenant.

Every change is journaled - append-only

Iru keeps a complete, tamper-evident history of changes to tenant data.
Every mutable table has a mirror audit (journal) table written by a database trigger on insert, update, and delete. Each entry records what happened (the action), who (both the acting database principal and the authenticated user), when, a correlation/trace id tying it to the originating request, and the full before/after state of the row.
The trail is protected three independent ways: the trigger only ever inserts journal rows; the application’s database role has read-only access to the audit schema; and the trigger runs with definer rights. There is no path for the application to alter or erase an audit record.
A standing consistency check compares every live table’s columns against its audit table and fails if any column is missing - so new fields can’t quietly escape the audit trail. Schema migrations are themselves versioned, checksum-verified, and journaled.

Sensitive fields get an extra layer

On top of isolation, particularly sensitive values are encrypted at the field level with AES-256-GCM (authenticated encryption) before they’re stored, and decrypted only when read by the application. Encryption keys are supplied at runtime from a managed secret store, never from code or the database.

How this fits the bigger picture

Isolation works hand in hand with regional data residency: each region has its own database, and a tenant’s queries only ever run against its region’s database (see Platform architecture). Within that database, RLS keeps each tenant separate.

Platform architecture

The regions, planes, and infrastructure this isolation runs on.

Authentication architecture

How sign-in and API requests are made phishing-resistant and tamper-proof.

Activity log

The audit trail, surfaced for admins to review.

Security & privacy

The guarantees these mechanisms add up to.