Skip to content

What protects your data, and how

Every item here names the mechanism that enforces it rather than the promise of it, and says how far that mechanism reaches today — because an accounting system is judged on what it can prove.

  1. Tenant isolation

    Every row in the database carries the tenant id, and every connection pins the tenant identity at the start of the request. Row-level security policies are enabled on two hundred tables, and forced enforcement is being tightened on them table by table.

    An attempt to reach another tenant's record answers 404, not 403 — because a 403 confirms the record exists, and that is covered by integration tests on read and write paths. Forced enforcement (FORCE ROW LEVEL SECURITY) is on five tables of the two hundred today, and the rest rely on the context pinned at connection time.

  2. Permissions by resource and action, not by role

    Three hundred and two permissions across ninety-one resources, sixteen predefined roles, and the ability to build custom roles. The code never checks a role — it checks a permission.

    The check runs before the request handler rather than inside it, and a route that declares no permission is denied by default. A permission is scoped as well: branch always, and business unit on sales, on the money paths and on stock writes — on the remaining routes that scope is advisory so far, and counted exactly in the repository.

  3. Two-factor authentication

    Two-factor authentication is mandatory on every account that holds a permission touching money, the books, permissions or personal data — not on administrative roles alone, but on whoever actually holds the permission.

    Recovery codes are issued once, stored hashed and used once. Our own platform operators are held to the same requirement, enforced by a database constraint rather than by agreement.

  4. Passwords and sessions

    Hashing with argon2id, a minimum length of twelve characters, and timing-safe comparison. The access token lives fifteen minutes, and the refresh token rotates on every use.

    Reusing a spent refresh token revokes the whole session family — which is what makes a stolen token detectable. Changing the password ends every session, and "log out everywhere" exists and works.

  5. An audit trail that cannot be edited

    A table that is appended to and never updated or deleted from, enforced by a database trigger. It records the actor, the action, the resource, the value before and after, the tenant, the network address and the request id.

    Before the write, the content passes through a redactor that strips passwords, tokens, two-factor secrets, IBANs, card numbers and national IDs, up to eight levels of nesting.

  6. Encryption of sensitive fields

    Nine columns are encrypted in the database with AES-256-GCM, and the column name itself is part of the authenticated data — so a ciphertext cannot be moved from one column to another.

    The encrypted columns are: the employee's national ID and IBAN, the national ID and IBAN on the payroll line, the national ID of the signer of a catering contract, and the two-factor secrets for users and for platform operators. Those columns by name, not all data.

  7. A posted document cannot be edited

    A posted entry is protected by a database trigger that refuses any update or delete. A correction is a reversing entry plus a new one, and both remain.

    The balance of the entry itself is enforced by a CHECK constraint, not by a check in code that can be skipped.

  8. A platform operator is not a user

    Our team's accounts live in a separate table, with separate credentials and tokens of a different audience. There is no role that grants a user in your company access to the platform.

    Support impersonation is capped at eight hours by a database trigger, requires a written reason, is recorded in our log and in your company's audit trail together, and shows as a visible banner in the interface.

Permissions are administered on a screen, not in a config file

The sixteen ready-made roles are a starting point. Custom roles are built from the same permissions and reviewed before they are granted, on a screen that says exactly what the role allows — because a role granted without being read is how permissions leak.

Permissions grantedPayments officercustom:payments-officer
  • expenses.readReads expense claims and their detailadded
What the role allows
Two-step verification
Not required
Separation of duties
No conflict

Can be saved

The role name is illustrative. The permissions and both rules are real: from the same permission registry the system runs on.

Reporting a vulnerability

If you find a security flaw, write to us directly before publishing it. We answer every report.