AiHummer docs
v1.0.x
RU EN

Row-Level Security

v1.0.x · updated 2026-06-26

AiHummer is multitenant, and its strongest isolation boundary lives in the database itself: PostgreSQL Row-Level Security (RLS). With RLS enabled, the database — not just application code — enforces that a query only ever sees the rows belonging to the current tenant.

Why RLS

Application-level filtering (WHERE tenant_id = ...) is necessary but fragile: a single forgotten clause can leak data across tenants. RLS moves the guarantee into PostgreSQL, so that even an unfiltered query returns only the current tenant’s rows. It is a defense-in-depth layer beneath the application’s own scoping. For the broader multitenancy model — and how it pairs with idempotent side-effects — see Multitenancy & idempotency.

The restricted role (opt-in)

RLS is opt-in and is activated by giving the gateway a second database connection that uses a restricted role rather than the owner:

# /home/.aihummer/etc/gateway.env
# Owner pool — runs migrations, used for system/bypass operations
AIHUMMER_DATABASE_URL=postgres://owner:...@localhost/aihummer

# Restricted application pool — RLS policies apply (aihummer_app role)
AIHUMMER_DB_APP_URL=postgres://aihummer_app:...@localhost/aihummer

The aihummer_app role is not the table owner, so PostgreSQL applies RLS policies to it. Application queries flow through this restricted pool. For host-native installs the installer wires AIHUMMER_DB_APP_URL automatically.

[!NOTE] Without AIHUMMER_DB_APP_URL, the gateway uses the owner pool for everything and RLS is effectively not enforced. Set the restricted pool to turn the database-level isolation on.

Per-tenant scoping

Inside a request, the application establishes the current tenant on the connection before running tenant-scoped queries — conceptually db.WithTenant. Once scoped, RLS policies on the restricted role limit every read and write to that tenant’s rows. The scope is tied to the unit of work, so it does not leak between concurrent requests.

request ─▶ resolve tenant ─▶ db.WithTenant(tenant) ─▶ queries see only that tenant

System / bypass mode for workers

Some work is legitimately cross-tenant or tenant-agnostic — background workers, schedulers, the outbox, and similar machinery. For these, the gateway uses a system (bypass) mode that runs on the owner pool, outside the per-tenant RLS policies, so infrastructure tasks can operate across the dataset.

[!WARNING] Bypass mode is for trusted internal workers only. Request-handling code paths that act on behalf of a user must always run through the restricted, tenant-scoped pool — never the bypass path.

Migrations run on the owner pool

Schema changes require privileges the restricted role does not have, so migrations always run on the owner pool (AIHUMMER_DATABASE_URL), under an advisory lock, at startup. The restricted aihummer_app role is used only for ordinary application traffic. This keeps the privilege separation clean: schema-changing operations use the owner; tenant data access uses the restricted role with RLS applied.

Where to next