Musafy Websites & bookings for travel agencies Sign in Start free
Security & data isolation

Three layers between one agency and another.

Your travellers' passports sit in the same database as other agencies' — and the database itself refuses to hand them over. Filtering in application code is the first layer, not the only one.

  1. 01

    The application filters

    Every table that belongs to an agency is read through a manager that adds the agency to the query automatically. No screen and no endpoint writes that filter by hand, so none of them can forget it.

  2. 02

    The database refuses

    Every one of those tables carries a row-level security policy, and the agency for the current request is set inside the transaction. A query that forgot its filter returns nothing at all rather than someone else's rows.

  3. 03

    The connection cannot opt out

    The application connects as a role that is not permitted to bypass those policies. The one role that can is used only by platform operations, and every use of it is on the audit trail.

ALTER TABLE package ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON package
USING      (tenant_id = current_setting('app.tenant_id')::uuid)
WITH CHECK (tenant_id = current_setting('app.tenant_id')::uuid);

The setting is scoped to the transaction, not the connection — which is what lets one pooled connection serve every agency safely, and is the difference between a pool that is fast and a pool that leaks.

The exceptions, stated plainly

Three things that are not scoped to one agency.

Shared reference data

Countries, airlines, room sharing types, plans, themes and apps belong to nobody. They carry no agency and are the same list for everyone.

The hostname lookup

Finding which agency a domain belongs to has to happen before there is an agency to scope to. It is the one cross-agency read in a normal page request, and it reads exactly one column.

A database of your own

If you need physical separation, an agency can be moved to a dedicated database without a code change, because every access already goes through the same key.

What is true of every record

Conventions, not promises.

ConventionWhat it means for you
Unguessable idsRecords are identified by random ids, so nobody can walk your bookings by counting
Nothing is really deletedDeletes are marked, not erased — a mistake is recoverable, and history stays intact
Every change is loggedWho, what changed, from where, and the request it belonged to — append-only
Secrets are encryptedGateway credentials, SMTP passwords, webhook secrets and two-factor secrets, with a key per agency
Money is exactStored as decimals with an explicit currency, never as floating point
Files sit under your own prefixLogos, brand assets, traveller documents and exports, uploaded straight from the browser with a signed URL

Staff see only their part

Roles are per agency and permissions are per module, so a counter agent can work leads without reaching your billing. The last owner can neither be removed nor demoted.

Support cannot quietly look

If we need to see your account to help you, the session is read-only and recorded — every impersonation ever started is listed, with who started it and why.

Your storefront sets no cookies

Public pages carry no session and no tracking of ours. It is what makes them cacheable, and it means a traveller browsing your packages is not being followed by us.