Finding · 01

One URL exposed every customer's orders.

MATT-2026-014 · Account takeover Illustrative · B2B SaaS · AI-assisted codebase CriticalClosed

Summary

How a missing ownership check became a refund machine

How does any signed-in user end up able to read, and refund, another customer's orders?

The order endpoints trusted the ID in the URL. Change one number and you were looking at someone else's order: customer email, total, last four digits of the card. The refund endpoint trusted the same ID, so the same change issued a refund on someone else's purchase.

The handler had been generated by an AI coding assistant and passed review. It did exactly what the ticket asked. Nobody had specified that it should check who owned the order, so it didn't. A model working for an attacker would find this in its first pass.

The fix was small and structural: ownership enforced once, at the data layer, for every order route. Refunds over $500 now require a second factor. Both protections became rules that run on every release.

Review catches style and logic. It rarely catches the check nobody wrote a test for.

MATT research team

Key insights

What we learned

01

The bug was correct code

The handler did what the ticket asked. It fetched an order by ID. Nobody wrote down that the order had to belong to the caller, so the generated code never checked. Review passed because there was nothing visibly wrong.

02

Fix it once, in one place

Adding a check to each handler would have left the next AI-written route just as exposed. Ownership is now enforced at the data layer, so every current and future order route inherits it.

03

Turn the fix into a rule

Two plain-English rules now run on every release: no user may read or act on another user's orders, and refunds over $500 need a second factor. The finding cannot quietly come back.

In action

Two requests, before and after

Signed in as user B. Order 48213 belongs to user A. Identifiers are illustrative.

— before —
$ GET /api/orders/48213
200 OK {"customer":"a***@acme.io","total":1240.00,"card_last4":"4412"}
$ POST /api/orders/48213/refund
200 OK {"refunded":1240.00}
— after —
$ GET /api/orders/48213
403 Forbidden ownership enforced at the data layer
$ POST /api/orders/48213/refund
403 Forbidden rule MATT-R-07 holds · release v2.14
RuleCheckedStatus
“A user must never be able to read or act on another user's orders.”Every releaseHolds
“Refunds over $500 require a second factor.”Every releaseHolds

Where we start

Find out what yours looks like.Book an ExploitOps Review