Repository navigation
Check on the server who may change what (writes) - #559
Merged
doomlab merged 2 commits intoOct 9, 2026
Merged
Conversation
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This was referenced Oct 9, 2026
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.
Stage 1 of closing the server-side access gap found while tracing #539. Stacked on #558 (it reuses its access helper), so this targets that branch; once #558 merges, retarget to
dev. Please review the rules below: this is a behaviour change on the server.Problem
Most write endpoints only checked that the caller was signed in. Hiding a button in the UI is not protection: anyone signed in could send the request directly. For example
deleteTask({ id })deleted the task and its logs for anybody (the file even had aTODO: add validation to ensure correct tenant), andupdateProjectMembertookprivilege,projectIdanduserIdfrom the browser, so a contributor could promote themselves to project manager. Task ids are small sequential numbers, so they are easy to guess.What changed
New
requireAccess.ts(next togetProjectAccess) with checks that report anything not allowed as "not found". Applied to 50 endpoints:userIdcan no longer reassign a role.createForm,importDefaultRoles,createRole,initializeWidgetsuserIdmust be the caller).initializeProjectWidgetscomments.markAsReadsrc/updates/queries/*migrationscreateDefaultWidgetswas split out ofinitializeWidgetsso the admin migration doesn't call the now self-only mutation.Not covered (follow-ups)
getComments,getTasks,getTask,getMilestones,getRoles,getTags,getProjectMembers,getUsers, ...). This PR is writes only.sendNotification: other mutations call it internally, so it needs to be split into a safe internal function first. It can currently be used to send notifications to arbitrary users.Testing
mutationAccess.test.ts: every one of 28 write endpoints is called as (a) a signed-in user with no part in the project and (b) a plain contributor, against a database stand-in that records writes. All must refuse with "not found" and write nothing. The same 28 as a project manager must get past the check. All 56 refusal tests fail against the old code and pass now.requireAccess.test.ts(14 tests) for each rule (invite email match, self-only, form/folder/role ownership, same-project attachments, ...), plus tests forinitializeWidgetsand the admin-only updates (fail against the old code).getJsonSchema.test.tserrors), lint and the full suite (209 tests) pass.🤖 Generated with Claude Code