IDOR: Reaction endpoints bypass conversation permission checks
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 3.6k
- Forks
- 398
- Avg merge
- 1h 14m
- Merged PRs (30d)
- 2
Description
Summary
The addReaction and removeReaction methods in server/services/core/chat/message.service.ts bypass the checkConversePermission() check that all other message operations use. This allows any authenticated user to add/remove reactions on messages in conversations they are not a member of, by specifying the target message's MongoDB ObjectID.
Vulnerable Code
In message.service.ts, the addReaction method (around line 492) and removeReaction method (around line 533) perform a direct findById(messageId) without any permission verification:
// addReaction - NO permission check
const message = await this.adapter.model.findById(messageId);
// removeReaction - NO permission check
const message = await this.adapter.model.findById(messageId);
Secure Comparison
All other message operations correctly call checkConversePermission() before accessing messages:
sendMessage(line ~201):await this.checkConversePermission(ctx, converseId, groupId)getMessage/deleteMessage/recallMessage/editMessage/fetchConverseLastMessages/fetchNearbyMessage: All callcheckConversePermission()
The checkConversePermission() method (line ~577) validates that the user is either a member of the group's panel, or a participant in the DM conversation.
Impact
- Severity: Medium - Requires valid MongoDB ObjectID (not easily guessable), but allows cross-conversation reaction manipulation
- Any authenticated user can add emoji reactions to messages in private groups/DMs they don't belong to
- Any authenticated user can remove other users' reactions from messages they can't access
- Reveals message existence (side-channel information disclosure)
Suggested Fix
Add checkConversePermission() call at the start of both addReaction and removeReaction:
async addReaction(ctx, messageId, emoji) {
const message = await this.adapter.model.findById(messageId);
if (!message) throw new Error('Message not found');
// Add this permission check:
await this.checkConversePermission(ctx, String(message.converseId), message.groupId ? String(message.groupId) : undefined);
// ... rest of the method
}
Apply the same pattern to removeReaction.
Discovery
Found through automated security research comparing permission patterns across message operation endpoints.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in server/services/core/chat/message.service.ts at addReaction and removeReaction, then compare their message lookup with checkConversePermission() and the other message operations listed in the issue. Ensure both reaction paths verify the message conversation and group before changing reactions; done means unauthorized users cannot affect or infer messages while permitted reaction behavior remains intact.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- mongodb, typescript
- Domain
- authorization, security
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100