← Back to list

How Unauthenticated Queries Exposed User PII and Privileged Accounts

Unauthenticated API queries exposed sensitive user data and enabled enumeration of the application’s entire observed userbase.

Sudheer · 2026-08-13 07:26 · 0 claps · 5.8 min read
#api-security #bug-bounty #web-security-testing #authentication #authorization
Open on Medium ↗
Wiki topics: LIT · Literature & Writing

How Unauthenticated Queries Exposed User PII and Privileged Accounts

Unauthenticated API queries exposed sensitive user data and enabled enumeration of the application’s entire observed userbase.

By Sudheer · Bug bounty writeup · August 2026

While assessing a manga-reading web application built on Convex, I identified three issues:

  1. An exposed third-party geolocation API key in the client-side JavaScript bundle — Low.

2. An unauthenticated query exposing sensitive user information through users:getUserByUsernameHigh

3. An unauthenticated user-enumeration endpoint through users:searchPeopleInformational

The main finding was the unauthenticated exposure of sensitive user information. A username lookup returned information that was not intended to be part of a public profile, including email addresses, IP/geolocation information, subscription-related data, and internal account metadata.

The user-search endpoint provided an unauthenticated way to discover usernames and identify privileged accounts. I tested single-character searches from **a through `z` and identified 14 unique accounts**, matching the approximately 14 accounts observed in the application.

The two query behaviors can therefore be chained: enumerate accounts through searchPeople, then retrieve sensitive information for individual accounts through getUserByUsername.

The Stack

  • Frontend: client-side SPA
  • Backend: Convex
  • Client bundle: contained backend function names and implementation details useful for reconnaissance

The JavaScript bundle exposed enough information to identify backend functions such as:

users:getUserByUsername
users:searchPeople

These became the primary targets for authorization and data-exposure testing.

This was an application-level authorization/data-exposure issue involving functions deployed on Convex, rather than a vulnerability in Convex itself.

Finding 1: Exposed Third-Party Geolocation API Key

Severity: Low — 3.2

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L

A third-party API key for ipgeolocation.io was present in the client-side JavaScript bundle.

The application made a browser-side request resembling:

https://api.ipgeolocation.io/ipgeo?apiKey=REDACTED

Because the key was delivered to the browser, it could be obtained by any visitor and therefore could not be treated as a secret.

Testing

I confirmed that the exposed key could be used outside the application’s normal UI. I also tested whether the API accepted an attacker-controlled ip parameter.

Why this matters

Depending on the provider’s restrictions and billing model, an exposed API key can allow an attacker to:

  • consume the application’s API quota;
  • consume provider-side rate limits;
  • potentially generate usage costs;
  • abuse functionality associated with the credential.

Remediation

  • Rotate the exposed API key.
  • Move the geolocation request server-side where practical.
  • Apply provider-side restrictions where supported.
  • Apply quota and usage monitoring.

Finding 2: Unauthenticated Disclosure of Sensitive User Information

Severity: High — 7.5

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

The users:getUserByUsername query was callable without authentication.

A direct request to the Convex query endpoint with a username returned information from the corresponding user document without requiring:

  • an account;
  • an Authorization header;
  • an authenticated session cookie.

The request structure was:

POST /api/query
{
  "path": "users:getUserByUsername",
  "format": "json",
  "args": {
    "username": "<target username>"
  }
}

Sensitive information exposed

During testing, the response included sensitive fields such as:

email
ipAddress
location
  ip
  city
  country
  isp
subscriptionStatus
subscriptionExpiresAt
emailVerificationTime
role
internal identifiers
creation-related information

The exact fields varied between user records.

These fields go beyond what would normally be required for a public profile.

Why this is a security issue

The function appears to support public username/profile lookup, but the response exposes private account information.

An unauthenticated attacker who knows a username can therefore retrieve sensitive information without possessing an account or authentication credentials.

The exposed information can include:

email addresses, IP addresses, approximate geographic information, ISP information, subscription status, account metadata.

Impact

This creates a direct confidentiality and privacy exposure.

Potential consequences include:

  • targeted phishing
  • social engineering
  • correlation of usernames with email addresses
  • correlation of accounts with IP/geolocation information
  • targeting of privileged accounts.

The application had approximately 14 user accounts during testing. The enumeration process described below identified all 14 accounts observed in the application, and the sensitive-data query could then be used against individual usernames.

I did not publish or retain unnecessary personal information.

Remediation

The server should explicitly return only fields intended for public consumption.

For example:

username, displayName, bio, avatar, public social links

Sensitive fields such as:

email, IP address, location, subscription information, internal identifiers should only be returned after appropriate authorization.

The safest implementation is to construct a dedicated public response rather than returning the underlying user document.

Finding 3: Unauthenticated User Enumeration

Severity: Informational

The users:searchPeople query was callable without authentication.

The request accepts a user-controlled searchQuery and limit:

{
  "path": "users:searchPeople",
  "format": "json",
  "args": {
    "searchQuery": "a",
    "limit": 32
  }
}

The response included fields such as:

_id, username, displayName, bio, image, role

The returned roles included:

user, creator, admin, founder

Demonstrated Enumeration

I tested single-character searches from athroughz.

Across the complete az search set, the endpoint returned 14 unique accounts.

This matched the approximately 14 accounts observed in the application, including the two accounts I created for testing.

The searches also returned overlapping results for different characters, demonstrating that the endpoint could be used as an unauthenticated user-enumeration mechanism.

The enumeration identified accounts with elevated roles, including:

  • founder
  • administrator
  • creator

Why I classify this as Informational

The role field is already displayed in the application's UI, so its presence in the API response is not by itself a private-information disclosure.

The endpoint also exposes internal _id values. However, I did not demonstrate that these IDs could be used to access additional unauthorized information or perform unauthorized actions.

Therefore, I do not consider the _id exposure a strong standalone vulnerability.

The security relevance of this endpoint is primarily that it provides a convenient way to discover usernames, including privileged accounts, which can then be supplied to getUserByUsername.

Remediation

If the endpoint is intended to remain publicly accessible:

  • return only genuinely public profile information;
  • avoid exposing internal database identifiers unless required;
  • consider whether roles need to be returned by the API;
  • apply reasonable rate limiting to automated enumeration.

How the Findings Can Be Chained

The two query behaviors can be combined without authentication.

Step 1 — Enumerate users

An attacker queries:

users:searchPeople

with different search terms.

The az test identified 14 unique accounts in the application.

Step 2 — Identify a target

The attacker selects a discovered username, including potentially privileged accounts.

Step 3 — Retrieve sensitive information

The username is supplied to:

users:getUserByUsername

which returns sensitive fields associated with the account.

The resulting workflow is:

Unauthenticated search
        ↓
Enumerate usernames
        ↓
Identify privileged accounts
        ↓
Query individual usernames
        ↓
Retrieve sensitive user information

In my testing, this allowed the 14 accounts observed in the application to be identified through the public search function and then individually queried for additional information.

I did not attempt to directly query the underlying database or perform destructive/excessive enumeration.

What I Tested Separately

I also investigated several related attack surfaces.

Privileged-function authorization

I went one step further as the application leaked sensitive information. So, I tested role-assignment and founder-analytics functionality using a lower-privileged authentication context.

The server rejected the unauthorized requests.

Result: no privilege escalation demonstrated.

Remediation Summary

1.Fix getUserbyUsername: Return only public profile fields to unauthenticated callers. Do not return the underlying user document directly.

2. Review searchPeople: Limit the response to fields genuinely required by the public search functionality. Consider removing internal database IDs and authorization roles if they are not required.

3. Rotate the exposed API Key: Treat the existing third-party credential as compromised and rotate it. Apply provider-side restrictions and quota controls.

4. Review stored geolocation information: Minimize retention of IP/geolocation data and ensure it is only accessible to appropriately authorized functionality.

5. Add abuse controls: Rate-limit public profile and search queries and monitor unusual enumeration patterns.

Takeaways

The client bundle can provide a useful map of an application’s backend attack surface.

In this case, it exposed function names and enough implementation information to identify:

users:searchPeople
users:getUserByUsername

The important finding was not simply that these functions were callable from the browser.

The problem was what the functions returned without authentication.

searchPeople returned relatively limited profile information and was classified as Informational because no concrete security consequence from the exposed _id was demonstrated.

In contrast, getUserByUsername returned sensitive account information that should not have been available to an unauthenticated requester.

The two behaviors can nevertheless be chained: the search function provides a practical way to discover usernames, while the profile query exposes sensitive information associated with those usernames.

The key lesson is:

A function being callable from the client does not mean every field returned by that function should be accessible to the client.

Similarly, credentials embedded in frontend JavaScript should be assumed to be obtainable by any visitor and should not be treated as secrets.

Disclosure

This testing was conducted as part of independent security research. The application does not have a public disclosure program, and the findings were submitted privately to the respective team.

The severity ratings in this write-up are my own assessment based on the observed impact, using CVSS 3.1 where applicable. They may differ from the affected program’s internal severity classification.


메타데이터
post_id
cb89efff14d8
slug
how-unauthenticated-queries-exposed-user-pii-and-privileged-accounts-cb89efff14d8
url
https://medium.com/@mr-sudheer/how-unauthenticated-queries-exposed-user-pii-and-privileged-accounts-cb89efff14d8
canonical_url
https://medium.com/@mr-sudheer/how-unauthenticated-queries-exposed-user-pii-and-privileged-accounts-cb89efff14d8
author_url
https://medium.com/@mr-sudheer
status
ok
fetched_at
2026-08-18 00:15:01