Root cause
Authentication & access control introduced
The new following list built each entry itself and included the followed account's email address in the response to a normal signed-in user.
How AI contributed: Direct introductionPublished Aug 10, 2026 in
misp/cti-transmuteAll 4 findings in this repository
The following list arrived in a change that also brought notifications and other account features, and its handler assembled each entry by hand instead of using the existing user serializer. That inline dictionary carried the followed account's email address to whoever called the endpoint. The route only requires a login and returns the follow list of the current user, so the disclosure is not limited to administrators or to the owners of the listed accounts: anyone with an account could read the addresses of the accounts they follow, even when those accounts never made an address public. The fix drops that key from the response, leaving the account id, the display name and the follow date, and does not touch the administrator-only serializers that still return the field.
The handler that lists the accounts a user follows builds each entry as an inline dictionary and puts the followed account's email address in it. The route is available to every signed-in user and is not limited to administrators, so any account can read the addresses of the accounts it follows, including users who never published one. The general user serializer already in the project was not used here and keeps its own copy of the field behind administrator-only endpoints, so this list is the exposed path. The fix removes the email key from the list entries and leaves the account id, the name and the follow date.
Root cause
The new following list built each entry itself and included the followed account's email address in the response to a normal signed-in user.
Fix
The email address was removed from the following list entries, which now return the account id, the name and the follow date.
Case facts
@@ -227,0 +269,18 @@ def delete_user(id) -> redirect:+@account_blueprint.route("/get_following", methods=['GET'])+@login_required+def get_following():+ page = request.args.get('page', 1, type=int)+ search = request.args.get('search', '', type=str) or None+ pagination = AccountModel.get_following(current_user.id, page=page, search=search)+ items = []+ for f in pagination.items:+ user = AccountModel.get_user(f.followed_id)+ if user:+ items.append({+ "user_id": user.id,+ "name": f"{user.first_name} {user.last_name}",+ "email": user.email,+ "since": f.created_at.strftime('%Y-%m-%d') if f.created_at else None+ })+ return {"success": True, "list": items, "total_page": pagination.pages}, 200+@@ -428,7 +428,6 @@ def get_following(): items.append({ "user_id": user.id, "name": f"{user.first_name} {user.last_name}",- "email": user.email, "since": f.created_at.strftime('%Y-%m-%d') if f.created_at else None }) return {"success": True, "list": items, "total_page": pagination.pages}, 200AI-assisted change ba66c847e1c722884b09b4cfc2cbad4b6983645a50bc0a252500d2111c124582 · Fix 76ea3556d175c5763c18dbe6a9f6c4f763a670e972eeadb3614553dcdb9e938e