diff --git a/internal/store/pg/channel_contacts.go b/internal/store/pg/channel_contacts.go index 807123fd..2563b090 100644 --- a/internal/store/pg/channel_contacts.go +++ b/internal/store/pg/channel_contacts.go @@ -152,14 +152,20 @@ func (s *PGContactStore) GetContactsBySenderIDs(ctx context.Context, senderIDs [ placeholders[i] = fmt.Sprintf("$%d", i+1) args[i] = id } + // Tenant scope: without this filter a low-priv user in tenant A could probe + // arbitrary sender_ids and exfiltrate displayName/username from tenant B. + // Sibling reads (GetContactByID, ListMergedContacts) already scope this way. + tid := store.TenantIDFromContext(ctx) + args = append(args, tid) + tenantPH := fmt.Sprintf("$%d", len(args)) query := fmt.Sprintf(`SELECT DISTINCT ON (sender_id) id, channel_type, channel_instance, sender_id, user_id, display_name, username, avatar_url, peer_kind, contact_type, thread_id, thread_type, merged_id, first_seen_at, last_seen_at FROM channel_contacts - WHERE sender_id IN (%s) - ORDER BY sender_id, last_seen_at DESC`, strings.Join(placeholders, ",")) + WHERE sender_id IN (%s) AND tenant_id = %s + ORDER BY sender_id, last_seen_at DESC`, strings.Join(placeholders, ","), tenantPH) rows, err := s.db.QueryContext(ctx, query, args...) if err != nil { diff --git a/internal/store/sqlitestore/contacts.go b/internal/store/sqlitestore/contacts.go index afc6c334..a48aabe7 100644 --- a/internal/store/sqlitestore/contacts.go +++ b/internal/store/sqlitestore/contacts.go @@ -155,11 +155,15 @@ func (s *SQLiteContactStore) GetContactsBySenderIDs(ctx context.Context, senderI placeholders[i] = "?" args[i] = id } + // Tenant scope — parity with PG store. Prevents cross-tenant probing of + // displayName/username by a tenant-A user supplying tenant-B sender_ids. + tid := store.TenantIDFromContext(ctx) + args = append(args, tid) // SQLite has no DISTINCT ON; emulate with GROUP BY + MAX rowid trick via subquery query := `SELECT ` + contactSelectCols + ` FROM channel_contacts - WHERE sender_id IN (` + strings.Join(placeholders, ",") + `) + WHERE sender_id IN (` + strings.Join(placeholders, ",") + `) AND tenant_id = ? GROUP BY sender_id ORDER BY sender_id, last_seen_at DESC`