Row-Level Security
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. Enabling
RLS is a matter of setting AIHUMMER_DB_APP_URL — and local/standard
(host-native) installs set that variable up automatically, so RLS is active out
of the box. It is “opt-in” only in the sense that a custom or manual deployment
must set AIHUMMER_DB_APP_URL itself.
[!NOTE] Without
AIHUMMER_DB_APP_URL, the gateway uses the owner pool for everything and RLS is effectively not enforced. Set the restricted pool (which the standard installer does for you) to turn the database-level isolation on.
[!IMPORTANT] RLS is not tier-gated. It works identically on every plan, Community included. If the license screen shows “Row-level security” as unavailable, that is an inaccuracy of the screen, not the state of your database.
Where RLS is on already and where you must turn it on
| How you installed | RLS after the install |
|---|---|
| Standard install, the installer provisions PostgreSQL | Active. The restricted role and AIHUMMER_DB_APP_URL are created for you |
| Your own or managed PostgreSQL (the database URL was supplied) | Not active. You create the role and set AIHUMMER_DB_APP_URL |
| Rootless install (no administrator rights) | Not active. Same as above |
[!WARNING] Two mistakes that leave RLS “on” but not protecting anything. First:
AIHUMMER_DB_APP_URLpoints at the table owner or a superuser — PostgreSQL exempts such roles from policies, and the gateway will still report RLS as active. Use a separate restricted role. Second: on your own PostgreSQL the restricted role may be created automatically with a predictable password — give it a password of your own before the database is reachable over the network.
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, delivery recovery and similar maintenance. 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
- Multitenancy & idempotency — the full tenant model and how side-effects stay safe under recovery.
- Secrets vault — per-tenant DEKs reinforce the same isolation at the secrets layer.
- RBAC & scoped API keys — authorization above the data layer.