Skip to content
ZivoHR

Interface style

Colour scheme

Security

Isolation you can
read in the source.

Multi-tenancy is a security property before it is an architecture choice. This page describes the mechanisms as they are implemented today — where the check lives, what happens when it is forgotten, and what we do not claim.

Tenant isolationRBACOIDC SSOEncrypted secretsSigned URLsAudit logArgon2id
01

Tenancy

The filter is in the data layer, so it cannot be forgotten in a view.

Most cross-tenant leaks are one missing WHERE clause in one endpoint written on a Friday. The only durable fix is to make the filter impossible to omit, and to make omitting it fail loudly rather than fail open.

How a request reaches only its own workspace's rowsA request to acme.zivohr.com binds the Acme tenant; every query goes through the tenant-scoped model manager, which returns only Acme's rows. A query with no tenant bound raises an error instead of returning everything.acme.zivohr.comtenant: Acmescoped managerAcme's rows onlyno tenant boundraises — never returns all

One database, shared schema, tenant_id on every row

Business data is not spread across per-customer databases that drift apart. It is one schema where every business table carries a tenant column, and every index leads with it.

The subdomain decides which tenant you are

The workspace is resolved from the host on the way in, before any view runs. There is no tenant picker in the request body for an attacker to change.

Scoping is enforced by the model manager, not by each view

The default manager on every business model filters by the tenant bound to the request. A query issued with no tenant bound raises — it does not quietly return every workspace's rows. Forgetting the filter is an exception in development, not a leak in production.

One escape hatch, and it is greppable

Reporting and platform jobs that legitimately cross tenants go through a single explicitly named manager. It is one string to search for in review, and it appears in a countable number of places.

Writes are stamped from context, and a mismatch raises

You cannot save a row into a workspace other than the one the request belongs to. The tenant is taken from context on save, and a mismatch between the row and the request is an error, not a warning.

02

Authentication

A token is a key to one workspace.

Sign-in, session lifetime and credential changes are the parts of an HR system that get attacked first, because they are the parts that are the same on every HR system.

Argon2idTenant-bound JWTSingle-use linksFail-closed throttlesOIDC single sign-on
  • The token knows which workspace it is for

    A JWT carries a tenant claim. Presented against another workspace's host, it fails — a stolen token is not a key to the platform, it is a key to one workspace.

  • Argon2id for passwords

    Memory-hard hashing, tuned for this deployment. Older hashes still verify and are upgraded on the next successful sign-in.

  • Changing a password ends every other session

    A password change or reset revokes every outstanding refresh token. The client that made the change receives a fresh pair; everything else signs out. That is the whole point of the mechanism.

  • Invite and reset links are single-use

    A used token is spent. An invite for an account that has been deactivated is refused, so an old link cannot reactivate someone who has left.

  • An administrator-issued password is spent on first use

    When HR or support sets a password for someone, that account is sent to change it before it can do anything else. A password someone else has typed is never a password you keep.

  • Sign-in throttles fail closed

    Login, reset and token doors are rate-limited per account and per address. If the counter store is unreachable, those doors refuse rather than wave everyone through.

Single sign-on

Okta or Entra ID in front. Your employee record behind.

SSO is OpenID Connect against Okta, Microsoft Entra ID or any standards-compliant OpenID provider, configured per workspace. The same sign-in works on the phone, through the system browser rather than an embedded web view.

OktaMicrosoft Entra IDAny OpenID providerWeb and mobile

Not supported today: SAML, and SCIM provisioning. Joiners and leavers are still managed on the employee record. Signing out of ZivoHR offers a link to sign out at your provider too; it does not redirect you there on its own.

It signs people in. It never signs them up.

Only someone who already has an employee record can come through, matched on the work email on that record. A valid identity-provider account with no ZivoHR record is refused, not created.

Every refusal is written down

The reason is audited and the last twenty are on a read-only screen for HR. The browser only ever sees a generic error, so the sign-in page does not confirm which emails exist.

Passwords switch off only after SSO has worked

Turning password sign-in off is refused until someone has completed a real SSO sign-in on the workspace. Nobody locks a company out by saving a typo in a client ID.

Owners keep a way back in

Workspace owners can still use a password as a break-glass route, and every use of it is audited. Password reset closes for everyone else.

The client secret is write-only

It is encrypted at rest and never returned by the API once saved. You can replace it; you cannot read it back.

Who has not come through yet

A list of linked people who have never signed in with SSO, so a rollout has an end you can see.

03

Authorization

Reaching a row is not permission to change what it belongs to.

Function-level authorization — may this role call this endpoint — is the easy half. The half that actually loses data is object level: whose row is this, and what am I allowed to set on it once I have it open.

Permissions are codes, not job titles

A role is a set of permission codes. "HR Manager" is a name for a bundle, and renaming it changes nothing about what it can do. Adding a capability means adding a code, which means it can be listed.

Row visibility is own record, subtree, or everything

The third dimension after "which module" and "read or write" is "whose rows". A manager sees their reporting subtree because the tree says so, not because someone maintained a list.

Object-level checks run on update, not only on create

Reaching a row tells you which object you may edit. It never tells you what you may set on it. Update paths are authorized separately from create paths.

Validated against the post-image

Authorization is checked against the record as it would be after the change, not as it was loaded. Otherwise a claim, a goal or a review can be moved onto a colleague by editing the field that says who it belongs to.

Ownership and classification freeze after creation

Which employee a record belongs to, who the reviewer is, which cycle it sits in, whether a note is confidential — read-only once the row exists, or re-authorized explicitly.

A role cannot be granted a permission its editor lacks

Otherwise permission to manage roles is permission to grant yourself everything, one hop later.

04

Data & files

Storage that assumes the link will be shared.

Secrets encrypted with their own key

Third-party credentials — SSO client secrets, Slack tokens, device keys — and Ziva conversation text are encrypted at the field level, with a key that must differ from the signing key. Bank accounts sit in their own table, off the record every manager reads, masked for anyone but payroll and HR.

Files stored per tenant

Uploads live under a per-tenant prefix and are never served directly. A download is a permission check followed by a short-lived signed URL that expires on its own.

Validated by magic bytes

A file is inspected for what it actually is, not for what its name claims. Anything a browser would render as markup is refused whatever the extension says.

Exports that cannot run a formula

Every CSV export neutralises cells that a spreadsheet would read as a formula, needs the permission to see everybody, and is written to the audit log with who pulled it.

Your timezone, per request

The workspace timezone is activated for the duration of the request, so an attendance day, a payroll cut-off and "today" all mean the customer's today.

05

Auditability

Who changed what, when, from where.

Every consequential write is recorded with the actor, the tenant and the source IP. The record is append-only and exportable.

  • The IP is read from the trusted endClient IP is counted back from the right of the forwarding chain by the number of proxies we actually run. The leftmost entry is attacker-written, and treating it as the truth makes audit rows forgeable.
  • Logs pass through a redacting filterTokens, credentials and personal identifiers are stripped before a line is written. Errors log the constraint that was violated, not the row that violated it.

The AI layer

Ziva can only see the person asking.

An assistant that can read everything is a data breach with a chat interface. Every tool Ziva has reads the signed-in employee's own record — their leave, their payslips, their claims — and nothing about anybody else.

  • Your workspace is not training dataNothing from a customer workspace is used to train models. Data goes to the model to answer the question in front of it, and that is the end of its life.
  • Arguments it was not given are refusedAsk about a colleague by name and the tool call is rejected, rather than quietly answered with your own data under their name.
  • It proposes; you confirmZiva can draft a leave request or an expense claim. The draft is stored server-side, expires in minutes, is used once, and is re-authorized when you press Confirm on a route the model cannot call.
  • User-settable strings are sanitisedNames, team titles and policy labels reach the model through a sanitiser, because a display name is otherwise a place to write instructions.
06

Operations

A deploy should not be the risky part of the week.

01

Schema changes are expand-then-contract, and it is checked

During a rolling deploy the old release and the new release share one database. A migration that drops or renames a column breaks every container still running the old code. A check runs before migrations and fails the deploy if a migration is not backwards-compatible; a genuine contract has to declare itself and be deployed deliberately.

02

Containers run unprivileged

Application containers run as a non-root user. No long-lived container carries an administrative password in its environment.

07

Compliance posture, stated plainly

We do not hold SOC 2 or ISO 27001.

Not yet, and we would rather say so here than let a badge on a marketing page imply otherwise. What is in place today is what this page describes: the controls in the code, an audit trail you can export, encrypted third-party secrets, and a security review applied while a feature is written rather than bolted on afterwards. When an audit is complete, this paragraph will change and will name the report and its period.

Security questionnaires: answered directly, by the people who wrote the code.
Architecture review: we will walk your team through the isolation model on a call.
Data residency and retention: discussed before you sign, not after.
No SAML and no SCIM yet. SSO is OpenID Connect, and it is switched on per workspace.

Reporting a vulnerability

Write to security@zivohr.com. Include the request, the response, and the account you were signed in as. If you can, include the smallest reproduction you have rather than a scanner report.

  • We acknowledge every report from a person, and we tell you what we found even when the answer is that the behaviour is intended.
  • Please test against your own workspace only. Do not attempt to reach another customer's data to prove a point — describe the path and we will reproduce it.
  • There is no paid bounty programme today. We will credit you in the fix note if you would like to be named.

Book a demo · 30 min

Bring your security reviewer to the call.

We will go through the isolation model, the permission table and the audit trail on screen, in the product, with your questions rather than our slides.