fix(store): tenant-scope GetContactsBySenderIDs to prevent cross-tenant leak

This commit is contained in:
viettranx committed 2026-04-18 22:49:48 +07:00
1 parent 9333fee5b6
commit 4bc7ca1320
2 files changed
+13 -3

No files matched your search

+8 -2
View File
@@ -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 {
+5 -1
View File
@@ -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`