Workflow

OpenAPI IDOR & BOLA Scanner

Read a target's OpenAPI spec, enumerate every GET that takes an object id, and probe each with altered ids and stripped auth.

100%

Notes on this workflow

Results

A Trickest PDF report containing:

Coverage summary — API paths, GET operations, object-id endpoints tested

Findings ranked by severity — CROSS_USER_IDOR, SHARED_OBJECT, UNAUTH_ACCESS flagged HIGH

Tested endpoints — every operation and the ids probed

Methodology — spec-driven, two-user, GET-only

Machine-readable JSONL also emitted to results.jsonl for export / SIEM pipelines.

Target Spec

Point the workflow at an OpenAPI / Swagger document — no manual endpoint list needed.

Two options:

1. URL — paste the spec URL into spec-url (default: Petstore demo). Change it to your target's Swagger/OpenAPI JSON.

2. Storage upload — upload your own .json spec to Trickest Storage, then wire it into ingest-spec. Disconnect fetch-spec → parse-operations if you want the uploaded spec to take priority.

> Swagger 2.0 and OpenAPI 3.x are both supported. The spec is the only input you need to change.

How It Works

1 · Enumerate

parse-operations reads the spec and finds every GET with an object-reference param (id, uuid, key…). Required params are filled from the spec's own examples. Output is a JSONL test plan.

2 · Probe (distributed, two-user)

For each target, the object id is varied (adjacent, zero, canary) and three requests are sent:

| Request | Auth |

|---|---|

| Unauthenticated | none |

| As User A | Bearer <USER_A_TOKEN> |

| As User B | Bearer <USER_B_TOKEN> |

Distributed across the fleet via batch-output-v2.

3 · IDOR / BOLA signals

| Finding | Meaning |

|---|---|

| UNAUTH_ACCESS | 200 with no auth — no auth check |

| CROSS_USER_IDOR | User A & B both 200, different bodies — each reads the other's object |

| SHARED_OBJECT | Both 200, same body — no per-user isolation |

> Tokens are optional — leave the placeholders for unauthenticated-only. GET-only, safe and idempotent. Authorized targets only.

Overview

Broken object level authorization sits at the top of the OWASP API Security Top 10, and it is the class a scanner is worst at, because every request is individually valid. The endpoint exists, the id is well-formed, the response is a 200. There is no signature to match, only an object that belonged to somebody else.

So the useful question is not what the bug is, it is which operations you managed to test for it. This drives the test from the API's own specification. You get every GET operation that takes an object reference, probed with varied ids and with authorization removed, and a list of the objects that came back when they should not have. Coverage follows the API as it grows rather than trailing it, which makes it worth running on every release as continuous security testing.

That enumeration step is the whole difference. A crawl or a proxy recording only reaches the routes something already called, so the endpoints added last sprint, the admin ones nobody links to, and the ones only a mobile client touches never get tested. The spec lists them the day they ship, names the parameter that carries the object reference, and supplies the example values needed to build a request the server will accept. Testing what the API says it offers beats testing what you happened to observe.

It is GET-only by design. Nothing here writes, so a run cannot create, modify or delete an object in the API under test.

Pipeline

  1. Read the OpenAPI or Swagger specification URL.
  2. The spec is fetched and parsed.
  3. Every GET operation taking an object reference is enumerated, with its parameters and expected responses.
  4. Each operation is requested with adjacent and canary ids and with authorization stripped, distributed across the fleet.
  5. Objects that returned without authorization are written to a PDF report.

Inputs

  • OpenAPI spec URL. The Swagger 2.0 or OpenAPI 3.x document describing the API under test. The run drives entirely off it, so there is no endpoint list to maintain by hand. The default is the public Swagger Petstore, an API published for testing, so a first run has a target you are allowed to hit.
  • Bearer token. Optional, and empty by default. Left empty, every probe is unauthenticated. Supply one and the same operations are also requested as a logged-in caller, which is how cross-object access gets tested.
  • Object id variations. The altered ids each operation is probed with, adjacent and canary values by default.

Outputs

  • Operation inventory. Every GET operation in the spec that takes an object reference, with the parameter carrying it, so you can see the tested surface before you read a single finding.
  • Probe results. Each request as sent, paired with the object that came back. An unauthenticated 2xx carrying object data is the finding, and the recorded request replays by hand.
  • PDF report. Spec coverage, the object-id endpoints tested, and the IDOR and BOLA findings ranked by severity.

Sample output

From a completed run against petstore.swagger.io, the API published for testing.

Three of the eight GET operations take an object reference, and each is enumerated with the parameter that carries it:

op_idmethodpathparamparam_inparam_typefilled_path
getPetByIdGET/pet/{petId}petIdpathinteger/pet/1
getOrderByIdGET/store/order/{orderId}orderIdpathinteger/store/order/1
getUserByNameGET/user/{username}usernamepathstring/user/user1
getUserByNameGET/user/{username}usernamepathstring/user/user2

Parsing the spec produces the coverage figure before a single request is sent:

{"total_paths": 14, "get_operations": 8, "objid_targets": 3, "title": "Swagger Petstore", "version": "1.0.7"}

The probe records each request as sent and what came back to a caller carrying no token at all:

{"op_id": "getPetById", "param": "petId", "had_token": false, "probes": [
  {"val": 1,  "url": "https://petstore.swagger.io/v2/pet/1",  "is_baseline": true,  "unauth": {"status": 200, "len": 138, "hash": "38eaad3f38a5", "ctype": "application/json"}},
  {"val": 2,  "url": "https://petstore.swagger.io/v2/pet/2",  "is_baseline": false, "unauth": {"status": 404, "len": 51,  "hash": "0320cbce8433", "ctype": ""}},
  {"val": 5,  "url": "https://petstore.swagger.io/v2/pet/5",  "is_baseline": false, "unauth": {"status": 200, "len": 135, "hash": "a0a1460a9b8a", "ctype": "application/json"}},
  {"val": 9,  "url": "https://petstore.swagger.io/v2/pet/9",  "is_baseline": false, "unauth": {"status": 200, "len": 134, "hash": "f344c7420ef0", "ctype": "application/json"}},
  {"val": 10, "url": "https://petstore.swagger.io/v2/pet/10", "is_baseline": false, "unauth": {"status": 200, "len": 138, "hash": "abd0908f6ed9", "ctype": "application/json"}}]}

Four ids returned four objects to an unauthenticated caller, and the differing body hashes are what separate that from one generic response repeated back. The other two operations answered 404 at every id they were probed with, so the run records them as tested rather than as findings. That pairing of request and response is what makes the report actionable, because the argument is never about whether the endpoint exists, only about who should be able to read it.

FAQ

Why drive the test from a spec instead of crawling the API?

A crawl reaches only the routes something already called. The spec lists every operation on the day it ships, names the parameter that carries the object reference, and carries example values for the other required parameters, so the baseline request is one the server accepts rather than one you guessed at.

How is this different from pointing a vulnerability scanner at the same API?

A scanner matches signatures, and authorization bugs have none. This does not look for a pattern in a response, it compares who asked with what came back across the operations the spec says take an object id.

Does the scan write to the API?

No. Only GET operations are enumerated and probed, so a run cannot create, modify or delete an object in the API under test.

Can it test as a logged-in user, not just anonymously?

Yes, by supplying a Bearer token. Note what that gives you: the comparison is between one authenticated caller and no caller at all, plus ids that are not that caller's. It is not a two-account diff, so there is no second set of credentials to configure.

What does a finding look like?

The request as sent, paired with the object that came back. Either an unauthenticated 2xx returning object data, or an id belonging to somebody else returning data. Because the full request is recorded, whoever fixes it can replay it rather than argue about reproduction steps.

  • Find SSRF in HTTP Parameters. Reach for this instead when the parameter you distrust takes a URL rather than an object id, and the bug is where the server fetches rather than who owns the record.
  • Directory & Content Discovery. Reach for this instead when there is no spec to read and you need to find the endpoints before you can test authorization on them.
  • Find Reflected XSS on a Domain. Reach for this instead when the API renders into a browser and the question is what the response does client-side, not who it belongs to.

Get a personalized demo

See Trickest in Action

A 30-minute walkthrough. We map the platform to your stack and answer pricing and deployment questions for your environment.