Restrict data export by branch: the check most miss
Every screen limited a branch manager to one branch, but the export did not. How to check what your staff can download, and what closing the gap took.
If you limit managers to their own branch, the screens are only half the job. The usual gap is the export: reports and downloads often take a separate route to the data, one that never meets the branch limit. A manager who cannot open another branch’s customer can still download the whole company’s list as a spreadsheet. The first thing to check is to log in as a branch-limited user, export a report, and count whose records are in the file.
Here is how that looked in our own reports, and what fixing it took.
You limit a manager to one branch. Every screen agrees: their lists show their branch, and records from other branches refuse to open. Then they click export on a report and receive the whole company’s customers, orders, stock levels, receivables and payments as a spreadsheet, and keep it.
That is what our reports did. A number on a screen is a number on a screen. A file leaves the building, gets emailed, sits on a laptop and survives the person leaving the company. So the risk was not a wrong figure. It was a complete copy of the business in the hands of someone who was only meant to see a slice.
What was actually going on
The reporting module kept organisations apart: one company’s reports could never show another company’s data. That held. What the module never received was who was asking. The report functions took a report name and some settings and returned rows, so there was no way to apply the asker’s branch limit.
Every other part of the product had been built around the person and their role. Reports had been built separately, later, by someone thinking about totals rather than permissions. They needed their own way of querying data, because the ordinary code returns whole records and a report needs arbitrary columns. The moment a report has its own route to the data, it has its own relationship with the permission rules, which is usually none.
The same is true of anything that produces a file, feeds a notification to another system, backs a scheduled email or serves an integration. Each is a way for data to leave that never passed through the place where the checks live.
What we changed
Reports are now told who is asking and which permission they are using. That second part matters, because a branch limit belongs to a role and a permission together. A branch manager may sensibly see company-wide totals on a dashboard and still be refused the rows behind them as a download. So reading a dashboard and exporting a file are checked separately, and can be set differently for the same role.
Each report then needed a decision about what “belongs to this branch” means. Orders, stock movements and payments narrow on their own branch. Customers narrow on the branch they are registered to. Outstanding balances needed a new view, because the existing one worked per customer: narrowing it by the customer’s branch would have shown that customer’s invoices from every branch, the right customers with the wrong money. We did not recalculate in application code, because two calculations of one number is a fault waiting for a date.
One limit the reports refuse outright: “only records this user created” makes sense on a list and not across a summarised report. The module says so with an explicit error rather than approximating, because a file whose contents nobody can describe is worse than none.
Then we proved the fix. We ran the tests against the unfixed code, and fourteen failed, three of them showing the export leak. A test written after the fix can agree with the new code and still miss the original fault, so running it against the broken version is the only way to know.
What it did not fix
A file exported within a manager’s own branch still leaves the building. The change narrows what goes into it; it does not follow the file afterwards.
How to restrict exports by branch
- Log in as a branch-limited user, export every report you have, and check whose records are in each file.
- List every other way data leaves your system: integrations, scheduled emails, notifications to other systems. For each one, name the check on that path. “We have permissions” is not an answer.
- Ask your vendor whether a person can be allowed to see a total but not download the rows. If the system cannot say that, it will be set to whichever answer is less annoying, and that is always the more permissive one.
- Decide who may download at all, separately from who may view.
Where this ends up
A branch-limited user is the ordinary case in Sazinga Field, where orders, stock, receivables and payments all belong to branches, so who may take a copy of them is a question its reports have to answer themselves.