Root cause
Authentication & access control introduced
The reaction endpoint validated the comment id and the emoji only, so it never established that the caller could see the comment being changed.
How AI contributed: Direct introductionPublished Aug 11, 2026 in
misp/cti-transmuteAll 4 findings in this repository
Comments, replies and emoji reactions arrived together in one change, and the reaction handler added there trusted whatever comment id the request carried. It validated only that the field was present and that the emoji was one of the five allowed ones, then passed both to the reaction service, which writes to the comment reaction table without consulting the visibility rules. The same change also added the helper that decides whether a comment may be seen by a given user, but that helper is applied when comments are listed, not on the reaction path. Because the handler never loads the comment or the conversion it belongs to, a signed-in user could react to a comment on a private conversion or to another user's private comment by sending its id. The fix closes the gap by loading the comment and its conversion and by requiring the visibility check before the reaction is toggled.
The reaction handler was added in the same change that introduced comments and their visibility rules. It reads a comment id and an emoji from the request body, checks only that the id is present and that the emoji is in the allowed list, and then calls the reaction service, which performs no authorization of its own. The visibility helper written in the same change was wired into the comment reading path only, so any signed-in user could add or remove a reaction on a comment attached to a private conversion, or on another user's private comment, simply by knowing the comment id.
Root cause
The reaction endpoint validated the comment id and the emoji only, so it never established that the caller could see the comment being changed.
Fix
The endpoint now loads the comment and its conversion and refuses the request when the caller is not allowed to see the comment.
Case facts
@@ -748,0 +881,23 @@ def get_history_details():+@convert_blueprint.route("/react", methods=['POST'])+@login_required+def react():+ """Toggle an emoji reaction on a comment."""+ data = request.get_json(silent=True) or {}+ comment_id = data.get('comment_id')+ emoji = data.get('emoji', '').strip()++ allowed_emojis = ['👍', '😊', '❤️', '🎯', '⚠️']+ if not comment_id or emoji not in allowed_emojis:+ return {"success": False, "message": "Invalid request", "toast_class": "danger"}, 400++ success, added = ConvertModel.react_to_comment(comment_id, current_user.id, emoji)+ if not success:+ return {"success": False, "message": "Failed to update reaction", "toast_class": "danger"}, 500++ return {+ "success": True,+ "added": added,+ "message": "Reaction added" if added else "Reaction removed",+ "toast_class": "success"+ }, 200+@@ -1279,9 +1279,16 @@ def react(): if not comment_id or emoji not in allowed_emojis: return {"success": False, "message": "Invalid request", "toast_class": "danger"}, 400 + comment = comments_repo.get(comment_id)+ if not comment or comment.is_deleted:+ return {"success": False, "message": "Comment not found", "toast_class": "danger"}, 404+ conversion = conv_repo.get(comment.conversion_id)+ if not conversion or not access.can_see_comment(current_user, comment, conversion):+ return {"success": False, "message": "Permission denied", "toast_class": "danger"}, 403+ try: added = comments_repo.toggle_reaction(comment_id, current_user.id, emoji)- except Exception: # noqa: BLE001 - e.g. reacting to a deleted comment (FK)+ except Exception: # noqa: BLE001 - e.g. the comment hard-deleted mid-request (FK) db.session.rollback() return {"success": False, "message": "Failed to update reaction", "toast_class": "danger"}, 500 AI-assisted change e970f5ae07eb471bc1a143dacd7045ec80e26375a462ce8288f45b28ff043457 · Fix ef590c5d00cef9322c27709fea85939ec92c3309d9a1219a7fa5540dce04e3b9