fix(security): only remove a student from a run#333
Open
Isaries wants to merge 1 commit into
Open
Conversation
The remove student endpoint took the target user straight from the student id and never checked it was a student. Both steps of removeStudentFromRun are already scoped to the run, so passing the id of a user outside the run does nothing. A teacher is a different matter: getListByRunAndUser does not filter by role and teachers do have a workgroup in a run, so passing the id of a teacher who shares the run removed that teacher from their own workgroup in it. Check that the target is a student before removing them.
Isaries
force-pushed
the
fix/remove-student-run-role
branch
from
July 16, 2026 09:10
ee3f7e8 to
82f32da
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
DELETE /api/teacher/run/{runId}/student/{studentId}/removehas the same shape as #332: it checks write permission on the run from the path, then loads the target user straight fromstudentIdwithout checking the two are related. This one is much less serious, and I want to be precise about why rather than overstate it.There is no cross-run problem here. Both steps of
removeStudentFromRunare already scoped to the run:removeStudentFromWorkgroupsInRunusesgetWorkgroupListByRunAndUser(run, user), filtered by run.removeStudentFromPeriodInRunusesrun.getPeriodOfStudent(user), which returns null for a non-member and is guarded.So passing the id of a student from another teacher's run does nothing at all.
The gap is the missing role constraint.
getListByRunAndUserdoes not filter by role, and teachers do have a workgroup in a run. So a teacher who shares a run can pass a co-owner's user id to this endpoint and remove that co-owner from their own workgroup in the run. The target has to already be associated with the run, so this is a lateral action between people who share it rather than an escalation, but the endpoint is documented as removing a student and should hold to that.Changes
Why this differs from #332
ChangeStudentPasswordControllerneeded a run membership check becauseupdateUserPasswordis not scoped to the run at all. Here the underlying operations already are, so a membership check would add nothing against the real gap while risking two behaviour changes I did not want to make blind:Happy to switch to
run.isStudentAssociatedToThisRunfor symmetry with #332 if you would rather have the stricter check and are not worried about either of those.Testing
mvnw test -Dtest=RemoveStudentRunControllerTestpasses (3 tests).removeStudentFromRunbeing called on the teacher rather than the request being rejected.