Skip to main content

A competitor downloaded all their customer data. Here is how it happened.

IDOR vulnerability case study: sequential IDs in a SaaS API let a competitor download 12,000 customer records. How it was found, exploited and fixed in 4 hours.

Security Engineering
Updated 5 min read
Laptop screen showing code, illustrating an API data exposure
IDOR vulnerability · Food-tech SaaS · Summit case study
On this page
  1. How the client came to us
  2. What we found: an IDOR vulnerability in the customer API
  3. What an attacker could do with it
  4. Why IDOR vulnerabilities exist
  5. How we fixed it
    1. Part 1: Replace sequential IDs with UUIDs
    2. Part 2: Enforce authorisation at the API layer
    3. Part 3: Rate limiting and anomaly detection
  6. Timeline of the incident
  7. What the IDOR vulnerability cost
  8. Frequently asked questions

How the client came to us

The founder runs a B2B SaaS platform in the food service industry. His product helps restaurant chains and cloud kitchens manage supplier relationships, track orders and store customer data. He had about 12,000 business records on the platform: small restaurants, food courts and catering companies across two cities.

He did not come to us because he had found the vulnerability. He came because three of his biggest clients stopped responding, then sent politely worded emails saying they were “exploring other options”. When he dug into it, he found his main competitor had been calling his clients directly, with information they could only have got from inside his platform.

By the time he reached us, the data had already been taken. The breach was not a future risk. It was a past event with real commercial damage already done. This is the worst position to be in, and the most preventable.

What we found: an IDOR vulnerability in the customer API

Within the first 20 minutes of the API penetration test, we found it. It was not hidden or subtle. It was a single endpoint:

GET /api/v1/customers?rid=1284 HTTP/1.1
Host: [redacted].com
Authorization: Bearer [valid-user-token]

The rid parameter (record ID) was a sequential integer. Any logged-in user could substitute any number and retrieve any customer record in the database. There was no check that the requesting account had permission to view that record: no tenant isolation, no ownership validation, just a database lookup by ID.

12,000

Customer records exposed

~20 min

To discover the vulnerability

1 script

Needed to extract all data

4 hours

To fully remediate

What an attacker could do with it

The endpoint returned full customer records: name, contact, address, pricing tier and order history. A motivated attacker could:

  • Loop from rid=1 to rid=20000 and download every customer in under five minutes
  • Export the data to a spreadsheet and hand it to a sales team
  • Identify the largest accounts by order volume and target them first
  • Undercut pricing by knowing exactly what each customer was paying

This was not theoretical. From the timeline the founder reconstructed, the competitor enumerated roughly 12,000 records over three days in December 2025. They went slowly, a few hundred requests per hour, so no rate limit or alert fired.

Why IDOR vulnerabilities exist

An insecure direct object reference (IDOR) is listed first in the OWASP API Security Top 10 as API1:2023 Broken Object Level Authorization, and MITRE tracks it as CWE-639. It is the most common severe issue we find in web applications and APIs. Our OWASP Top 10 guide covers the wider family of access-control failures.

It happens because teams think about building features, not access control. When you write SELECT * FROM customers WHERE rid = ? and bind the user-supplied rid, it works perfectly in development. Every test passes, the endpoint returns data and the feature ships. Nobody asked: should this user be allowed to read record 1284?

The flaw is not a bug in the usual sense. The code does exactly what it was written to do. The problem is a missing requirement: the system was never designed to check that a user has the right to a specific record before returning it.

How we fixed it

The fix has three parts. The second is the real fix; the other two reduce the blast radius if anything slips through.

Part 1: Replace sequential IDs with UUIDs

Sequential integers are guessable by design. Random UUIDs (version 4, 122 random bits) mean an attacker cannot predict valid identifiers, so enumeration stops working even if an authorisation check is missed somewhere.

-- Add a random public identifier alongside the internal integer key (PostgreSQL 13+)
ALTER TABLE customers ADD COLUMN uuid UUID DEFAULT gen_random_uuid();

-- Backfill existing rows
UPDATE customers SET uuid = gen_random_uuid() WHERE uuid IS NULL;
ALTER TABLE customers ALTER COLUMN uuid SET NOT NULL;
CREATE UNIQUE INDEX customers_uuid_key ON customers (uuid);

-- Keep the integer id for joins. Never return it from the API.

Part 2: Enforce authorisation at the API layer

UUIDs alone are not enough. The server must check that the caller’s account owns, or is allowed to access, the requested record. This is the actual fix; UUIDs are defence in depth. The OWASP IDOR prevention cheat sheet describes the same pattern.

// BEFORE: no ownership check
app.get('/api/v1/customers', async (req, res) => {
  const customer = await db.query(
    'SELECT * FROM customers WHERE rid = ?',
    [req.query.rid]
  );
  res.json(customer);
});

// AFTER: ownership enforced on every request
app.get('/api/v1/customers', async (req, res) => {
  const customer = await db.query(
    'SELECT * FROM customers WHERE uuid = ? AND tenant_id = ?',
    [req.query.uuid, req.user.tenantId] // tenantId comes from the verified auth token
  );
  if (!customer) return res.status(404).json({ error: 'Not found' });
  res.json(customer);
});

The key change is tenant_id = ?, bound to the tenant in the caller’s verified token, which they cannot forge. If a user in tenant A asks for a record that belongs to tenant B, the query returns nothing and the API answers 404. Not 403: you do not want to confirm the record exists.

Part 3: Rate limiting and anomaly detection

This would not have prevented the original breach, but it shortens the window for any future attempt:

  • Rate limit API endpoints per authenticated user, for example 100 requests per minute per token
  • Alert on burst patterns, such as more than 50 sequential IDs requested within 30 seconds
  • Log every API access with user ID, IP address and resource identifier so you can investigate later

Timeline of the incident

  1. December 2025: data extracted

    The competitor made about 400 API calls an hour over three days, extracting all 12,000 records with contacts, order history and pricing.

  2. January 2026: clients start leaving

    Three major accounts stopped responding. The competitor's sales team was contacting them with specific, accurate pricing.

  3. February 2026: the founder investigates

    The pattern became clear: the competitor knew too much. The founder suspected a leak but could not find the source internally.

  4. March 2026: Summit engaged

    VAPT commissioned. Vulnerability found within 20 minutes, remediation completed in 4 hours, report issued the same day.

  5. After the fix: monitoring in place

    Rate limiting and anomaly alerts deployed. The founder now has an audit trail of all API access. No further incidents.

What the IDOR vulnerability cost

The founder lost three clients directly because of the breach and estimated the impact at several lakhs of rupees in annual recurring revenue. The Summit engagement, covering testing, fix guidance and the report, cost a fraction of that.

This is the arithmetic of security testing. A web application penetration test before launch would have caught this before a single customer record existed on the platform. Because the exposed records included personal contact details, the incident may also carry notification duties under India’s DPDP Act.

If you have not tested your API

Your platform very likely has access-control gaps. IDOR and broken object-level authorisation are the most common serious issues we find, in every industry, stack and team size. The question is whether you find them first. See what a finding looks like in our sample VAPT report.

Frequently asked questions

What is an IDOR vulnerability?

An insecure direct object reference (IDOR) happens when an application uses an identifier supplied by the user, such as a record ID in a URL or API parameter, to fetch data without checking that the user is allowed to access that record. Changing the ID returns someone else's data. In APIs, OWASP calls this Broken Object Level Authorization (BOLA).

Do UUIDs fix IDOR?

No. UUIDs make identifiers hard to guess, which slows enumeration, but they are not access control. IDs leak through logs, emails, shared links and other API responses. The fix is a server-side ownership or permission check on every request; UUIDs are defence in depth.

How do you test an API for IDOR?

Create two accounts in different tenants or roles, capture every request that references an object ID, then replay each request with the second account's token. Any response that returns or changes the first account's data is a finding. Automated scanners rarely catch this because they do not know which data belongs to whom.

Is IDOR a reportable data breach?

If personal data was accessed by someone who should not have it, it usually is. Under laws such as India's DPDP Act, GDPR and Saudi PDPL, unauthorised access to personal data can trigger notification duties. Take legal advice on your specific facts.

  • IDOR
  • OWASP API1
  • Access Control
  • SaaS Security
  • API Security

Summit Research Team

Summit's penetration testers write up findings from manual engagements. Every case study is anonymised and published with the client's permission.

Talk to a tester

Want us to look at your application?

Scoped quote within a day. Manual testing, verified findings, a fix-and-retest cycle and a report your auditors accept.