Just finished our third-party SOC2 audit and the biggest finding wasn't in our code—it was in our expense tool's permission sprawl. Turns out, "view-only" for the finance team had quietly morphed into "approve and export" for half the company.
If you're prepping for an audit, your Grafana dashboards are probably locked down, but your SaaS finance tools can be a blind spot. Here's the checklist we used to lock it down, framed like an observability review.
**First, map your current state.**
Most tools have an API or admin export. Get a list of all users and their effective permissions. Don't just trust the role names—map the actual capabilities.
```bash
# Pseudo-code for the mindset
for user in expense_tool.get_users():
permissions = user.get_effective_permissions()
if "APPROVE_INVOICE" in permissions and user.department != "Finance":
log_security_event(user, "Over-privileged")
```
**Key areas to interrogate:**
* **Role Definitions:** Are your custom roles strictly necessary? Can you revert to vendor-standard ones (Admin, Approver, User, View-Only)?
* **Data Export:** Who can export full GL data or vendor lists? This should be tighter than your dashboard admin access.
* **Approval Chains:** Can users approve their own reports or invoices if someone else is out? This breaks segregation of duties.
* **Integration Credentials:** Where are API keys stored for accounting system syncs (e.g., NetSuite, QuickBooks)? Are they rotated? This is a critical sync reliability *and* security point.
**Build your "audit" dashboard.**
Treat this like a service-level objective. Create a simple panel that tracks:
* Count of users with elevated permissions (admin, super-admin)
* Changes to role assignments (log this as an event)
* List of service accounts/integration users with active credentials
This gives you a real-time view and a history to show auditors. The goal is to prove you can observe and control access, not just that it's correct on a single day.
Found any other sneaky permission pitfalls in tools like Expensify, Ramp, or Bill.com? Share your reconciliation workflows for user access.
zzz
Sleep is for the weak
This is dead on. That permission mapping script is crucial. We caught something similar in our cost management platform where the "Viewer" role had an undocumented "create custom reports" permission that also allowed exporting raw cost data.
You need to audit the service accounts/service principals too, not just human users. A forgotten integration with read-write to all billing data is a common hole.
And don't forget to check the permission inheritance model. Some tools let you assign a role at the organization level, then more granular roles at the project level, and the effective permissions can be additive in non-obvious ways.
cost optimization, not cost cutting
Spot on about the role definitions. We found the same thing, and switching back to vendor-standard roles was a game changer for us. Our custom "Finance Analyst" role had slowly picked up every new feature flag over two years.
The "data export" point is so real. We locked that down to a specific IAM role, and then funneled all export requests through a short Lambda that logs to our SIEM. It adds a step, but now we have an audit trail for every CSV that leaves the platform. 😅
Did you have to deal with any pushback from teams who'd gotten used to the extra permissions? That was our biggest hurdle.
cost first, then scale
Permission mapping is such a smart first step. At my last place, we never pulled that data before an audit, we just relied on the admin console view. It's easy to miss service accounts that way.
Do you run that mapping script on a schedule, or is it a manual step before each review?
Mapping the current state via API is the only reliable method. The admin console is often a curated view that hides deprecated service accounts or inherited permissions from nested groups.
A point to add to your mapping step: you need to correlate that permission data with your IdP's group membership. We found users who had been removed from our "Finance" Okta group six months prior still had active accounts in the expense tool because the de-provisioning sync had failed. The script should flag any user where the tool's permissions don't match their current IdP group membership.
We run the mapping weekly via a scheduled job that dumps to a secured S3 bucket. The diff from the previous run is what's useful - it shows you the permission drift as it happens, not just during audit prep. That's how you catch the "quiet morphing" in real time.
CPU cycles matter