Security

Security and access control

Every account arrives by invitation, every change is recorded, and every permission is checked on the server. Here is exactly what that means.

Role-based access control

Permissions are data, not code branches. Every company starts from the same default grid and can change any cell of it — for its own organisation only, and without waiting for a release.

Why it matters

  • Permissions are granular down to the individual action — approving a drawing, seeing a cost, awarding an enquiry — each one its own switch, grouped by module.
  • Eight studio-side project roles ship as standard and can be assigned to anyone on the team: project manager, senior architect, architect, procurement officer, quantity surveyor, site engineer, finance and viewer.
  • Client and vendor access is derived rather than handed out. A client is matched to their project by email address and a vendor arrives through their own portal, so neither identity can be granted to the wrong person by mistake.
  • A company can override any default grant for its own organisation. The shared defaults stay untouched underneath, and resetting a role returns it to them.
  • Custom roles a company creates, renames, duplicates from an existing role, and deletes.
  • Permission checks run in the API route, on the server. Hiding a button is presentation; the gate is behind it.
  • The matrix only offers toggles the application actually enforces. Permissions for modules that are not built yet are withheld or shown disabled, so nobody grants an authority that governs nothing.

Invite-only access

The question of who is allowed in is answered once, in one place, before an account exists. Nobody is admitted first and sorted out afterwards.

Why it matters

  • No public registration. The sign-up flow only completes for an address that was already expected.
  • The check runs in a server-side hook on account creation, not in the form. A request that skips the interface is refused the same way.
  • The invitation decides what the account is — studio member, client or vendor — so the role is never inferred after the fact.
  • Accepting an invitation places the account in exactly one organisation, with the role that invitation carried.

Sessions and sign-in

Two layers, deliberately. The cheap check runs at the edge on every request; the real one runs against the database before anything is rendered.

Why it matters

  • Edge middleware checks for a session cookie; the dashboard layout revalidates the session against the database on every request.
  • Five consecutive failed sign-ins lock the account for fifteen minutes. The lock expires on its own — no administrator has to unpick it.
  • An account can be invited, active, suspended, inactive or archived. Only active and invited accounts can hold a session; the rest are refused at sign-in and on every authenticated route.
  • Every request that changes something is checked same-origin and fails closed, and the endpoints that warrant it are rate-limited.

Audit log

The log is not a debugging aid that happens to be visible. It is a first-class screen, built to answer the question an auditor actually asks: who changed this, and what did it say before.

Why it matters

  • Actions across the platform write an audit event carrying the actor, the action, and the record it touched.
  • Each entry carries the values before and after the change, readable field by field in a detail drawer.
  • Filter by actor, action, category or record type, search across actors and actions, and sort oldest or newest.
  • Export the whole filtered set as CSV — not only the page on screen.

Data handling

Nothing exotic, and that is the point: a standard managed Postgres, queried the one safe way, with the genuinely sensitive fields encrypted before they are stored.

Why it matters

  • PostgreSQL on Supabase, with file storage on the same platform.
  • Every query is parameterised. Values never reach SQL through string interpolation.
  • Vendor bank details are encrypted at rest with AES-256-GCM, in an envelope versioned so keys can be rotated without re-encrypting history. A studio reading them writes an audit entry.
  • Files the app serves go through an authorising proxy. It re-checks the caller's access to that project on every request, mints a short-lived signed URL server-side and streams the bytes back, so the browser never holds a storage URL and the response is marked private and uncacheable.
  • Records carry the organisation that owns them, and the queries behind each screen filter on it.

Content Security Policy and headers

Set once, at the edge, for every route the application serves — so a new page cannot arrive without them.

Why it matters

  • A Content Security Policy on every response. It defaults to our own origin, and allows connections, scripts and images only from our origin, our Supabase storage and our analytics host.
  • frame-ancestors 'none' and X-Frame-Options: DENY — the application cannot be embedded in another site.
  • X-Content-Type-Options: nosniff on every response.
  • Referrer-Policy: strict-origin-when-cross-origin, so full URLs are never sent to another site.
  • base-uri and form-action are both restricted to our own origin, so an injected form has nowhere to post.

Send us the questionnaire

If your IT team has a review process, we would rather answer it properly than have you guess from this page.

Talk to us