Why multi tenant reporting leaks in real life
Most breaches in shared reporting systems come from routine mistakes, not clever attackers.
- Someone runs the right report in the wrong tenant.
- A template queries the right tables but misses a tenant filter in one subquery.
- Support staff access a customer workspace to troubleshoot and see data they did not need.
If your default state is shared access and you rely on filters everywhere, one missed filter becomes a leak.
The goal is simple. Make isolation the default, then grant access only when you must.
The core threat: accidental cross tenant access
Shared architectures often put many tenants behind one platform, one template library, and sometimes one database. You then try to prevent leaks with rules like tenant_id filters or row level security.
This fails for predictable reasons.
- Templates evolve. A new join or nested query slips through without a tenant condition.
- Different teams use different query patterns, and enforcement becomes inconsistent.
- Views and ORM layers do not cover every edge case.
- Someone copies a template from Tenant A and runs it for Tenant B.
Even when row filtering works, templates can still leak. The template structure can reveal confidential fields, business logic, or even that a data set exists.
If you build one system and then add tenant restrictions, every component must be perfect forever. That is not realistic.
Workspace isolation: start with separation
A workspace is a full reporting environment for one tenant or one business unit.
Each workspace holds its own:
- Templates
- Data sources and database connections
- Themes and branding
- Users and roles
- Parameters and configuration
Users inside Workspace A do not see Workspace B. They do not see its templates, its data sources, or its users. The safest state is that other tenants do not exist.
What you gain from this design:
- A bad template stays contained. It can only affect its own workspace.
- A compromised account has a smaller blast radius.
- Operational mistakes stay local. A support session in one workspace cannot accidentally change another workspace.
For external customers, the usual model is one workspace per client.
For internal reporting across departments, use one workspace per business unit when data separation matters.
Admin: cross workspace operations with guardrails
Someone still needs to manage the platform. That user needs visibility across workspaces to:
- Create new workspaces
- Configure system level settings
- Support troubleshooting when tenants hit issues
Per workspace roles: RBAC that matches real jobs
Isolation between tenants is not enough. Inside one workspace, you still need least privilege.
Role based access control lets you give users only what they need.
Typical permission areas:
- Reports, view and run
- Templates, create and edit
- Themes, manage branding
- Data sources, view and configure connections
- Parameters, define and maintain inputs
- Workspace settings, users, roles, and policies
A simple and effective permission scale:
- No access, hide the section
- Read, view only
- Full, create, edit, delete
Example roles that work in practice:
Report operator
- Full access to Reports
- Read access to Parameters
- No access to Templates, Themes, Data sources, Workspace settings
Designer
- Full access to Templates and Themes
- Read access to Data sources so they can see available data
- No access to users, roles, or connection edits
Workspace admin
- Full access inside the workspace
- No visibility into other workspaces
Assign roles per workspace. The same person can be an admin in one workspace and a viewer in another.
Resource level access: restrict specific reports and data sources
Sometimes one workspace serves a whole department, but not every report is for everyone.
Use resource level controls to restrict:
- Specific reports, for example HR or payroll
- Specific data sources, for example a database with personal data
This adds another layer of least privilege.
It also helps you avoid creating too many workspaces. Keep the workspace as the tenant boundary, then use resource controls for sensitive items inside the tenant.
Data isolation: database per client beats tenant filters
Workspace isolation means little if every workspace queries the same database with shared tables.
The safest approach is database per client, or at least separate schemas with separate credentials.
You then configure each workspace with connections that only reach that tenant data.
Why this is safer than tenant_id filtering:
- A missing WHERE clause cannot leak other tenants because the data is not reachable.
- Credentials enforce boundaries at the infrastructure level, not in template logic.
- A template designer cannot query another tenant database because the connection does not exist in the workspace.
This removes an entire class of errors, including row filter gaps in nested queries.
Limit query scope with preconfigured data sources
Not every template author should write arbitrary SQL.
Preconfigured data sources let you publish named datasets like:
- monthly_revenue
- customer_summary
- inventory_levels
Designers select the dataset by name instead of writing queries.
This helps in three ways.
- You reduce exposure. Templates cannot pull tables you did not publish.
- You centralize maintenance. One change updates all templates that use the dataset.
- You standardize business logic, so teams do not implement the same calculation five different ways.
For highly sensitive tables, publish only aggregated views or curated datasets.
Authentication: use SSO so accounts stay controlled
Separate reporting passwords create weak habits, reuse, and shared accounts.
Single sign on via OpenID Connect keeps identity control in your main provider, such as Google Workspace, Microsoft Entra ID, Okta, or Keycloak.
What you gain:
- Central password policies and MFA
- Fast offboarding, disable the user once and access ends everywhere
- Cleaner audit trails tied to real identities
Per workspace secrets: keep credentials contained
A multi tenant platform accumulates credentials fast:
- Database passwords
- API keys
- SMTP credentials
- Storage access keys
Store secrets per workspace and encrypt them.
Important practice: mask secrets in the UI. Let admins replace a password, but do not show the existing value. This prevents accidental exposure during screen sharing and reduces copying secrets into tickets.
A practical checklist
Workspace setup
- Create one workspace per tenant or per unit that requires isolation
- Confirm users cannot see other workspaces
- Use clear workspace naming and ownership
Admin control
- Limit admin to platform operators
- Log access and changes
- Review admin access quarterly
Roles and users
- Define roles that map to real jobs
- Apply least privilege
- Review user lists and role assignments regularly
Data sources
- Prefer database per client or separate schema with separate credentials
- Do not rely on tenant_id filters as your primary boundary
- Use curated data sources for most template authors
Resource controls
- Restrict sensitive reports
- Restrict sensitive data sources
- Recheck restrictions when teams change
Authentication
- Use SSO
- Block shared accounts
- Enforce MFA at the identity provider
Secrets and hygiene
- Scope secrets per workspace
- Rotate credentials on a schedule
- Keep deletion and access logs where compliance needs them
How to roll this out without pain
Start with one tenant.
- Create a workspace, add a workspace admin, and set up roles.
- Configure tenant specific database connections.
- Publish curated data sources for designers.
- Add a few test users with different roles and try to break the boundaries.
- Repeat the pattern for the next tenants.
If you already run a shared database model, keep it running while you migrate tenant by tenant to separate databases or schemas with dedicated credentials. You do not need a big bang cutover.
The key principle stays the same. Isolation first, permissions second, and a clean audit trail for the small set of people who can cross boundaries.