The bug report is short: Alice can read Bob's invoice.
Three patches arrive. All three stop the exact request in the report. Which one would you approve?
This is a fictional code-review exercise. In this application, each invoice belongs to one user. Authentication middleware provides a verified req.user.id. There are no shared accounts or administrator exceptions.
The original endpoint
app.get('/invoices/:id', requireLogin, async (req, res) => {
const invoice = await db.invoice.findUnique({
where: { id: req.params.id }
});
if (!invoice) return res.sendStatus(404);
return res.json({ id: invoice.id, total: invoice.total });
});
Alice changes the invoice ID in the URL. The server returns Bob's invoice because the lookup never checks ownership.
Patch A: reject the reported ID
if (req.params.id === 'invoice-bob-17') {
return res.sendStatus(404);
}
Patch B: shut down the endpoint
return res.sendStatus(403);
Patch C: scope the lookup to the authenticated user
const invoice = await db.invoice.findFirst({
where: {
id: req.params.id,
ownerId: req.user.id
}
});
if (!invoice) return res.sendStatus(404);
return res.json({ id: invoice.id, total: invoice.total });
Before reading on: what tests would distinguish these patches?
Reveal the review
Patch A blocks one example. Another invoice belonging to Bob still passes through. It can also prevent Bob from opening his own reported invoice.
Patch B blocks the attack by removing the feature. It fails the requirement that owners can read their invoices.
Patch C expresses the stated policy: the requested invoice must also belong to the authenticated user. The owner comes from the verified session, not a user-supplied query parameter.
The test matrix I would ask for
| Request | Expected result |
|---|---|
| Alice requests her own invoice | 200, expected fields |
| Alice requests Bob's reported invoice | 404 |
| Alice requests another invoice belonging to Bob | 404 |
| Bob requests his own invoice | 200, expected fields |
| Alice requests a nonexistent invoice | 404 |
| Anonymous caller requests an invoice | Rejected by authentication |
These are expected outcomes derived from the example, not results from an executed application. A real implementation also needs input validation and tests against its actual database and authentication stack.
The detail I care about most is the second invoice belonging to Bob. It separates a general authorization rule from a patch tailored to the bug report.
Why this is a useful review exercise
“The original exploit stopped working” is necessary evidence. It is incomplete evidence.
A useful review checks the policy, a different instance of the same failure, and legitimate behavior. Otherwise, we can accidentally reward a fix that blocks the demo while leaving the vulnerability class intact.
I am building Breachloom, a browser-based security practice platform with code-repair exercises. This kind of reasoning is what I want learners to practice. The platform also has a free IDOR case without an account.
What additional test would you require before approving Patch C? State the assumption it tests.
Top comments (0)