Let's talk
security

Operations staff could see what every client was charged

The crew who mount and photograph boards could open Billing and read client rates. Removing the permission revoked nothing, and the next fix would have erased data.

· · updated

An office manager at a long table checking a client invoice in Sazinga AdBoard on a laptop.

Your operations crew mount the flex, photograph the board and log the visit. They have no reason to know what the client paid for that board, and the role they hold says, in its own description, that they should not. Yet an operations user could open Billing in the office portal and see what each client had been charged and what each supplier had been promised, and on the phone could read the campaign and site rates.

Nothing in the log says anyone misused it. That is not the point. The point is that a rate a client negotiated is the most sensitive number in an outdoor-media business, and it was readable by the people least likely to be asked to keep it, on the device most likely to be shown to someone else.

What was actually going on

The Operations role’s definition granted it the two permissions that make up the Billing section. Taking them out of the definition should have fixed it. It fixed nothing, because the routine that applies role definitions to each customer’s database only ever added missing permissions and never removed surplus ones. A permission dropped from a role stayed granted for ever on every installation already set up. The change looked applied and silently was not, which is the worst kind of access-control change.

The second exposure came later, from a different permission: the one that governs seeing money at all. Operations held it. The obvious fix, taking it away, would have destroyed data. The site editing form loads a site from the server and sends the whole form back on save, so a user who could no longer see supplier rates would have sent blanks in their place, and every save would have overwritten the stored rates with nothing.

And four parts of the dashboard were gated only on the permission for their own module and never asked the money permission. Once the money fields were stripped from what the server sent, those screens carried on rendering with a hole where the figure had been. That is how an operations user was shown a Pipeline of ₹0: the screen, given nothing, printed zero.

What we changed

The role routine now reconciles in both directions, granting what is missing and revoking what is surplus, with a dry run that names exactly what it will take away. The first dry run found that the flag only silenced the revokes and not the grants, which was fixed before the tool was trusted. The real run named two permissions and nothing else, and the result was verified by reading the production database rather than the script’s own output. Operations now holds no billing permission; owner, administrator and finance keep the full set.

For the second exposure the larger change was the safer one. The money permission was split in two: one for client money, what a client was charged, and one for costs, what a job costs us in supplier rates, utility bills, installation and expenses. Operations lost the first and kept the second, so the form can still see the rates it would otherwise have blanked. On the original 77-permission build this distinction did not exist, because nobody had yet needed it.

The dashboard sections are now withheld as whole blocks rather than as a missing number, because each screen turned a missing number into a confident falsehood: the phone summed a trend into not-a-number, the profit panel printed “No active projects this period” for an empty list, and the portal formats an absent amount as ₹0. Money fields are also stripped from proposal and project responses, where five had been passing through, including a total whose two parts were both denied.

What it did not fix

A signed-in operations user kept seeing the Billing menu until they signed out and back in, because the portal remembers the permission list from login; the server enforces per request, behind a sixty-second cache, so the pages themselves were closed at once. The read-only Viewer role still carried both billing permissions when the first fix was made and was flagged rather than changed. And the log does not record how long Operations had held the access, or whether anyone had looked.

The mechanism

Two rules came out of this. A role routine that only grants is not a role routine; it must prune, and it must say what it will prune before it does. And a permission is enforced at the response, not the screen: the server removes the fields a caller is not entitled to, keyed to two lists, client money and costs, and a caller holding both skips the redaction entirely. Where a figure is computed from parts, denying the parts must deny the sum, which was the mistake on the pipeline value and again on the settlement total.

AdBoard, which runs for Gold Sign Media, an outdoor media operator, puts a phone in the hand of every field user, and what that phone shows about money is decided on the server, per permission, not by which screens the app happens to have.

This came out of building Sazinga AdBoard

Every site, every booking, every invoice — in one place. The problem above is one we met while building it, and what we did about it is in the product.

If you run something like this, there is one thing you can do without a call: tell us your board count.