Educational demonstration

Broken Object-Level Authorization in a Multi-Tenant API

An invoicing API checks that you are logged in, but not that the invoice is yours. One parameter change crosses tenants.

Lab target · not a client engagement
Overview

The lab API models a typical B2B SaaS: two organizations, an admin and a member in one of them, and invoices belonging to each. We tested every endpoint with each token, for every verb.

Findings
high
XV-DEMO-08 · Verified by test

Invoices readable across tenants by ID

Description

GET /api/invoices/:id loads by ID without checking the caller's organization.

Impact

Any authenticated user can read any organization's invoices.

Recommendation

Scope every lookup by the caller's organization, ideally in a shared data-access layer or with database row-level security.

high
XV-DEMO-09 · Verified by test

Members can delete invoices via an unguarded verb

Description

Role checks exist on PATCH but were never added to DELETE.

Impact

Least-privileged users can destroy financial records.

Recommendation

Evaluate one central policy function for every route and verb.

Outcome

Measured in the lab: in vulnerable mode, a tenant-A member read tenant B's invoice (200) and deleted an invoice (204). In fixed mode, a single can(user, action, invoice) policy is applied to every verb, and all 12 cells of the authorization matrix return the intended status — including 404 rather than 403 for objects the caller may not see.

Reproduce

The lab code and tests for this case study live in labs/web2 of the Xevry website repository.

cd labs/web2npm installnode --test test/bola.test.js

Files: labs/web2/src/invoices.js · labs/web2/test/bola.test.js

Start a conversation

Let's discuss your security needs.

Tell us what you're building. We'll respond with clear questions, an honest view of whether we're the right fit, and a scoped proposal if we are.