Back home# OMNIA — Security Posture
**Version:** 1.0
**Last reviewed:** 2026-05-10
## Tenancy model
- Every domain table has a non-null `school_id` column.
- Row-Level Security is enabled on every domain table.
- Read / write policies use the `has_school_access(uid, school_id)`
SECURITY DEFINER function, which checks the caller's `profiles.school_id`.
- Admin-scoped writes additionally require `has_role(uid, 'admin')`.
- All `supabaseAdmin` (service-role) writes that target tenant tables
re-validate `school_id` in application code as a defence-in-depth layer
(see `mergeIntoPlan` in `src/lib/parent-voice/public.functions.ts`).
## Authentication
- Supabase Auth, email + password and Google OAuth.
- Roles stored in `user_roles` (separate from `profiles`) — never on the
user record, to avoid privilege-escalation via profile edit.
- Available roles: `superadmin`, `admin`, `inclusion_lead`, `senco`,
`teacher`, `peep_only`, `read_only`.
## Public surfaces
| Surface | Auth | Rate limit |
|---|---|---|
| `/p/$token` (plan share) | Hashed token + 4-digit PIN | 5 attempts → 15 min lockout |
| `/parent-voice/$token` | Hashed token + 4-digit PIN | 5 attempts → 15 min lockout |
| `/api/public/hooks/retention-sweep` | `apikey` header (publishable key) | Cron-only, 1×/day |
Tokens are stored as SHA-256 hashes; PINs are scrypt-hashed with a per-row
salt. The raw token / PIN is shown to the issuing staff member exactly once.
## Audit log
`audit_logs` captures every read/export/share event. Retention 730 days.
SENCo and admin can read their own school's log. Per-pupil panel surfaces
the last 90 days on the pupil page for inspector handover.
## Secrets
| Secret | Purpose |
|---|---|
| `SUPABASE_SERVICE_ROLE_KEY` | Admin client (server only) |
| `SUPABASE_PUBLISHABLE_KEY` | Browser client + cron auth |
| `MS_GRAPH_CLIENT_ID` / `_SECRET` / `_TENANT_ID` | SharePoint discovery + parent-voice mailbox |
| `LOVABLE_API_KEY` | AI Gateway (specialist-summary LLM) |
No secret is exposed to the browser. `SUPABASE_SERVICE_ROLE_KEY` is only
imported via `@/integrations/supabase/client.server`, which is blocked from
client bundles by Vite import protection.
## Known limitations (disclosed)
- **Scanned PDFs** uploaded to Specialist Summary are not OCR'd; image-only
PDFs return empty text and the staff member is asked to re-key.
- **SharePoint discovery** matches on national ID — pupils without an ID
configured are skipped (counted in the run report).
- **AI Gateway** processes specialist report text in-flight; we do not
control upstream provider retention beyond what the AI Gateway exposes.
## Incident playbook
1. **Suspected token leak** → SENCo revokes the share link from the pupil
page; the daily sweep removes the row at 365d.
2. **Suspected RLS bypass** → admin runs the Supabase database linter and
reviews `audit_logs` for unexpected `actor_kind=service`.
3. **Account takeover** → admin disables the `user_roles` rows for that
user; Supabase Auth password reset + session revoke from the dashboard.
4. **Data subject request** → use **Download data pack** on the pupil page
for access; **Erase pupil record** for erasure.
OMNIA AssistantBeta · here to help
Hi, I'm the OMNIA Assistant. Ask me about plans, pupils, or how to use the app.