Members named access or blocks hijack the member-role route and mutate project settings
Description
Static route segments shadow the {username} param route, so a member-role POST for a user named access or blocks silently runs a different, project-wide action.
The routes
internal/server/ui_routes.go:159, :161, :162:
r.Post("/{owner}/projects/{key}/members/{username}", s.uiUpdateProjectMember)
r.Post("/{owner}/projects/{key}/members/access", s.uiUpdateProjectAccess)
r.Post("/{owner}/projects/{key}/members/blocks", s.uiBlockProjectUser)
chi resolves static segments before param segments, so the static patterns always win.
Steps to reproduce
- Add a project member whose username is
access. - On the members page, change that member's role and submit.
- The form posts to
/{owner}/projects/{key}/members/access— the URL built byuiProjectMemberPath(internal/server/ui_paths.go:104-106), emitted by the member row form atinternal/server/templates/project_member_page.html:54.
Expected
uiUpdateProjectMember sets the role of the member named access.
Actual
uiUpdateProjectAccess runs and changes the project's visibility level. A member named blocks hits uiBlockProjectUser the same way. Neither handler reads {username}, so the operator mutates project-wide settings while believing they changed one member's role.
Why it is reachable
access and blocks are both legal usernames. store.NormalizeUsername (internal/store/accounts.go:17-32) only enforces 3–32 chars of [a-z0-9_-], and there is no reserved-name list anywhere in the repo. Same applies to candidates on the GET side (ui_routes.go:157).
Fix
Move the collection-level actions out of the {username} namespace (.../member-access, .../member-blocks), and/or add a reserved-username list enforced at signup. Add route tests asserting a member named access gets uiUpdateProjectMember.
Sub-issues
0Linked issues
0GitHub
0No branches or pull requests linked.