fix: comprehensive security hardening from full codebase audit

Addresses 14 findings from the security audit across all severity levels:

CRITICAL:
- C-1: Add user blocking system (migration, DB queries, REST API, WS DM
  send check) to prevent harassment via unconsented DMs
- C-2: Remove server version from unauthenticated /health and /info endpoints
  to prevent fingerprinting

HIGH:
- H-1: Remove dangerous-settings feature from tauri-plugin-http
- H-3: Default allowSelfSigned to false in API client (was hardcoded true)
- H-4: Cap invite expiration to 30 days (720 hours)
- H-5: Add 256KB message size limit to LiveKit WS proxy (prevents OOM)
- H-6: Cap concurrent sessions to 25 per user (evicts oldest on overflow)
- H-8: Restrict /diagnostics/connectivity to ADMINISTRATOR role

MEDIUM:
- M-2: Deny access to legacy NULL-uploader unlinked attachments
- M-4: Log warnings on TOTP plaintext decryption fallback paths
- M-8: Remove acceptInvalidCerts from OG preview fetches
- M-10: Expand file upload blocklist (Java .class, OLE2, WASM, .lnk)
- M-12: Add LIMIT to ListInvites (200) and ListMembers (1000)
- M-14: Add CHECK constraint trigger on channels.type (text/voice/dm)

https://claude.ai/code/session_01KKo3RwjdmcNzkgXNfUkgNT
This commit is contained in:
Claude
2026-04-04 16:48:57 +00:00
parent 330fd8eed7
commit 1673c37b9c
17 changed files with 395 additions and 31 deletions
+27
View File
@@ -26,6 +26,14 @@ func MountDMRoutes(r chi.Router, database *db.DB, broadcaster DMBroadcaster) {
r.Get("/", handleListDMs(database))
r.Delete("/{channelId}", handleCloseDM(database, broadcaster))
})
// User blocking routes — prevent DM creation and messaging.
r.Route("/api/v1/blocks", func(r chi.Router) {
r.Use(AuthMiddleware(database))
r.Get("/", handleListBlocks(database))
r.Put("/{userId}", handleBlockUser(database))
r.Delete("/{userId}", handleUnblockUser(database))
})
}
// createDMRequest is the JSON body for POST /api/v1/dms.
@@ -101,6 +109,25 @@ func handleCreateDM(database *db.DB) http.HandlerFunc {
return
}
// Check if either user has blocked the other.
blocked, blockErr := database.IsEitherBlocked(user.ID, req.RecipientID)
if blockErr != nil {
slog.Error("handleCreateDM IsEitherBlocked", "err", blockErr,
"user_id", user.ID, "recipient_id", req.RecipientID)
writeJSON(w, http.StatusInternalServerError, errorResponse{
Error: "INTERNAL_ERROR",
Message: "failed to check block status",
})
return
}
if blocked {
writeJSON(w, http.StatusForbidden, errorResponse{
Error: "BLOCKED",
Message: "cannot create DM with this user",
})
return
}
// Get or create the DM channel.
ch, created, err := database.GetOrCreateDMChannel(user.ID, req.RecipientID) //nolint:contextcheck // TODO: propagate context through this call path
if err != nil {