HireHenry is multi-tenant: many companies' data lives in one system. The only question that matters is what stops one customer's data reaching another's, and whether that something is a rule people follow or a rule the machine enforces.
Ours is enforced by the database, and checked against production continuously rather than at audit time. This page says exactly how, in enough detail that you can hold us to it.
Every row of customer data carries the tenant it belongs to, and PostgreSQL
row-level security filters on it before any query sees a result. That matters
because of what it removes: there is no code path, no forgotten WHERE clause, and no
new endpoint that can accidentally return another company's rows. The filter is not in the query —
it is underneath every query.
The application connects as a non-superuser role. This is the part that is easy to get wrong and invisible when you do: a superuser connection bypasses row-level security entirely, and everything keeps working, and nothing looks different. So we check the role itself, not just the result.
Our operational status endpoint scopes a connection to a tenant that does not exist and counts the rows it can see. Two things have to be true, every time:
This runs against the production database, not a copy, and not a test fixture. A passing check is a statement about the system you are using right now.
PostgreSQL publishes no port outside the application's private network. There is no public endpoint to find, credential-stuff, or scan. The only route to your data is through the application, which means the isolation above is the only door rather than one of several.
HireHenry's assistant improves with use because it remembers how you like to be helped. That is a feature with an obvious failure mode, so it is built with three limits:
Personalization lives in its own service with its own database, isolated by the same row-level security — and additionally by a composite foreign key, so a stored row's tenant cannot disagree with its user's tenant. A constraint prevents the mismatch rather than a report finding it afterwards.
Each tenant declares what must never be remembered, and the service enforces it: credentials, payment and bank details, government identifiers, health information, biometrics, precise location and more are stripped from text before it is summarised, embedded or written. It removes the sensitive span and keeps the rest, and it tells the caller which rule fired — so a filter failing upstream is visible rather than silent.
Anything the system infers about you stays marked as inferred and is never used until a person confirms it. A remembered preference you did not state cannot quietly shape an answer.
We think the last one matters more than it looks. Being told your data is safe is a claim; being able to see who opened it is a fact.
Two principles do most of the work here, and both come from things that went wrong somewhere before they were written down.
A check that shares assumptions with the thing it checks will pass. Isolation is verified from outside the application, by a separate process, using a second tenant's credentials — because a test that asks the system whether it is correct will get the answer the system believes.
Silence is not success. Our checks alert on failing, and also on not running — a monitor that quietly stopped looks exactly like one reporting good news.
If you believe you have found a security issue, write to security@hirehenry.io with enough detail to reproduce it. We would rather hear it early and awkwardly than late and politely.