Three Security Fixes. Only One Survives the Tests.
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, e
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.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.