Unauthenticated IDOR on /api/notes discloses other users' private notes
idor · server.js:48-53
f87c6fc2fa by Macintosh1011 on 2026-05-30Summary
The /api/notes handler at server.js:48 fetches a note solely by the numeric id supplied in the query string and returns it directly, without authenticating the requester or checking that the note's owner matches the caller. Notes flagged `private: true` belonging to other users are returned identically to public notes. Expected behavior is to require authentication and only return a note when it is public or owned by the authenticated user.
Impact. Any unauthenticated remote attacker can enumerate note IDs (e.g., id=1, 2, 3, ...) and read every user's private notes — including sensitive content such as bank PINs, passwords, and personal data — as confirmed by retrieving alice's private note containing a bank PIN via a simple GET request.
Vulnerable code — server.js
🔴 if (parsed.pathname === "/api/notes") {🔴 const id = Number(q.id || 1);🔴 const note = NOTES.find((n) => n.id === id);🔴 if (!note) return send(res, 404, "no such note");🔴 return send(res, 200, JSON.stringify(note), "application/json");🔴 }
Working exploit
curl -s 'http://127.0.0.1:4600/api/notes?id=2'
Exploit transcript
HTTP 200 Content-Type: application/json
{"id":2,"owner":"alice","text":"alice PRIVATE: bank pin 4821","private":true}Confirmed: HTTP 200 with JSON body {"id":2,"owner":"alice","text":"alice PRIVATE: bank pin 4821","private":true} returned to an unauthenticated request for id=2.
CVSS v3.1
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N→ 7.5 (high)Recommended fix
AI-suggested fix — review before applying (derived from analysis of untrusted repo content):
Require authentication and enforce owner/visibility checks before returning the note:
--- a/server.js
+++ b/server.js
@@
if (parsed.pathname === "/api/notes") {
- const id = Number(q.id || 1);
- const note = NOTES.find((n) => n.id === id);
- if (!note) return send(res, 404, "no such note");
- return send(res, 200, JSON.stringify(note), "application/json");
+ const user = authenticate(req); // verify session/token
+ if (!user) return send(res, 401, "authentication required");
+ const id = Number(q.id);
+ if (!Number.isInteger(id)) return send(res, 400, "invalid id");
+ const note = NOTES.find((n) => n.id === id);
+ if (!note) return send(res, 404, "no such note");
+ if (note.private && note.owner !== user.username) {
+ return send(res, 404, "no such note"); // avoid id enumeration
+ }
+ return send(res, 200, JSON.stringify(note), "application/json");
}Ship the fix
Rook found it and proved it. Hand the finding — with its working exploit as a failing test — to Otis to write the fix PR.