September 20, 2026
How I Found an Undocumented GraphQL Endpoint Leaking Home Addresses of French Company Owners (EUR…
The documented unauthenticated endpoint had 1 query. The undocumented one had 7 queries and 7 mutations. One of those queries returned…

By anshh.bohara
3 min read
The documented unauthenticated endpoint had 1 query. The undocumented one had 7 queries and 7 mutations. One of those queries returned residential addresses of company beneficial owners for any French company. No auth.
The Target
The target was a European embedded banking-as-a-service platform. They provide APIs for companies to integrate banking features — accounts, cards, payments, KYC. Their bug bounty program is on a European vulnerability disclosure platform.
The dashboard was a React SPA that connects to a backend GraphQL API.
The Configuration Leak
The dashboard serves an env.js file meant to provide client-side configuration. But it contained more than just public config:
-
Internal GraphQL endpoints for federation, partner access, and exposed-internal services
-
Internal Kubernetes service names revealing the production cluster layout
-
Third-party API keys for payment processing, maps, identity management, and messaging services
The internal URLs told me two things: the platform uses a "live-" prefix for production endpoints, and there are multiple GraphQL surfaces beyond the documented one.
Finding the Undocumented Endpoint
The platform documents a public unauthenticated GraphQL endpoint with a single query and no mutations. Given the naming pattern from env.js (live-partner, live-exposed-internal), I guessed live-unauthenticated:
POST https://[REDACTED-API]/live-unauthenticated/graphql
{"query": "{ __schema { queryType { fields { name } } mutationType { fields { name } } } }"}POST https://[REDACTED-API]/live-unauthenticated/graphql
{"query": "{ __schema { queryType { fields { name } } mutationType { fields { name } } } }"}It existed. And it had 7 queries and 7 mutations — 7x the surface of the documented endpoint.
The UBO Query
Among the 7 queries was one for looking up Ultimate Beneficial Owner data by company registration number and country. UBO stands for Ultimate Beneficial Owner — the individuals who ultimately own or control a company. For a banking platform, this is sensitive data used in KYC/AML processes.
{
companyUboByCompanyRefAndHeadquarterCountry(
input: {
headquarterCountry: "FRA",
companyRef: "[SIREN_NUMBER]"
}
) {
individualUltimateBeneficialOwners {
firstName
lastName
birthDate
birthCity
birthCountryCode
gender
residencyAddress {
addressLine1
city
postalCode
country
}
}
}
}{
companyUboByCompanyRefAndHeadquarterCountry(
input: {
headquarterCountry: "FRA",
companyRef: "[SIREN_NUMBER]"
}
) {
individualUltimateBeneficialOwners {
firstName
lastName
birthDate
birthCity
birthCountryCode
gender
residencyAddress {
addressLine1
city
postalCode
country
}
}
}
}The companyRef is a French SIREN number (company registration number). I tested with the platform's own registration number. The response returned real data: co-founders' full names, birth dates, birth cities, and their residential home addresses — street, city, postal code.
The Scale Problem
French SIREN numbers are sequential 9-digit numbers. There are approximately 5 million active French companies registered. With no rate limiting on the endpoint, an attacker could enumerate them all. I tested 10 sequential requests and all returned 200. No throttling.
An attacker could build a database of every French company's beneficial owners with their home addresses.
The Additional Mutations
The undocumented endpoint wasn't just about reading UBO data. The 7 mutations included the ability to delete KYC supporting documents without auth and generate S3 upload URLs without auth.
I tested the document deletion mutation with a non-existent document ID. It didn't return a "Forbidden" rejection. It processed the request and only failed because the document wasn't found. With a valid document ID, this would delete a production KYC document without authentication.
A card PIN information query on production also didn't return a forbidden error — it returned an error from the internal card-management-system, meaning the request was being forwarded to the internal service and processed.
Why Home Addresses Matter
While UBO names and some company ownership data may be available through public registries, residential addresses have special protections. Under GDPR, exposing personal data without a lawful basis is a violation. Under AMLD5 (Anti-Money Laundering Directive 5), EU member states have actually pulled back public access to UBO registers following a 2022 EU Court of Justice ruling.
For a banking platform, beneficial owners can include wealthy individuals, executives, and political figures. Exposing their home addresses creates a stalking and physical safety risk that goes beyond typical data exposure.
Negative Controls
1. Naming pattern consistency. The "live-" prefix matched the production endpoints found in env.js.
2. Real data returned. The UBO data for the platform's own company matched publicly verifiable company registration information, with the addition of private data (home addresses) that isn't in public registries.
3. Documented endpoint comparison. The documented unauthenticated endpoint has 1 query and 0 mutations. The undocumented one has 7 queries and 7 mutations — this isn't a mirror, it's a superset.
4. Internal service integration. The card PIN query forwarded requests to an internal card management system that returned real errors, not mock responses.
Outcome
Reported through a European vulnerability disclosure platform. Awarded EUR 1,000 bounty.
Lessons
1. Configuration files map the hidden surface. env.js, config.js, firebase init files — these are loaded by the browser and are public. Developers dump internal URLs, API keys, and service names into them because the client needs some of that information. But they often include more than the client needs. Read every config file first. It's the fastest recon.
2. Derive the naming pattern. When you find one internal URL like live-partner/graphql, you now know the naming scheme. Try every combination: live-unauthenticated, live-admin, live-internal, sandbox-admin. The undocumented endpoint that nobody remembers is the one with the weakest access controls.
3. Compare surface areas. If the documented endpoint has 1 query, and the undocumented one has 7 queries and 7 mutations, the extra 13 operations are the attack surface. Those are the operations that weren't meant to be public, and they're the ones most likely to have missing access controls.
4. Mutations on unauthenticated endpoints are always worth testing. A query that returns data is a confidentiality issue. A mutation that processes requests without auth is an integrity issue. A document deletion mutation accepting requests on an unauthenticated endpoint is a much bigger problem than reading UBO data, even if the UBO data is what gets the headline.
5. Context elevates impact. A PII leak on a social media platform is bad. A PII leak on a banking platform that exposes home addresses of company owners is a physical safety risk. The same technical bug has different impact depending on what kind of data it exposes and who the affected users are. Make sure the report reflects the context.