OMNIAInclusion
HomeFeaturesConnectionsEvidence basePricing & ROIInsightsAboutFAQContactBook a demoSign in
All documents
Ask a question
OMNIA INCLUSION LTD

Security Posture Overview

Version 1.4See documentLive

Data controller / processor: OMNIA Inclusion Ltd (registered in England & Wales, company no. 17228173). Registered office: 169 High Street, Marske-by-the-Sea, Redcar & Cleveland, TS11 7LN, United Kingdom.

Version: 1.4 Last reviewed: 2026-06-01

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').
  • Defence-in-depth status (honest): server functions that write to tenant tables via supabaseAdmin (e.g. mergeIntoPlan) re-validate school_id in application code. Server functions that read via the user-scoped client (e.g. analytics, DSAR export) also apply an explicit .eq("school_id", schoolId) filter as a backstop in case of an RLS regression. The same explicit filter is now applied to timetable delete mutations. A handful of pre-existing reads still rely on RLS alone — these are tracked and being migrated.

Authentication

  • Supabase Auth, email + password and Google OAuth.
  • Leaked-password protection (HIBP) is enabled at the Auth provider — passwords are checked against the Have I Been Pwned database on signup and password change.
  • 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.
  • MFA (TOTP) is enforced for admin and superadmin roles. The /admin and /superadmin shells gate render on mfa.getAuthenticatorAssuranceLevel().currentLevel === 'aal2' and on the existence of a verified TOTP factor. Users without one are redirected to /mfa to enrol; users with one but at aal1 are prompted to step up. Other roles can enrol voluntarily.
  • Demo access flow: the /demo access code entry endpoint validates the client-supplied redirectTo against the request's own origin to prevent open-redirect / session-theft attacks. Demo access codes are not single-use; they expire 10 days after generation and redemptions are tracked in the demo_access_codes table.

Public surfaces

SurfaceAuthRate limit
/p/$token (plan share)Hashed token + 4-digit PINPer-IP and per-token via enforceRateLimit (salted with RATE_LIMIT_SALT, backed by public.rate_limits); plus 5 attempts → 15 min lockout per row
/parent-voice/$tokenHashed token + 4-digit PINPer-IP + per-token via enforceRateLimit; plus per-row lockout
/pupil-voice/$tokenHashed token + 4-digit PINPer-IP + per-token via enforceRateLimit; plus per-row lockout
/api/public/hooks/*CRON_SECRET bearer token (timing-safe) — never the publishable/anon keyCron-only
/demo (demo access entry)Access code format validationPer-IP rate limit; codes expire 10 days after generation; redemption tracking in demo_access_codes; no pupil data

enforceRateLimit (from @/lib/rate-limit/check.server) is the first line of every public token handler. The previously-disclosed gap (per-row only) is closed.

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.

Tamper-evident: RLS denies UPDATE and DELETE on audit_logs and system_audit_logs for authenticated and anon roles via explicit USING (false) policies. Only server-side code holding the service-role key (i.e. supabaseAdmin in a server function) can modify rows — a compromised user session cannot edit or erase audit history.

Secrets

SecretPurpose
SUPABASE_SERVICE_ROLE_KEYAdmin client (server only)
SUPABASE_PUBLISHABLE_KEYBrowser client
CRON_SECRETBearer token for /api/public/hooks/*
MS_GRAPH_CLIENT_ID / _SECRET / _TENANT_IDSharePoint discovery + parent-voice mailbox
LOVABLE_API_KEYAI Gateway (standard-mode LLM traffic)
BYOK_ENC_KEYAES-256-GCM master key wrapping per-school BYOK API keys at rest
RATE_LIMIT_SALTHMAC salt for IP / token rate-limit buckets in public.rate_limits
available_interventions RLSSchool-scoped reads for members; SENDCo and admin writes only; staff names in trained_staff field treated as staff personal data

SUPABASE_SERVICE_ROLE_KEY is only imported via @/integrations/supabase/client.server, which is blocked from client bundles by Vite import protection. Application secrets (LOVABLE_API_KEY, MS_GRAPH_*, BYOK_ENC_KEY, RATE_LIMIT_SALT) never reach the browser.

BYOK (Bring Your Own Key) — Connected tier

Schools on the Connected tier may activate BYOK to route AI traffic to their own Anthropic or Azure OpenAI account instead of the Lovable AI Gateway. Implementation:

  • Keys are AES-256-GCM encrypted at rest (src/lib/byok/crypto.server.ts) using BYOK_ENC_KEY. The plaintext key is never returned to the browser. Only provider, endpoint, last4, active, and verified_at are surfaced.
  • school_ai_credentials.api_key_ciphertext has column-level SELECT revoked from authenticated and anon. Only supabaseAdmin reads it, and only to decrypt server-side immediately before an upstream call.
  • callLovableAiChat auto-routes to the school's BYOK when a schoolId is passed and school_ai_credentials is active. PII scrubbing still applies. A BYOK upstream failure falls back to the Lovable Gateway only where the school has opted in to fallback; otherwise the error surfaces to the school admin.
  • The school chooses the Azure region (e.g. uaenorth, uksouth). That region may sit outside the UK/EEA — the school is the controller of its provider relationship and is responsible for the residency decision. Documented in the school's signed DPA.

Credential columns are not readable by the browser client. Across plan_share_links, parent_voice_requests, pupil_voice_requests, pending_emails, import_connections, and school_export_settings, column-level SELECT on pin_hash, pin_salt, token_hash, webhook_secret, api_key, and sent_to_email is REVOKEd from the authenticated and anon roles. Even a select('*') issued with the publishable key cannot return those columns. The only readers are server functions that explicitly use supabaseAdmin (e.g. PIN verification on the public plan-share / parent-voice / pupil-voice endpoints, and MIS connector code on the server).

Additionally, pending_emails SELECT/UPDATE and aa_notification_log SELECT are restricted at the RLS layer to admin / superadmin only, and the trigger / RLS helper SECURITY DEFINER functions (handle_new_user, enforce_signup_open, has_role, has_school_access, is_superadmin, user_school_id) have their EXECUTE grants tightened — trigger functions are not callable by clients at all, and the helpers are callable only by authenticated.

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 (standard mode) processes specialist report text in-flight; we do not control upstream provider retention beyond what the AI Gateway exposes.
  • BYOK mode moves AI processing onto the school's own provider tenancy. OMNIA cannot enforce retention, residency, or training-opt-out beyond what the school configures with that provider.

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.

Reporting a vulnerability

Please email security@omnia-inclusion.com with a description of the issue, reproduction steps, and (where relevant) the affected URL or endpoint. The machine-readable contact is also published at /.well-known/security.txt. We commit to:

  • Acknowledge receipt within 2 working days.
  • Provide a triage assessment within 5 working days.
  • Coordinate disclosure on a 90-day window from triage; we will keep you updated and credit you in the post-mortem unless you ask us not to.

Please do not run automated scans against production tenants or attempt to access data that does not belong to you. Good-faith research that follows this policy will not lead to legal action from us.

For misuse reports (spam, harassment, content takedown), email omnia.abuse@omnia-inclusion.com instead.

This document is published by OMNIA Inclusion Ltd and is subject to change. For the current version visit omnia-inclusion.com/legal/security.
OMNIA Inclusion Ltd
Company no. 17228173 · ICO: 00014144622
omnia-inclusion.com · hello@omnia-inclusion.com
© 2026
OMNIA Inclusion Ltd · omnia-inclusion.com · Security Posture Overview · v1.4Confidential

OMNIA

Every SEND decision, grounded in evidence.

Product

  • Features
  • Pricing & ROI
  • Trust & Security
  • Insights

For your school

  • England (EHCP)
  • UAE (ADEK)
  • IB World Schools
  • Multi-Academy Trusts

About OMNIA

  • About
  • Evidence base
  • Founding schools
  • Frameworks & acknowledgements
  • FAQ
  • Legal
  • Accessibility
  • hello@omnia-inclusion.com

© 2026 OMNIA Inclusion Ltd · Registered in England & Wales · Company no. 17228173 · ICO registration no. 00014144622