ARGUS
autonomous IDOR / broken-access-control validation

Is your app one guessed ID
away from a breach?

Change one number in a URL and you could be reading another user's data. That's IDOR, and it hides in almost every fast-built app. Argus is an autonomous agent that hunts it and proves each finding with a real exploit, run in an isolated sandbox. No maybes: confirmed with evidence, or rejected.

install & run
$ pip install argus-idor
$ argus scan --provider <provider>   # gemini, deepseek, or openai
$ argus eval                          # the benchmark · no key

Works with DeepSeek, OpenAI, or Gemini (free flash tier). Runs locally against a target you authorize, in a sandbox that can reach nothing else.

100%
Precision
100%
Recall
48
Cases tested
0
False positives

Benchmarked across 2 target apps: saas (12) · clinic (36). 24 real IDORs confirmed, 24 decoys rejected, 0 false positives.

Watch it work

0/39 steps
replaying a captured run…

Confirmed exploits

6 in this run · with proof
GET /api/documents/{id}
alice → bob
✓ confirmed
200

alice retrieved bob's document (doc_2001), response carried the victim's unique data.

› evidence
exploit request
GET /api/documents/doc_2001
Authorization: Bearer <redacted>
response · victim data leaked
HTTP 200
{"owner":"u_bob","secret":"BOB-DOC-55d4","title":"Vendor contract"}
status_2xxvictim_content_presentground_truth_cross_boundarycross_principal
Fix: On GET /api/documents/{id}, enforce an ownership/tenant check by loading the document and verifying its owner/tenant ID matches the authenticated user derived from the session or token before returning any data; if it does not match and the user lacks an explicit grant, deny with 403 or 404.
GET /api/documents/{id}
bob → alice
✓ confirmed
200

bob retrieved alice's document (doc_1001), response carried the victim's unique data.

› evidence
exploit request
GET /api/documents/doc_1001
Authorization: Bearer <redacted>
response · victim data leaked
HTTP 200
{"owner":"u_alice","secret":"ALICE-DOC-11b8","title":"Q3 strategy"}
status_2xxvictim_content_presentground_truth_cross_boundarycross_principal
Fix: Enforce a server-side ownership check on every document fetch: after resolving the document by `{id}`, verify the authenticated user matches the document’s owner (or has an explicit grant), and return 403/404 if not. Prefer scoping the query to the authenticated user’s owner ID so unauthorized documents are never even loaded.
GET /api/invoices/{id}
alice → bob
✓ confirmed
200

alice retrieved bob's invoice (inv_2001), response carried the victim's unique data.

› evidence
exploit request
GET /api/invoices/inv_2001
Authorization: Bearer <redacted>
response · victim data leaked
HTTP 200
{"amount":1375.5,"customer":"Globex Inc","owner":"u_bob","secret":"BOB-INV-9c2e"}
status_2xxvictim_content_presentground_truth_cross_boundarycross_principal
Fix: On every request, derive the caller's identity from the authenticated session/token and enforce an ownership check server-side, e.g., scope the lookup to `invoice.id = {id} AND invoice.owner_id = current_user.id`, returning 404 (not 403) when no matching record exists. Never trust a client-supplied user or tenant identifier, and apply the same owner-scoped filter to all read and write operations on that resource.
GET /api/invoices/{id}
bob → alice
✓ confirmed
200

bob retrieved alice's invoice (inv_1001), response carried the victim's unique data.

› evidence
exploit request
GET /api/invoices/inv_1001
Authorization: Bearer <redacted>
response · victim data leaked
HTTP 200
{"amount":420.0,"customer":"Northwind Ltd","owner":"u_alice","secret":"ALICE-INV-7a3f"}
status_2xxvictim_content_presentground_truth_cross_boundarycross_principal
Fix: Enforce an ownership check on every request: derive the caller's identity from the authenticated session/token (never from a client-supplied user or tenant ID) and confirm the invoice's owner matches the caller before returning any data. If it doesn't match, respond with 404 (or 403) and reveal nothing about the resource's existence.
GET /api/messages/{id}
alice → bob
✓ confirmed
200

alice retrieved bob's message (msg_2001), response carried the victim's unique data.

› evidence
exploit request
GET /api/messages/msg_2001
Authorization: Bearer <redacted>
response · victim data leaked
HTTP 200
{"body":"sent the wire","owner":"u_bob","secret":"BOB-MSG-8a09"}
status_2xxvictim_content_presentground_truth_cross_boundarycross_principal
Fix: On every request for `/api/messages/{id}`, after loading the message, verify server-side that the authenticated user's ID matches the message's owner (or an explicitly permitted recipient), and return 403/404 otherwise; never rely on the client or on ID obscurity.
GET /api/messages/{id}
bob → alice
✓ confirmed
200

bob retrieved alice's message (msg_1001), response carried the victim's unique data.

› evidence
exploit request
GET /api/messages/msg_1001
Authorization: Bearer <redacted>
response · victim data leaked
HTTP 200
{"body":"lunch at 1?","owner":"u_alice","secret":"ALICE-MSG-2e71"}
status_2xxvictim_content_presentground_truth_cross_boundarycross_principal
Fix: Enforce an ownership/authorization check on every request: resolve the message by `{id}` only within the authenticated user's scope, and return 403/404 if it does not belong to them. Do not rely on client-supplied identifiers alone, scope the lookup (or add a post-fetch comparison) to the current user's identity.

Rejected · false positives

6 in this run · access held
GET /api/orders/{id}
alice → bob
✕ rejected
403

access denied (HTTP 403); access control held.

› evidence
exploit request
GET /api/orders/ord_2001
Authorization: Bearer <redacted>
response · access control held
HTTP 403
{"error":"forbidden","status":403}
GET /api/orders/{id}
bob → alice
✕ rejected
403

access denied (HTTP 403); access control held.

› evidence
exploit request
GET /api/orders/ord_1001
Authorization: Bearer <redacted>
response · access control held
HTTP 403
{"error":"forbidden","status":403}
GET /api/users/{id}/profile
alice → bob
✕ rejected
403

access denied (HTTP 403); access control held.

› evidence
exploit request
GET /api/users/u_bob/profile
Authorization: Bearer <redacted>
response · access control held
HTTP 403
{"error":"forbidden","status":403}
GET /api/users/{id}/profile
bob → alice
✕ rejected
403

access denied (HTTP 403); access control held.

› evidence
exploit request
GET /api/users/u_alice/profile
Authorization: Bearer <redacted>
response · access control held
HTTP 403
{"error":"forbidden","status":403}
GET /api/settings/{id}
alice → bob
✕ rejected
403

access denied (HTTP 403); access control held.

› evidence
exploit request
GET /api/settings/set_2001
Authorization: Bearer <redacted>
response · access control held
HTTP 403
{"error":"forbidden","status":403}
GET /api/settings/{id}
bob → alice
✕ rejected
403

access denied (HTTP 403); access control held.

› evidence
exploit request
GET /api/settings/set_1001
Authorization: Bearer <redacted>
response · access control held
HTTP 403
{"error":"forbidden","status":403}
Built by Vatsalya Soni, CS undergrad. Argus is open source, now by you too.prove it, don't guess.