Skip to content
ZivoHR

Interface style

Colour scheme

All writing
AI at work

An HR assistant that cannot see your salary is more useful, not less

Most assistant demos work because there is one user and nothing confidential. Real HR data has a visibility boundary that runs between colleagues, and an assistant that ignores it is not a feature — it is an incident.

The ZivoHR engineering teamEngineering7 May 2026 · 7 min read
The assistant answers through the asker's permissionsAn employee asks "What is Priya's salary?" and the asker's-permissions gate stays closed, so the answer is no access; HR asks the same question, the gate opens, and the answer comes back.EmployeeWhat is Priya'ssalary?asker'spermissionsno accessHRWhat is Priya'ssalary?asker'spermissionsanswer

The demo is always the same. Someone types “how many people are on leave next week?” into a chat box, the answer appears, the room nods. It is a good demo. It is also a demo with one user in it, and that one user can see everything.

HR data does not work like that. The boundary in an HR system does not run around the company — it runs between two colleagues sitting next to each other. One of them can see the other’s payslip. The other cannot. Both of them are going to type questions into the same box.

That constraint is usually presented as the thing holding an HR assistant back. We think it is the thing that makes one deployable at all.

The boundary is per-person, and it is not simple

A quick inventory of what “who can see this” means in practice, because it is worse than most people assume:

  • A manager sees their reporting subtree — but a skip-level manager sees deeper, and a dotted-line manager may see nothing.
  • Compensation is visible to the employee, to finance, and to some slice of HR. Usually not to the direct manager, and almost never to peers.
  • A performance review is visible to the reviewer and the reviewee at different points in its lifecycle, and a review still in draft is visible to neither in final form.
  • A helpdesk ticket about a colleague’s behaviour is visible to a named group of two people, and treating it as ordinary tenant data is a serious failure.
  • Documents carry their own classification, and one flag on the record is the entire difference between a routine file and one that must never appear in a search result.

None of that is expressible as “this user is an admin”. It is a per-row decision that depends on the asker, their role’s permission codes, and their position in the org tree.

Query as the asker, not as a service account

The single architectural decision that matters: the assistant runs inside the asker’s identity, not beside it.

Concretely, the tools the model can call are the same data-access paths the API uses, bound to the same request context. Row scoping is enforced by the model manager, so a query with no tenant bound raises rather than returning everything, and a query bound to this request returns what this employee is allowed to see. The assistant has no privileged credential to fall back on, because there is no such credential in the path.

This has a property that is easy to undersell: the assistant cannot be more permissive than the application. Any bug in what it can reach is a bug in what the API can reach, and that surface is already tested, audited and understood. You have one authorization model, not two.

The alternative — a service account with broad read access, and a check somewhere after — means the assistant is a second implementation of your permission model. Second implementations drift. Yours will drift on a Friday.

“Filter the answer afterwards” is the wrong shape

The tempting design is to retrieve broadly, generate an answer, then strip out anything the asker should not have seen. It fails on four counts, and they compound.

The data is already in the context. Once a salary figure is in the prompt, the only thing standing between it and the user is the model’s willingness to withhold it. That is not an access control. That is a request.

Aggregates leak. “What is the average salary of my team?” over a team of two, where the asker is one of them, is a subtraction problem. Redacting the individual rows does not help if the summary survives.

Retrieval indexes leak more quietly. If you embed every document into one index and filter at read time, the nearest-neighbour search has already ranked confidential text against the query. Everything about the response — what it says, what it declines to say, how long it takes — carries a little information about what was in there.

Injection gets a target. If the context contains data the asker is not entitled to, then any instruction the model can be talked into following becomes an exfiltration path. If the context never contained it, there is nothing to talk it into.

So the filter goes at retrieval, in the query, in the scoped manager — not at the end.

When the answer is not available, say so

An assistant that has been correctly narrowed will hit questions it cannot answer. What it does at that moment is most of its trustworthiness.

There are three different noes, and collapsing them is a mistake:

  1. “You are not able to see this.” The record exists; your role does not include it. Say that. Vagueness here reads as a bug and generates a support ticket.
  2. “Nobody has recorded this.” The policy does not cover the case, or the field is empty. This is the honest answer to most policy questions, and it is the one a general-purpose model is least willing to give.
  3. “This needs a person.” Anything with a legal or contractual consequence. Route it, name who owns it, stop.

What it must never do is fill the gap. A model that does not know the notice period and produces a plausible one has done something worse than fail — it has produced an answer an employee will act on, in a register that sounds authoritative. Grounding is not only about retrieval quality. It is about the willingness to return nothing.

The display name is untrusted input

Here is a bug we shipped and had to fix, and it is worth stating plainly because it is easy to reproduce elsewhere.

Assistant prompts are assembled from workspace data: the asker’s name, their department, their manager, the policy text. Those strings go into an instructions block. Some of them are user-settable. A display name is a text field that the employee controls.

Set your display name to a sentence that looks like an instruction, and you have written into the assistant’s instructions. The mitigation is boring and it works: every user-settable string that touches an instruction block goes through an identifier sanitiser, which strips control characters and anything structural, and the untrusted values are clearly delimited from the instructions.

The important part is the layering. Even with a successful injection, the tools remain bound to the asker’s permissions — so the worst case is a rude answer, not another employee’s bank details. Prompt hygiene is defence in depth. Scoped tools are the defence. Anyone who has only the first has a demo, not a deployment.

Refusing to act is what earns the trust

The last piece is on the write side. An assistant that can file a leave request can also file the wrong one. Ours acts within a narrow set of operations, each of which is the same operation the UI performs, subject to the same approval workflow — the request goes to the same approver, in the same engine, and produces the same audit row with the actor recorded.

Anything with a signature on it stops and asks for a human. Not because the model could not draft it, but because the person receiving that letter is entitled to know a person sent it.

The honest trade

A permission-aware assistant answers fewer questions than an unconstrained one. That is not a temporary limitation to be engineered away; it is the specification. It also costs something real in engineering: retrieval has to be scoped per asker, which means you cannot precompute one index and be done, and answers arrive a little slower for it.

We will take that trade every time. An assistant that occasionally says “you cannot see this” is one an HR team will deploy to two thousand people. An assistant that answers everything is one that gets deployed to four people in HR, is never trusted with anything else, and quietly stops being used.

Book a demo · 30 min

See it on your own numbers.

Thirty minutes, your policies, your payroll edge cases. We will show you the parts that usually break.