Found while reviewing #253 (now closed as obsolete). This is on main today and was not introduced by that PR.
The behavior
backend/routes/social.pyget_students returns a profile for every user in the database — display name, streak, course list, mastery stats (mastered/learning/struggling counts), and top concepts.
It is authenticated but not scoped. social.py:426:
user_id=get_session_user_id(request) # assigned and never used
That call enforces "is logged in" and nothing else — the value is dead code. There is no require_self / require_admin, and neither quite fits (require_self guards a target user_id).
So any authenticated user can enumerate every other user, across all courses, offerings, and terms, along with their academic performance.
Why this needs a decision, not just a patch
The docstring ("Return a lightweight profile for every user in the DB") reads as intentional — this looks like it backs a student-discovery / class-intel surface. So the question isn't "is it a bug", it's what the intended visibility boundary is:
- Same offering only? (
enrollments → offering_id matching the viewer's) - Same abstract course, any offering/term?
- Same term?
- Opt-in directory, with a privacy setting?
Mastery data in particular is academic-performance information about identifiable students. Worth an explicit call rather than an implicit one.
Note
The repo has prior art for this class of fix in calendar scoping (test_calendar_scoping_enrollment.py, test_calendar_read_enrollment.py, test_calendar_sibling_write_scoping.py) — the enrollment-scoping pattern is established, just not applied here.
Per conventions this should resolve through services/academics.py rather than hand-rolling the embed. Note academics.py currently exposes user_offering_ids_for_course / offering_course_id but has no bulk "names for all enrollments" helper — which is likely why both this and #253 hand-rolled it. Adding that helper is probably a prerequisite.
Found while reviewing #253 (now closed as obsolete). This is on
maintoday and was not introduced by that PR.The behavior
backend/routes/social.pyget_studentsreturns a profile for every user in the database — display name, streak, course list, mastery stats (mastered/learning/struggling counts), and top concepts.It is authenticated but not scoped.
social.py:426:That call enforces "is logged in" and nothing else — the value is dead code. There is no
require_self/require_admin, and neither quite fits (require_selfguards a target user_id).So any authenticated user can enumerate every other user, across all courses, offerings, and terms, along with their academic performance.
Why this needs a decision, not just a patch
The docstring ("Return a lightweight profile for every user in the DB") reads as intentional — this looks like it backs a student-discovery / class-intel surface. So the question isn't "is it a bug", it's what the intended visibility boundary is:
enrollments→offering_idmatching the viewer's)Mastery data in particular is academic-performance information about identifiable students. Worth an explicit call rather than an implicit one.
Note
The repo has prior art for this class of fix in calendar scoping (
test_calendar_scoping_enrollment.py,test_calendar_read_enrollment.py,test_calendar_sibling_write_scoping.py) — the enrollment-scoping pattern is established, just not applied here.Per conventions this should resolve through
services/academics.pyrather than hand-rolling the embed. Noteacademics.pycurrently exposesuser_offering_ids_for_course/offering_course_idbut has no bulk "names for all enrollments" helper — which is likely why both this and #253 hand-rolled it. Adding that helper is probably a prerequisite.