The access-control flaw that would have let one client see another's payroll
A pre-launch security audit found a broken-access-control flaw that let a crafted request pull another tenant's payroll records, rated and fixed before the product ever reached a customer.
Who, what, and how long
- Industry
- Professional Services
- Duration
- 2 weeks
- Cooperation model
- Fixed price
Client name withheld under NDA. Engagement details are shown to the extent our agreement permits.
What went wrong, and when
The application had never been formally assessed, and launching with real payroll and personal data across multiple tenant companies raised the stakes of any undiscovered access-control gap considerably.
The application authenticated well and authorized incompletely. Endpoints checked that a request carried a valid session, and in one case didn't check that the object it named belonged to that session's tenant. So a crafted request with a valid login and another company's record ID returned that company's payroll. Per-endpoint testing doesn't find this, because every endpoint passes its own test.
We reviewed the application for broken access control, examined server configuration and exposed services, audited the dependency tree for known CVEs, and reviewed the permission model. That surfaced a critical flaw where a crafted API request could return another tenant's payroll records. We rated it as the top finding and delivered a prioritized remediation plan the team closed before launch.
Phase by phase
Phase 1: Review logic
Across tenant boundaries
Reviewed application logic and access control specifically across tenant boundaries, beyond per-endpoint checks.
- Access-control review
- Tenant isolation findings
Phase 2: Assess surface
Config and services
Assessed server configuration and exposed services for hardening gaps.
- Configuration review
- Exposure map
Phase 3: Audit dependencies
Known-vulnerable components
Audited the dependency tree for known CVEs and reachable vulnerable paths.
- Dependency report
- Reachability analysis
Phase 4: Rate and plan
In order of severity
Rated every finding by severity and delivered a prioritized remediation plan the team closed before launch.
- Severity-rated findings
- Remediation plan
- Retest report
Effective permissions as actually resolved: six roles against eleven objects before and after the patch, and the one handler that never applied the tenant scope its policy declared.
The numbers, before and after
1 (cross-tenant data exposure)
Critical findings surfaced
14
Total findings rated and prioritized
Yes, 4 days before go-live
Critical finding closed before launch
One critical and fourteen total are counts from the assessment. The pre-launch closure is verified by retest, not by the team's report of the fix. Severity ratings follow OWASP ASVS with a written impact statement per finding, so the ordering reflects consequence, not category.
Client name withheld under NDA. Figures are approximate, drawn from the engagement’s own reporting.
The engagement
The team was two weeks from launching a multi-tenant payroll platform and wanted a security assessment before onboarding its first paying customers.
A multi-tenant payroll platform two weeks from onboarding its first paying customers, never formally assessed. The stakes are what made the timing worth paying for: payroll and personal data for multiple client companies in one database, where a boundary failure isn't a bug. It's a notifiable incident affecting every tenant at once.
Security Audit & Vulnerability Assessment
How it was handled
- 01
Reviewed application logic and access control across tenant boundaries
Every endpoint was replayed with a valid session from one tenant and identifiers belonging to another: the axis per-endpoint testing doesn't cover.
- 02
Assessed server configuration and exposed services for hardening gaps
Configuration review ran alongside the code review, so a hardening gap and the logic it exposes could be reported as one finding.
- 03
Audited the dependency tree for known-vulnerable components
The dependency tree was resolved to its transitive leaves and filtered to what's actually reachable, so an unreachable build-time CVE didn't bury the real findings.
- 04
Rated every finding by severity and delivered a prioritized remediation plan
Every finding carries a reproduction, a specific fix and a written impact statement, sequenced into must-close-before-launch and after.
Cross-tenant review
Access control examined specifically across tenant boundaries, where the flaw was.
Most access-control testing checks whether a user can reach an admin page. This looked at the axis that actually mattered for a multi-tenant payroll product: whether one tenant could reach another's data. Every endpoint was exercised with a valid session from tenant A and identifiers belonging to tenant B, which is how the critical finding surfaced: an object reference the API authenticated but never scoped.
- Every endpoint replayed with tenant A's session and tenant B's IDs
- Found an object reference authenticated but never scoped
- Cross-tenant access tested as its own axis
The replay trail in time order: every endpoint called with tenant A's session and tenant B's identifiers, the crafted request at 09:52:08 that returned another tenant's payroll run, and the 403 at retest that closed it.
Nine advisories matched across 1,284 packages, filtered to the two that are reachable and sequenced into three patch batches, beside the twelve listeners reachable from the public subnet and which of them were closed at the edge.
Dependency and config audit
Known-vulnerable components and hardening gaps assessed alongside application logic.
The dependency tree was resolved to its transitive leaves and matched against CVE feeds, then filtered to what's actually reachable in this application. An unreachable vulnerability in a build-time package isn't a launch blocker, and reporting it as one buries the findings that are. Server configuration was reviewed alongside: exposed services, TLS settings, error verbosity and default credentials.
- Transitive dependency tree resolved and matched to CVE feeds
- Filtered to reachable code paths, so the report stays actionable
- Exposed services, TLS, error verbosity and defaults reviewed
Severity-rated remediation
The remediation plan: every finding with its fix, owner and the day it closed, split into must-close-before-launch and after, with the critical one closed four days before go-live.
Every finding rated and sequenced, so the critical one was unambiguous.
Findings were rated against OWASP ASVS with an explicit impact statement, never just a severity word, and sequenced so the team knew what had to close before launch and what could follow. The cross-tenant flaw was rated alone at the top, with everything else ordered beneath it. Each entry carried a reproduction and a specific fix, not a category and a link.
- Rated against OWASP ASVS with a written impact statement
- Sequenced into must-close-before-launch and after
- Every finding carries a reproduction and a specific fix
Working inside their operation
A cross-functional team of 3 worked on a fixed price basis over 2 weeks, covering Application review, Dependency & permission audit, Remediation plan. We ran a weekly demo and kept a shared board they could check at any time. Their team took over day-to-day operation before the engagement ended, with handover built into the last phase.
Two weeks, fixed price, ending in a rated and sequenced report. Cross-tenant access was tested as its own axis, separate from role testing, and that's the decision that surfaced the critical finding. The remediation plan was ordered by what had to close before launch, and the team closed the critical one four days ahead of go-live.
What changed in the runbook
- 01
The critical finding was in the boundary between tenants, the place per-endpoint testing doesn't look.
Each endpoint passed its own authorization test. The flaw lived in what none of them checked, which is why the boundary needs testing as a boundary.
- 02
Two weeks before launch is the cheapest this class of finding will ever be to fix.
The same finding after launch is a notifiable incident across every tenant. Two weeks earlier, it was a one-line change and a retest.
- 03
Rating the findings mattered: an unranked list of twenty invites the team to start with the easiest.
An unranked list of fourteen gets worked from the top by whatever is quickest. Rating them is what put the cross-tenant flaw first instead of eleventh.
A valid login and another company’s run ID
The crafted request from the replay trail, followed through the handler before the fix and after it: what it carried, what was checked, and what came back. Switch builds or steps, or use the arrow keys once a tab is focused.
A real login from one company and an identifier copied from another. Nothing about the request is malformed, and it is byte-for-byte the same as the one the replay sent. Only the handler changed.
- RequestSession tn_4F1A · id from tn_9C07
- Tenant checkNot performed
- Response200 · Ostwick Dental Group's payroll
From a replayed request to a finding closed by retest
Per-endpoint testing passes an endpoint that authenticates and never scopes. The assessment ran the boundary as a boundary, and carried every finding through to a verified close.
- 01 · TriggerCross-tenant replayEvery endpoint called with a valid session from tenant A and identifiers belonging to tenant B, tested as its own axis, separate from role testing.
- 02 · CollectCode, config and dependency treeConfiguration reviewed alongside the code, and the dependency tree resolved to its transitive leaves and matched to CVE feeds.
- 03 · RateOWASP ASVS + impact statementDependencies filtered to reachable code paths; every finding rated with a written impact statement, never just a severity word.
- 04 · RecordSequenced remediation planEvery entry carries a reproduction and a specific fix, ordered into must-close-before-launch and after.
- 05 · DeliverRetest reportClosure verified by retest, not by the team's report of the fix. The critical one closed four days before go-live.
So one client never sees another’s payroll
Tenant isolation, rated findings & verified closure
The tenant boundary tested as a boundary
Every endpoint was replayed with one tenant's valid session and another tenant's identifiers. Each endpoint had passed its own authorization test; this is the axis that found the object reference authenticated but never scoped.
The critical finding can't sit eleventh
All fourteen findings were rated against OWASP ASVS with a written impact statement and sequenced into must-close-before-launch and after, so the cross-tenant flaw stood alone at the top.
Closed means retested
Pre-launch closure was verified by retest, not by the team's report of the fix. The critical finding was closed four days before go-live, before the first paying customer was onboarded.
Could one of your customers read another’s data with a request nobody has tried? Scope your build in 3 minutes.
Scope your buildNearby engagements
AI & AutomationA private legal assistant grounded in verified precedents
A private knowledge assistant that searches internal case files and precedents, providing cited answers legal teams can verify in seconds.
Legal & Law Firms · 14 weeks
Product DesignAn onboarding flow that guides trial users to value
A redesigned SaaS trial onboarding experience with progressive checklists, sample data, and inline guidance that turns signups into active subscribers.
Professional Services · 10 weeks
Product DesignA design system that brought speed and consistency to 4 product teams
A token-based design system in Figma and React that eliminated component duplication across 4 product squads and cut the time from design handoff to merged frontend.
Professional Services · 14 weeks
Let's talk
Running a large platform, shaping a first MVP, or getting a product ready for a funding round? Tell us where you are. We'll shape the process around it, and stay with you after launch.














