agentggagentgg
Back to all findings
MEDIUMconfirmedmissing-access-controlidor-user-read45b23598f2e6

IDOR in GET /api/Users/:id — authenticated user can read any other user's record

The Finale-auto-generated GET /api/Users/:id endpoint is wrapped only with `security.isAuthorized()`; no handler narrows the lookup to the JWT user, so any logged-in account can read any other user's record by enumerating ids.

Fileserver.ts
Lines358361
Confidence
90%
File statusvalidated
Details

server.ts lines 358-361:

app.route('/api/Users/:id')
  .get(security.isAuthorized())
  .put(security.denyAll())
  .delete(security.denyAll())

The .get() middleware merely asserts authentication. Finale then exposes the generic read-by-pk handler from the autoModels block ({ name: 'User', exclude: ['password', 'totpSecret'], model: UserModel }), which performs UserModel.findByPk(req.params.id) with no per-user scoping.

There is no where: { id: callerId } filter and no comparison between req.params.id and the JWT subject anywhere in the chain — the only excluded fields are password and totpSecret, so email, role, deluxe status, lastLoginIp, profileImage and the rest of the UserModel attributes are returned.

Proof of concept
GET /api/Users/1 HTTP/1.1
Host: target
Authorization: Bearer <attacker-JWT-for-user-42>

Responds with user id 1's full record (admin account in default seed). Repeating with /api/Users/2, /3, … enumerates every user.

Impact

Authenticated information disclosure: any customer-level session can read every user's profile data (including admin), enabling targeted attacks against accounts identified this way. No elevated role required.

Validation
confirmed

The route definition at lines 358-361 wraps GET /api/Users/:id with only security.isAuthorized(), which checks JWT validity but does not scope the lookup. The Finale auto-generated handler (autoModels entry { name: 'User', exclude: ['password', 'totpSecret'], model: UserModel }) performs a generic findByPk on req.params.id with no where clause tying the record to the JWT subject, and only the password and totpSecret columns are stripped. An authenticated low-privilege user can therefore enumerate /api/Users/1, /api/Users/2, etc. and harvest email, role, deluxe status, lastLoginIp, and profile data of every account. The scope explicitly says to treat findings as if in production, so this real IDOR stands.

CVSS 3.1
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
Base score: 6.5 · MEDIUM

The endpoint is a standard HTTP API route (AV:N) and exploitation only requires incrementing the :id path parameter (AC:L, UI:N). The app.route('/api/Users/:id').get(security.isAuthorized()) chain at lines 358-361 requires a valid JWT but no role check, so any registered customer can call it (PR:L). The Finale autoModel for User only excludes password and totpSecret, so the attacker can enumerate every user — including admins — and read email, role, deluxe status, lastLoginIp, profileImage and every other attribute, which is near-total disclosure of the User table (C:H). PUT and DELETE are explicitly gated with security.denyAll() so integrity and availability are unaffected (I:N, A:N), and the impact stays inside the application's own authority (S:U).

References