Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

IDOR in AI Tools 🔓

When an AI agent's tool trusts the object id it's handed and skips the ownership check, any user can read any tenant's data.

This is a hands-on lab for one of the most important — and most overlooked — auth bugs in modern LLM/agent systems: IDOR / Broken Object-Level Authorization (BOLA) in the tool-calling layer. Teams spend enormous effort hardening the model (prompt injection, jailbreaks) while the real breach is boring and classic: a backend tool that never checks whether the id it was given belongs to the caller.

⚠️ For education / authorized security training only. The app leaks data by design. Do not deploy it. All data (names, emails, phones, IPs, addresses) is 100% fictional, generated by a seeded PRNG.

The concrete example here — "PayPortal", a fake multi-tenant payment dashboard with an AI copilot — is an anonymized reproduction of a real, already-fixed bug-bounty finding. Company name, live endpoints, and real data are removed; what remains is the reusable pattern, which applies to any agent tool that takes an object id (order, invoice, ticket, user, document, file…).


The bug in one paragraph

An AI assistant is given a backend tool, query_api_record(orderId), wired directly to a privileged lookup that can read every tenant's records. The tool checks that you're logged in — but never checks that the orderId you asked for belongs to you. Because ids are sequential and predictable, any single account can enumerate them and exfiltrate all tenants' customer PII. The AI became a confused deputy: it wields more privilege than the user driving it.

The normal app path (GET /api/orders) is correctly tenant-scoped — proving the flaw lives only in how the agent's tool was wired, not in the model or the login.

The pattern, model-agnostic: an LLM may choose which tool to call, but it must never be what authorizes the call. Enforce object ownership inside the tool, from the authenticated session identity — never from model output or user-supplied arguments. Swap the mock engine for a real model (OpenAI, Llama, Claude, … — see below) and the bug is identical: a smarter model does not fix a missing authorization check.

Quick start

npm install
npm start                 # -> http://localhost:8989   (PORT=xxxx to change)
# in another terminal:
npm run exploit           # mass cross-tenant exfiltration PoC (vulnerable vs fixed)

In the UI: toggle ❌ vulnerable / ✅ fixed to see the same attack succeed, then get blocked, live.

Optional: drive it with a REAL LLM

The Copilot ships with a deterministic mock engine (no key, offline). To prove the bug is in the tool wiring and not the model, you can plug in a real LLM — any OpenAI-compatible provider or local runtime:

cp .env.example .env        # then fill in LLM_BASE_URL / LLM_API_KEY / LLM_MODEL
npm start                   # a "🤖 live LLM" engine toggle now appears in the UI

Works with OpenAI, OpenRouter (one endpoint → Claude, Llama, GPT, Mistral, …), Groq, Together, or a local Ollama / vLLM / LM Studio. The real model decides to call query_api_record and picks the orderId — the tool still runs through the same vulnerable/fixed resolve, so a smarter model does not fix the bug. That is the lesson, made concrete.

What to read

File What it shows
docs/VULN.md Root cause, before/after code, remediation, OWASP/CWE mapping
docs/WALKTHROUGH.md Step-by-step guided demo (~10 min)
src/copilot.ts runVulnerable vs runFixed — the whole lesson, side by side
src/services.ts queryRawGlobal (privileged) vs queryScoped (ownership-checked)
exploit/exploit.ts The enumeration attack, lab-safe

Project layout

src/
  server.ts     Express app + routes (login, scoped dashboard, SSE Copilot)
  data.ts       Multi-tenant fictional dataset (seeded, deterministic)
  services.ts   Backend services: privileged-global vs tenant-scoped
  copilot.ts    Agent orchestration + tool-calling (vulnerable & fixed wirings)
  llm.ts        Optional real-LLM engine (any OpenAI-compatible provider, via .env)
public/
  index.html    Chat UI with a vulnerable/fixed toggle
exploit/
  exploit.ts    Mass cross-tenant exfiltration PoC
docs/
  VULN.md, WALKTHROUGH.md

The takeaway

An LLM may decide which tool to call, but it must never be what authorizes the call. Enforce object ownership inside the tool, from the authenticated session identity — never from model output or user-supplied arguments.

License

MIT — teaching material.

About

IDOR / Broken Object-Level Authorization in AI agent tool-calling — a hands-on vulnerable lab. An LLM tool trusts the object id it is handed and skips the ownership check, so any user reads any tenant's data.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages