Back to blog
APItechnicalHR Tech

User enumeration: how your API reveals who your customers are

Published on 2026-07-016 min readCleanIssue

A leak that doesn't look like a leak

No data is stolen, no database is breached. Yet a competitor or attacker can learn: which companies use your SaaS, how many employees they manage, and which work email addresses exist at each one. That's enumeration, and it flies under the radar because it only exploits "normal" behaviors of your API.

For an HR SaaS it's doubly sensitive: your customer list is your commercial asset, and a client's employee list is the perfect starting point for a targeted phishing campaign ("your payslip is available").

The 4 classic vectors

Differentiated error messages

Login: "incorrect password" vs "unknown user". Sign-up: "this address is already in use". Inviting a colleague: "this user belongs to another organization". Every differentiated message is an oracle confirming or denying that an account exists.

Sequential IDs

GET /api/companies/1847 returns 403, GET /api/companies/1848 returns 404. The 403 says "exists but not yours", the 404 says "doesn't exist". Iterate, and you've counted your customers — and their month-by-month growth if IDs are chronological. Use UUIDs, and return 404 in both cases.

Response timing

Even with identical messages, if password verification (bcrypt, ~100 ms) only happens when the user exists, the latency difference is enough. Perform a dummy hash when the account doesn't exist.

The forgotten side endpoints

Login is often protected, but not: the password reset request, the email-availability check at sign-up, adding a member to a team, the user search field. The attacker uses the chattiest link in the chain.

Rate limiting: necessary but not sufficient

Without rate limiting, enumeration runs at thousands of requests per minute. But IP-based limits alone no longer cut it: attackers rotate through thousands of rented residential IPs. Combine:

  • limits per IP and per targeted account/email
  • progressive slowdown (backoff) rather than hard blocking — harder for the attacker to detect
  • CAPTCHA triggered on anomalous behavior, not systematically
  • an internal alert when the global failure rate climbs — that's the signal of an ongoing campaign
  • Checklist

  • [ ] Identical messages and status codes whether the account exists or not (login, reset, sign-up, invitations)
  • [ ] Constant response time (dummy hash for non-existent accounts)
  • [ ] UUIDs rather than sequential IDs on exposed resources
  • [ ] 404 (not 403) for other tenants' resources
  • [ ] Rate limiting per IP and per target, with progressive backoff
  • [ ] Monitoring of authentication failure rates
  • Our API audit systematically tests these vectors in real-world conditions — they're often the first thing automated scanners miss.

    Go further

    Related articles

    Three adjacent analyses to keep exploring the same attack surface.

    Sources

    Written by CleanIssue
    Reviewed on 2026-07-01

    Related services

    If this topic maps to a real risk in your stack, these are the most relevant CleanIssue audits.

    Need an external review of your HR SaaS?

    Share your product, stack, and client context. We will come back with the right review scope.

    Discuss your audit