Repository navigation
Conversation
Creating a note with parentId only checked that the parent exists, and only after the child note was already saved. Any logged-in user could attach a note to someone else's note tree. Now the handler resolves the parent first and requires a Write role on it (same rule as the userCanEdit policy), replying 403 otherwise. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Coverage Report
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
neSpecc
commented
Oct 21, 2026
There was a problem hiding this comment.
🟢 Approval recommended
No blocking issues were identified; the remaining suggestion is an optional orphan-prevention regression test.
1 open issue
What changed in this PR
Prevents unauthorized child-note creation by checking parent edit permissions before saving.
Changes:
- Requires Write access to the parent, including inherited access.
- Rejects unknown parents before persistence.
- Adds tests for nonmembers and read-only members.
| File | Description |
|---|---|
| src/presentation/http/router/note.ts | Validates parent existence and edit permission before creation. |
| src/presentation/http/router/note.test.ts | Tests rejection of users without parent edit access. |
Review effort: Balanced
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add parentNoteResolver middleware, which resolves the parent note from "parentId" or "parentNoteId" in the request body, and the userCanEditParentNote policy, which requires a Write role on it. POST /note uses them instead of the inline check. An unknown parent note now gets 406 "Parent note not found" from the resolver, the same way noteResolver handles an unknown note. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
neSpecc
added a commit
that referenced
this pull request
Oct 21, 2026
Replace the router helper with parentNoteResolver and the userCanEditParentNote policy from #310. An unknown parent note now gets 406 "Parent note not found" from the resolver, as in POST /note. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
neSpecc
commented
Oct 21, 2026
userCanEditParentNote skipped the check whenever request.parentNote was empty. If a route used the policy without parentNoteResolver, every request with a parent note id would pass unchecked. Now the policy checks the body for "parentId" or "parentNoteId" itself and replies 406 "Parent note not found" if the id is there but the parent note is not resolved.
neSpecc
added this pull request to stack #312
October 21, 2026 01:18
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.

Problem
POST /noteaccepts aparentId, but the handler only had theauthRequiredpolicy.NoteService.addNotechecked that the parent exists, and only after the child note was already saved. So any logged-in user could attach a note to someone else's note tree. Because the existence check came last, an unknownparentIdalso left an orphan note (and its history record) behind, even though the request returned 400.Changes
parentNoteResolvermiddleware (src/presentation/http/middlewares/note/useParentNoteResolver.ts). It reads the parent's public id fromparentId(this route) orparentNoteId(relation routes, see fix: check edit permission on parent note in relation routes #311), resolves it intorequest.parentNote, and replies 406Parent note not foundif it does not exist, the same waynoteResolverhandles an unknown note. A request without a parent id is left alone.userCanEditParentNotepolicy (src/presentation/http/policies/userCanEditParentNote.ts). It requiresMemberRole.Writeonrequest.parentNote(same rule asuserCanEdit, a role inherited from an ancestor's team counts) and replies 403You don't have permission to edit the parent noteotherwise. If the body has noparentIdorparentNoteId, there is nothing to check. If it has one butrequest.parentNoteis empty (the route forgotparentNoteResolver), the policy replies 406Parent note not foundinstead of letting the request through.POST /noteuses the resolver and the policy. Since the parent is resolved before the handler, an unknownparentIdno longer leaves an orphan note.parentIdnow gets 406Parent note not foundinstead of 400Note with public id … was not found.note.test.ts:MemberRole.Readboth get 403You don't have permission to edit the parent note;parentIdgets 406 and leaves nonotesornote_historyrow (fails onmainbecause of the orphan);userCanEditParentNote.test.ts: with a parent id in the body but no resolved parent note, the policy replies 406 and does not check the role.Testing
yarn typecheck,yarn eslint srcandyarn vitest runpass locally (169 tests).🤖 Generated with Claude Code