fix(backend): close D4 (anonymous vote results) and D5 (waitlisted submission read) - #195
Merged
Merged
Conversation
ListVoteResults used RequireSubject, which admits the anonymous subject, so an unauthenticated caller could read placements the moment VIEW_RESULTS was open — the handler's own comment says "any signed-in user". Switch to RequireUser. The capability gate still lets organisers review a tally before publishing, and the only caller is the authenticated /my/hackathon/[id]/voting route, so nothing public breaks.
…ission read (D5) GetSubmission/ListSubmissions let a team member read their own team's submission (team-scoped) and fell back to a hackathon-wide Submission:Read so participants can read every team's work to vote on it. But Join grants the Member role even to waitlisted registrants (so they can propose), and Member carries that hackathon-wide read — so an unapproved registrant could read every team's submissions, a composition nobody chose. Extract a shared authorizeSubmissionRead helper: team-scoped read is unchanged, and the hackathon-wide path now excludes callers holding a waitlisted participant row. Confirmed participants (no waiting row) and organizers/admins (who reach it via Owner/admin, not a participant row) are unaffected. Pinned by submission_read_gate_test.go: a waitlisted joiner gets PermissionDenied reading another team's submission, and approval lifts it to NotFound (past the gate, no submission) — a different code proving the gate opened.
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.
Two more backend authorization holes from the audit, same shape as D1/D2/D3.
D4 — anonymous could read vote results
ListVoteResultsusedRequireSubject, which admits the anonymous subject, so an unauthenticated caller could read placements the momentVIEW_RESULTSwas open — even though the handler's own comment says "any signed-in user". Switched toRequireUser. The capability gate still lets organisers review a tally before publishing, and the only caller is the authenticated/my/hackathon/[id]/votingroute, so nothing public breaks. (TheVIEW_RESULTS-defaults-open half is left as-is: all six capabilities seed open by documented design — "a hackathon states its policy explicitly" — and the organiser can close it in the panel.)D5 — waitlisted registrants read every team's submission
GetSubmission/ListSubmissionsfall back to a hackathon-wideSubmission:Readso participants can read every team's work to vote on it. ButJoingrants theMemberrole even to waitlisted registrants (so they can propose), and Member carries that hackathon-wide read — so an unapproved registrant could read all submissions. A composition nobody chose.Fix: a shared
authorizeSubmissionReadhelper — team-scoped read unchanged; the hackathon-wide path now excludes callers holding a waitlisted participant row. Confirmed participants (no waiting row) and organizers/admins (who reach it via Owner/admin, not a participant row) are unaffected.Tests (both pinned)
submission_read_gate_test.go— a waitlisted joiner getsPermissionDeniedreading another team's submission; approval lifts it toNotFound(past the gate) — a different code proves the gate opened.RequireUser, already covered byrequire_user_test.go.Verification (devcontainer, fast tier)
go build ./...clean ·go test ./internal/service/... ./internal/middleware/...OK (D5's new spec passes) ·gofmt -lempty