The Target
A global HR and employer-of-record platform that manages payroll, contracts, and employee data for companies across dozens of countries. The platform exposes a public API that customers use to integrate with their own systems.
The Discovery
The platform's API tokens follow a predictable format: ra_test_{company_id}_{secret}. Each token embeds the company UUID directly in the string. I noticed the server validates the secret portion against the database, but uses the company ID from the token string itself to set the tenant context.
That means if you take a valid token and swap the company ID prefix with another company's UUID, the server authenticates you as your own user but drops you into the target company's context.
The Attack
Token format analysis: I created two separate accounts on the platform. Each got an API token with the same format. The only difference between them was the company ID prefix and the secret suffix.
Prefix swap: I took Account B's secret and prepended Account A's company ID. Called the identity endpoint. The server authenticated B's user but returned A's company context. Full cross-tenant access confirmed in both directions.
Company ID enumeration: The platform had a public config endpoint that returned feature flags. Buried in the flag conditions were 14 company UUIDs, accessible without any authentication. No brute force needed.
Mass PII access: Using one of the leaked company IDs, I accessed a tenant with 4,552 employees. The API returned full employment records: names, emails, job titles, birthdates, nationalities, home addresses, salaries, marital status, and contract details.
National IDs via custom fields: The custom fields endpoint returned 55 fields per employee including national ID numbers, passport numbers, personal emails, work phones, payroll IDs, and government ID fields.
Write access confirmed: I created a webhook inside the target company's tenant, which returned 201 Created. I also tested offboarding endpoints. The forged token passed auth (returned a validation error for missing fields), while the real token correctly blocked cross-tenant access with "Company not found".
Impact
- Full cross-tenant read access to employee PII: national IDs, salaries, addresses, birthdates
- Write access confirmed: webhook creation and offboarding endpoints passed auth
- 5,073 employees accessed across 5 companies during testing
- Company IDs leaked via unauthenticated endpoint, making targeting trivial
Timeline
Reported and triaged within a day. Initially rated High because the team assumed company UUIDs required org membership to obtain. After pointing out the unauthenticated config endpoint leaking company IDs, severity was upgraded to Critical (9.6) with an additional bounty. Fix was deployed shortly after.
Takeaway
Never trust client-supplied identifiers for tenant context. If a token embeds a company ID, the server must resolve the tenant from the token's database record, not from the string itself. This is the same class of bug as IDOR but at the tenant isolation layer, which makes the blast radius much larger. Also worth checking: any unauthenticated endpoint that might leak internal identifiers. In this case, a feature flag config endpoint gave away 14 company UUIDs for free.