
Testing Tenant Isolation in AI APIs with Burp and JavaScript
When I test AI APIs, I do not start with model quality. I start with tenant boundaries. If two workspaces can see each other's data, the model just becomes a very efficient way to surface a normal authorization bug.
That is the failure mode this post focuses on: cross-tenant reads, writes, and exports in APIs behind a chat or assistant UI. The UI often looks clean. The backend is where isolation either holds or falls apart.
Why Tenant Isolation Fails in AI APIs
AI products usually mix ordinary application data with model-facing context: conversations, files, tool outputs, embeddings, workspace settings, and audit logs. The bug is rarely “the model leaked something.” More often, the API trusted a tenant identifier from the wrong place.
A common pattern is:
- the browser sends
workspaceId - the server accepts it without binding it to the authenticated session
- a later endpoint uses that ID to fetch messages, files, or vector search results
If the response is useful to the attacker, the problem is real even when the UI tries to hide it.
What to Verify Before You Trust an API Response
Tenant IDs, workspace IDs, and hidden context
Check every identifier that selects tenant-scoped data:
tenantIdworkspaceIdprojectIdorgIdconversationIddocumentId
Do not assume only the visible request body matters. I have seen tenant context arrive in headers, cookies, query strings, and server-side session state in the same product. That mixed design is where mistakes show up.
Mixed sources of truth between client and server
A secure API should have one source of truth for who owns a record. If the client sends workspaceId, the server should treat it as a hint at best and re-derive authorization from the authenticated account.
If the API checks only that an ID exists, you are testing lookup behavior, not authorization.
Setting Up a Safe Burp and JavaScript Test Harness
Capturing requests and replaying them with controlled changes
Burp is useful here because you want to compare requests, not brute-force them. Capture a normal workflow from Account A and Account B, then replay the same request with only one field changed.
A simple approach:
- Log in as Account A.
- Trigger a normal read, update, or export request.
- Send the request to Repeater.
- Swap only the tenant-scoping value.
- Compare the response status, body, and timing.
You are looking for inconsistent authorization, not crashes.
POST /api/workspaces/list HTTP/1.1
Host: app.example
Authorization: Bearer <token-a>
Content-Type: application/json
{"workspaceId":"ws_a_123","limit":20}Using JavaScript to compare responses across accounts
For repeatable checks, I like a tiny Node script that sends the same request with two different sessions and diffs the shape of the response.
const fetch = global.fetch;
async function req(token, workspaceId) {
const res = await fetch("https://app.example/api/workspaces/list", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ workspaceId, limit: 20 }),
});
return { status: res.status, text: await res.text() };
}
const a = await req(process.env.TOKEN_A, "ws_a_123");
const b = await req(process.env.TOKEN_B, "ws_a_123");
console.log({ a: a.status, b: b.status });
console.log(a.text.slice(0, 500));
console.log(b.text.slice(0, 500));
This is not about exploiting anything fancy. It is about proving whether the backend uses the authenticated principal or just the submitted identifier.
Practical Isolation Tests That Catch Real Bugs
Swap identifiers and watch for cross-tenant reads
Start with read paths. They are usually the easiest place to catch a broken boundary:
- list conversations from another workspace
- fetch a document by ID from another tenant
- retrieve embeddings or search results for foreign data
A 403 is good. A 404 can be fine if it is consistent. A 200 with foreign content is the bug.
Test create, update, delete, and export paths separately
Do not stop at reads. I have seen systems protect GET and forget POST /export, PATCH /settings, or DELETE /conversation/:id.
| Path type | What to test | Common failure |
|---|---|---|
| Read | foreign IDs and list endpoints | leaked data |
| Write | update another tenant's object | cross-tenant mutation |
| Delete | remove foreign records | destructive access |
| Export | download workspace data | bulk exfiltration |
Check model-related endpoints, not just normal CRUD routes
AI APIs often add special routes like:
/chat/tool-call/files/upload/embeddings/exports/runs/:id
These endpoints may use different middleware than standard CRUD routes. That split is where enforcement gets uneven.
Reading the Results Without Guessing
What strong isolation looks like
Strong isolation has boring behavior:
- foreign IDs return nothing useful
- response shape stays consistent
- error messages do not reveal existence
- audit logs show the denied access attempt
If the server binds data to the session and ignores client-supplied tenant context, you usually see that consistency everywhere.
Signs of partial enforcement or UI-only protection
Red flags:
- the UI hides another workspace, but direct API calls still return it
- list endpoints are protected, but detail endpoints are not
- exports work even when reads are blocked
- some endpoints use
workspaceIdfrom the token, others trust the body
That last one is especially common. Mixed enforcement makes bugs look random until you map every route.
Fixes That Belong on the Backend
Server-side tenant binding and authorization checks
The fix is not “hide the field in the frontend.” The fix is:
- derive tenant membership from the authenticated session
- verify object ownership on every request
- reject cross-tenant references before touching the datastore
- apply the same check to read, write, export, and model-tool routes
If you use shared services or async jobs, carry the authenticated tenant context through the job boundary and re-check it at execution time.
Safer logging, error handling, and audit trails
Logs should help you investigate abuse without leaking tenant data. Keep them structured, but do not print raw secrets, document bodies, or full model inputs.
A good audit trail should answer:
- who tried to access the object
- which tenant they belonged to
- which object was targeted
- whether the request was denied or allowed
That makes incident review much easier when a boundary slips.
Conclusion
Tenant isolation bugs in AI APIs are usually ordinary authorization bugs with a larger blast radius. Burp helps you replay the same action with surgical changes. JavaScript helps you automate the comparison across accounts.
If a workspace boundary matters to the product, test it on every route that can read, write, export, or feed model context. The model is not the weak point. The trust boundary is.


