Remediation Guide 2 min read

Fixing IDOR without breaking your API

Insecure direct object references are the most common high-severity finding we report. The usual fix — swapping IDs for UUIDs — does not actually fix it.

Insecure direct object reference is the finding we report most often, across almost every kind of application. It is also the one most frequently closed incorrectly, because the intuitive fix addresses discoverability rather than authorisation.

What the finding actually says

A request identifies a resource, and the server returns it without checking whether the authenticated caller is entitled to that specific resource. Authentication passes. Authorisation is never evaluated at the object level.

The fix that does not work

Replacing sequential integers with UUIDs makes identifiers hard to guess. It does not make them hard to obtain. Identifiers leak through shared links, exports, logs, referrer headers, support tickets and any endpoint that returns a list. Once an identifier is known, an unauthorised request still succeeds — you have raised the effort of discovery and left the vulnerability in place.

The fix that works

  1. Scope the query, do not check after it. Load the resource with the caller’s identity in the WHERE clause rather than fetching by ID and then comparing owners. A record that is not the caller’s should not be retrievable in the first place.
  2. Put the check below the controller. Object-level authorisation implemented per handler will be missed on the next endpoint. Enforce it in the data access layer, a policy object, or query scopes applied by default.
  3. Default deny on new endpoints. New routes should fail closed until a policy is attached. This is what stops the finding from recurring in six months.
  4. Test it in CI. One test per resource type: authenticate as user A, request user B’s object, assert 404. This is cheap and it is exactly what we will re-test.

Return 404, not 403

A 403 confirms that the resource exists. For anything where existence is itself sensitive — private repositories, patient records, draft filings — return 404 for both “does not exist” and “not yours”. Keep 403 for cases where the caller legitimately knows the resource exists but lacks a permission.

How we re-test

We take the original request, replay it with a second account, and confirm the response. We also probe adjacent endpoints — list, export, search, webhook callbacks — because a fix applied at one handler is the most common cause of a re-test failure.

Tell us what triggered the search. We will scope to that.

We reply to every assessment request within one business day.