Security & data protection

Every claim below names the mechanism that makes it true. Where something is not in place, it is listed at the bottom rather than left out.

Tenant isolation enforced by the database, not the application

One clinic can never read another clinic's records, and that is not a matter of the application remembering to filter. Every tenant-scoped table carries a row-level security policy that Postgres itself enforces on every query. The application connects as a restricted role that cannot bypass it; the migration role that can is never used to serve a request.

How: Postgres RLS with FORCE ROW LEVEL SECURITY on 76 tables and a tenant_isolation policy on 75 of them. The remainder are deliberately global (users, tenants, public holidays) or are token tables that must be looked up before a tenant is known — each one documented in the schema with the reason.

Sensitive fields encrypted at rest, individually

Disk encryption protects a stolen drive. It does not protect a leaked database dump. Specific columns holding contact and identifying data are encrypted individually, so the ciphertext is useless without a key held outside the database.

How: AES-256-GCM with a random 12-byte IV per write, keyed from a secret the application refuses to start without. Applied to 8 columns selected by auditing which fields the server never needs to filter or sort on.

Every access to clinical data is recorded

Reads as well as writes. An audit trail that only records changes cannot answer the question that actually matters after an incident, which is who looked.

How: An audit_log row per operation carrying actor, action, entity, and IP. Requests authenticated by API key are recorded with an api_key actor type and a null user — indistinguishable in shape from staff access except in who made the call.

Multi-factor authentication and single sign-on

MFA is available on any account. Clinics that already run an identity provider can use it instead of managing another set of passwords.

How: TOTP enrolment with verification, and OIDC-based SSO configured per organisation.

Role-based access, narrowable but never widenable

Each role has a ceiling defined in code. An administrator can tighten what a role reaches; they cannot grant a role more than its ceiling. A too-permissive default would be unrecoverable, so the direction of travel is one-way by design.

How: A permission matrix per role and module, with the hardcoded default as a hard maximum that a tenant override can only reduce.

Patient rights are implemented, not promised

Access, portability and erasure exist as working endpoints rather than a process someone performs by hand on request.

How: Full per-patient JSON export, and anonymisation-based erasure requests that preserve the financial record's integrity while removing identifying data. Consent is tracked per purpose with its legal basis — including a distinct purpose for disclosing fit-for-work status to an employer, which never carries clinical findings.

Bulk-export monitoring

The realistic insider risk in a clinical system is not a break-in; it is a legitimate account exporting far more than it needs.

How: A scheduled detector that flags export volume anomalies into a security-events log.

What we do not claim

Architecture verified against the codebase and live schema on 25 August 2026. Questions from a prospective clinic’s data protection officer are welcome and answered directly — hello@kalinga.app.