Row-Level Security
All tenant isolation in KVM Fleet is enforced at the database level using Postgres Row-Level Security (RLS).
Design
The API connects to Postgres as the kvmfleet_app role — a non-superuser with FORCE ROW LEVEL SECURITY enabled. This means RLS policies apply even if the role owns the tables.
ALTER ROLE kvmfleet_app SET row_security = on;
ALTER TABLE devices FORCE ROW LEVEL SECURITY;
ALTER TABLE audit_log FORCE ROW LEVEL SECURITY;
-- ... (all tenant-scoped tables)
How it works
Before each request, the API sets a session variable with the authenticated user's org ID:
RLS policies filter all queries automatically:
CREATE POLICY org_isolation ON devices
USING (org_id = current_setting('app.current_org_id')::uuid);
CREATE POLICY org_isolation ON audit_log
USING (org_id = current_setting('app.current_org_id')::uuid);
What this means
- A bug in the API code cannot leak data across organizations
- Even raw SQL injection would only return data for the attacker's own org
- No application-level
WHERE org_id = ?filters needed (but they're there anyway as defense-in-depth)
Tables with RLS
All tenant-scoped tables have RLS enabled:
devicesaudit_logusers(scoped to org membership)alert_rulesalert_historyconsole_sessionsinvitesreports
Audit log protection
The audit_log table has an additional restriction: the kvmfleet_app role only has INSERT and SELECT — no UPDATE or DELETE. Combined with the hash chain, this provides strong tamper evidence.
Testing RLS
RLS policies are tested in CI by:
- Inserting data for two different orgs
- Setting
app.current_org_idto org A - Asserting that queries only return org A's data
- Attempting cross-org access and asserting zero rows returned