Information security overview
Written to be assessed rather than to reassure. Where a control is weaker than a large buyer would like, it says so and says why.
Authentication
Accounts are held by Supabase Auth. Passwords are never stored or seen by our application: they are hashed with bcrypt by the identity provider, and the application only ever handles a short-lived session token. Sign-in is by email and password or a single-use email link, and email addresses are verified before an account becomes usable.
- Session tokens are short-lived and rotated on use; the rotation is persisted server-side so a stale token cannot be replayed.
- Session cookies are HTTP-only, Secure and SameSite=Lax, so they are unreadable by page scripts.
- Credential attempts are rate limited.
- Standing administrative access carries an expiry date, forcing periodic review rather than accumulating indefinitely.
Authorisation, enforced server-side
No access decision is made in the browser. Roles map to permissions in database tables, and every request resolves the caller's permissions from that mapping server-side. Hiding a button is treated as a courtesy to the user, never as a control.
- Permissions are data, so a new role is a database row rather than a code change — and cannot accidentally inherit a capability.
- Every route and every server action re-checks the permission it needs, independently of what the page rendered.
- Entitlement to paid content is resolved from a licence seat recorded in the database and can never be asserted by the client.
University data isolation
Multi-tenancy is enforced by PostgreSQL row-level security, not by application query discipline. Every tenant-scoped table carries the organisation, and its policy resolves the caller's memberships inside the database. The practical consequence is that a page which forgets to filter by organisation returns nothing rather than another university's data.
- A second, independent layer of permission checks sits in the application, so a policy mistake and a code mistake would both have to happen for data to cross a tenant boundary.
- Column-level grants restrict what an authenticated role may write, not merely which rows. A signed-in user physically cannot write their own entitlement, and cannot amend an answer once submitted.
- Cohort reporting is served from an aggregate view that suppresses any group smaller than five students, so a university's own analytics cannot be used to read an individual's record.
Encryption
All traffic is HTTPS with TLS 1.2 or above, with HTTP Strict Transport Security enforced and preloaded. Data at rest is encrypted with AES-256 by the database provider, including backups.
Application hardening
A Content Security Policy is applied to every response. Pages that carry personal data receive a strict nonce-based policy with strict-dynamic, so an injected script cannot execute. Public reference pages are statically generated and receive a policy that closes everything except inline script execution, which the framework's hydration requires in a prerendered page.
- HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy and Cross-Origin-Opener-Policy are set on every response.
- Framework and version disclosure headers are disabled.
- Responses carrying personal data are marked no-store, so no shared cache can serve one student's data to another.
- Payment webhooks are signature-verified before any state change.
- Rate limits apply to authentication, answer submission, search, content fetches, data exports and administrative writes.
Logging and audit
Administrative and security-relevant actions are written to an append-only audit trail: who acted, when, against which organisation, on what, and whether it was permitted. Refused authorisation attempts are recorded, not only successful ones.
- The application role holds no insert, update or delete grant on the audit table. A user cannot edit the record of what they did.
- The trail deliberately excludes request bodies, answer content and full IP addresses. Source addresses are truncated to a network prefix — enough to correlate an incident, not enough to be a location history.
- An investigation can establish what happened, when, which account, which organisation and what was changed.
Backups and recovery
The database is backed up continuously by the managed provider with point-in-time recovery. Application code is deployed from version control and any release can be rolled back in minutes, because the application holds no state of its own.
- Honest limitation: our recovery objectives are inherited from the provider rather than independently proven. A rehearsed restore has not yet been performed and recorded, and until it has we will not quote a recovery time we cannot evidence. This is the next item on our continuity work.
Secrets and dependencies
Secrets are held in the deployment platform's encrypted environment configuration and never in source control. The database service-role key — the one credential that bypasses row-level security — is used by exactly one code path, the payment webhook, and is never exposed to the browser.
- Dependencies are pinned and updated deliberately; automated advisories are monitored.
- The dependency surface is kept small on purpose. Every additional package is a supply-chain risk and, where it processes personal data, a subprocessor to declare.
What exists now, and what comes next
Separated deliberately. Treating a nice-to-have as a requirement, or the reverse, is how a security conversation goes wrong in both directions.
Required before the first university
- Everything described above.
- Verified email-domain control before any organisation's tenant accepts automatic joins.
- A named security contact and a documented incident response procedure.
Important as we scale
- Cyber Essentials certification — frequently a hard procurement requirement, and cheap relative to what it unlocks.
- An independent penetration test, with the findings and remediation published to customers.
- Rate limiting moved to a shared store so limits are exact across instances rather than per-instance.
- Single sign-on against university identity providers, so student accounts follow institutional lifecycle automatically.
- A rehearsed and recorded database restore.
Future certification
- Cyber Essentials Plus, which adds independent technical verification.
- ISO/IEC 27001. Deliberately not attempted early: it certifies a management system, and certifying one we do not have the people to operate would be a paper exercise.
To report a vulnerability, please contact us with enough detail to reproduce it. We will acknowledge within two working days, will not pursue anyone acting in good faith, and will credit you if you would like us to.
