fix: reject non-string tag and conversationId in forced-retention helpers

The bookmark-tag and conversation ids passed to the forced-retention
helpers come from untyped request bodies, so a crafted PUT /api/tags
body like {"tag": {"$gt": ""}} reached Conversation.find({ tags }) as a
query operator and matched every tagged conversation instead of one,
bulk-converting them under ephemeral retention (NoSQL operator
injection). The same applied to req.body.conversationId on POST.

Guard applyForcedRetention and applyForcedRetentionToTag to ignore any
non-string conversationId/messageId/tag, and pass a guaranteed string
from the tag rename route.
This commit is contained in:
Marco Beretta 2026-06-24 15:43:46 +02:00
parent e764c983cf
commit aae066dca6
No known key found for this signature in database
GPG key ID: D918033D8E74CC11
3 changed files with 38 additions and 2 deletions

View file

@ -98,7 +98,8 @@ router.put('/:tag', configMiddleware, async (req, res) => {
const decodedTag = decodeURIComponent(req.params.tag);
const tag = await updateConversationTag(req.user.id, decodedTag, req.body);
if (tag) {
await enforceForcedRetentionForTag(req, req.body?.tag || decodedTag, 'PUT /api/tags/:tag');
const renamedTag = typeof req.body?.tag === 'string' ? req.body.tag : decodedTag;
await enforceForcedRetentionForTag(req, renamedTag, 'PUT /api/tags/:tag');
res.status(200).json(tag);
} else {
res.status(404).json({ error: 'Tag not found' });