A branch manager could export everyone's customer list
Every screen limited a branch manager to their own branch. The report export did not, so one click produced the whole company's customers and payments.
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.
The pattern, for anyone who limits staff by branch
List every way data leaves your system: exports, integrations, scheduled emails, notifications to other systems. For each one, name the check on that path. “We have permissions” is not an answer. The paths people miss are the ones whose output is not a screen.
And ask one question of your vendor: can a person 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.
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.