[Server] Recursive alt search with match evidence (#7510)

* [Server] Recursive alt search with match evidence

getUserAlts() now walks a breadth-first graph to the requested depth (clamped 1-3): each level expands frontier accounts over registration email, current client id, recent session IPs (6-month window), and historic session client ids, so identifier changes no longer hide older alts. Every row carries match_reason (email / clientid:<id> / ip:<addr>), depth, and shared_count fan-out so moderators can distinguish real alts from VPN/shared-network overlap. Command_GetUserAlts depth is plumbed through; unset depth defaults to 1, preserving previous single-hop behavior for old clients.

* [Server] Fix alt search review findings

- Seed the BFS with the stored user name so the searched account is not
  re-listed as its own alt at depth 1.
- Normalise emails to lower case for the membership and fan-out checks;
  SQL compares them case-insensitively.
- Widen the per-level row limit by the visited count so level 2+ cannot
  return only already-known accounts.
- Prepare dynamic IN-list queries locally instead of through the shared
  prepared-statement cache, whose keys are query texts.
- Count historic client-id fan-out from _sessions, not from the current
  _users ids.
- Order capped identifiers by relevance (current email/client id first,
  then most recent session IPs) instead of alphabetically.

* Apply suggestion from @ZeldaZach

Co-authored-by: Zach H <zahalpern+github@gmail.com>

* Apply suggestion from @ZeldaZach

Co-authored-by: Zach H <zahalpern+github@gmail.com>

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: Zach H <zahalpern+github@gmail.com>
This commit is contained in:
BruebachL 2026-11-08 08:14:03 +01:00 • committed by GitHub
parent a1ff047a43
commit 933db4edd2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 396 additions and 52 deletions

View file

@ -2595,7 +2595,8 @@ Response::ResponseCode AbstractServerSocketInterface::cmdGetUserAlts(const Comma
}
Response_UserAlts *re = new Response_UserAlts;
const QList<ServerInfo_UserAlt> alts = sqlInterface->getUserAlts(userName);
const int depth = cmd.has_depth() ? static_cast<int>(cmd.depth()) : 1;
const QList<ServerInfo_UserAlt> alts = sqlInterface->getUserAlts(userName, depth);
for (const ServerInfo_UserAlt &alt : alts) {
re->add_alts()->CopyFrom(alt);
}