An access log shows one session reading three invoice ids in a row — find and fix the handler defect
An access log shows a single authenticated session fetching /api/invoices/1041, then 1042, then 1043 within a few seconds, each answered with 200. This handler serves that route.
Constraints:
req.user.idis the authenticated user, set by trusted middlewaredb.invoices.findByIdtakes only an invoice id and ignores the caller- the response must not reveal whether another account's invoice exists
app.get('/api/invoices/:id', requireLogin, async (req, res) => {
const invoice = await db.invoices.findById(req.params.id);
if (!invoice) return res.status(404).json({ error: 'not found' });
res.json(invoice);
});
Find and fix the vulnerability.
The handler authenticates but never authorizes — it loads the invoice by id, so any logged-in caller reads another account's record. Scope the lookup to the authenticated owner and answer 404 on a mismatch.
- ✗Reading
requireLoginas an authorization check rather than authentication - ✗Fixing the identifier format instead of adding the ownership constraint
- ✗Answering 403 on a denied object, confirming that it exists
- →Why is scoping the query safer than loading first and comparing afterwards?
- →What test would prove this route is closed for a second account?
The handler verifies only that someone is logged in. db.invoices.findById knows nothing about the caller, so any authenticated user receives another account's invoice — broken object-level authorization, made obvious by the sequential ids in the log.
The correct fix constrains the lookup itself to the owner rather than loading the record and comparing afterwards. A denied invoice produces the same response as a missing one, so existence is never disclosed.
app.get('/api/invoices/:id', requireLogin, async (req, res) => {
const invoice = await db.invoices.findByIdForOwner(req.params.id, req.user.id);
if (!invoice) return res.status(404).json({ error: 'not found' });
res.json(invoice);
});
The check must run on every request and on every verb of the route, not just on the read path.