They Batched Eight API Calls Into One. I Chained Three of Them Past Authorization.

This story starts in the middle of a sprint, in the meeting room of a small fintech startup with the release cadence of a giant. I was the secure-development consultant invited in to give an opinion on an idea that had come up.

The team lead comes in and says: “there are too many API calls between the frontend and the backend. We are wasting precious seconds. I want to bundle all the calls into one batch, in a new endpoint: /api/batchRun.”

The move sounds brilliant. Instead of 8 calls – one. The frontend sends a JSON of commands, the backend runs them in a loop and returns ordered results.

In practice a request looked like this:

{
 "commands": [
 {"action": "getBalance", "userId": "23"},
 {"action": "getTransactions", "userId": "23", "since": "2023-01-01"},
 {"action": "transfer", "from": "23", "to": "999", "amount": "10"}
 ]
}

The team lead argued firmly that there was no problem with this. Because it is simply a bundling of requests, each of which could have been accepted separately anyway. Each one goes through its permission checks, its input validation and so on.

All the same, something felt wrong to me.

What the system was not checking

The system handled each command in order, but never checked the logical relationships between the actions, and every combination was sent through as-is.

After thinking about it, I designed a deliberately malicious combination:

  • First, transfer 10 shekels from myself to another account.
  • Then ask to see the balance of that account.
  • Then try to pull a PDF report using the identifier of a previous action – one I had no permission to reach.

By connecting commands inside the same batch, I bypassed controls that existed on every separate endpoint – because the code all ran against the same session object, and never re-checked permissions on each result.

What came out of it

When we sat down to analyse it, the team lead explained: “we did not think of the commands as dependent. We built this purely as an optimisation.” But in practice, attackers create chains out of the combination, and produce attacks that do not exist in any single API.

The lesson the development team took away: every optimisation at the interface layer has to be re-examined at the security layer too.

And at the end of the process I will not forget what the team lead said: “this is not the cleverest bug, it is the most sophisticated one – because we built it ourselves, out of a desire to improve.”


I first shared a version of this as a LinkedIn post on 2025-07-21. It is republished here, lightly edited, so it is easier to find and reference. — Erez Metula