The restore handler was overwriting the live SQLite database while the
old *sql.DB handle remained open. Now: broadcasts server_restart to
clients, checkpoints WAL, closes the DB connection, then copies the
backup file over the closed database. Server must restart after restore.
Voice states were loaded with GetAllVoiceStates across the entire server,
leaking who was in hidden voice channels. Now voice states are filtered
through the visible channel set before inclusion in the ready payload.
Updated tests to use explicit roles where voice state visibility matters.
If GetRoleByID fails or returns nil during WebSocket connect, the
server now disconnects the client instead of serving a permissive
ready payload with all channels visible. In buildReady, nil role is
now treated as zero-access (no channels) instead of full-access.
Updated tests to pass explicit Owner role where channel visibility
is expected.
DM channel rows were returned by ListChannels and included in both
the REST channel list and the WebSocket ready payload. Added type="dm"
skip in handleListChannels and buildReady filter loops. DMs are already
delivered separately via dm_channels. 2 new tests verify exclusion for
both member and admin roles.
Private attachments were accessible without authentication if the UUID
was known. Added AuthMiddleware to the GET /api/v1/files/{id} route,
uploader_id tracking on uploads, and channel-level permission checks
(guild READ_MESSAGES, DM participant, admin bypass) in handleServeFile.
Migration 010 adds uploader_id column to attachments table.
8 new access-control tests covering all authorization paths.
BUG-122: Remove channelID==0 bypass in deliverBroadcast that leaked
all channel-scoped broadcasts to unfocused clients. Clients must now
send channel_focus to receive channel events.
BUG-126: Reject edits and reactions on soft-deleted messages in
handleChatEdit and handleReaction.
BUG-108: Revoke all other sessions when a user changes their password
or enables/disables TOTP 2FA. Adds DeleteOtherSessions DB function.
7 new test cases covering all three fixes.
- voice_join.go: treat GetVoiceState DB error as switch failure instead
of silently proceeding (HIGH: could bypass capacity check)
- voice_leave.go: move ctx to first parameter per Go idiom, remove
nolint:revive directive (MEDIUM: style compliance)
- Update all call sites for new parameter order
- BUG-084: Broadcast filter now delivers channel messages to unfocused
clients (channelID==0) instead of silently dropping them
- BUG-086: leaveVoiceChannelWithRetry retry goroutine respects context
cancellation and hub stop to prevent leaks on shutdown
- BUG-088: Voice channel switch verifies old state is cleared before
joining new channel, preventing capacity bypass on DB failure
- BUG-089: handleFreshConnect RemoveParticipant goroutine checks hub
stop and documents identity-based targeting safety
- BUG-085: ring buffer EventsSince off-by-one — change < to <= so
afterSeq == oldestSeq returns nil (triggers full ready payload)
- BUG-087: GracefulStop not idempotent — wrap body in sync.Once to
prevent double lkProcess.Stop() on concurrent calls
- BUG-090: FTS query truncation at byte boundary — use []rune
truncation to preserve valid UTF-8 for CJK/emoji input
- BUG-091: updater downloadFile double-closes file on Windows —
add closed sentinel to guard defer against explicit Close()
The splitStatements function naively split on every semicolon, breaking
CREATE TRIGGER definitions that contain semicolons inside their
BEGIN...END bodies. Now tracks depth so trigger bodies are kept intact.
Fixes TestWebhook_ParticipantLeft_NoDoubleBroadcast_AfterFreshCleanup
and TestWebhook_ParticipantLeft_OldToken_DoesNotTeardownReplacement.
The migration runner now splits multi-statement SQL files and executes
each statement individually. "duplicate column name" errors are skipped
since the column already exists from a prior partial run. This fixes a
crash on startup when migration 004_voice_optimization.sql re-ran
against a database that already had the columns.
Install and configure mutation testing (Stryker for client, go-gremlins
for server), load testing (k6), chaos testing (toxiproxy), WAF middleware
(Coraza with OWASP rules, opt-in via waf_enabled config), and Zod for
runtime schema validation. All tools verified building cleanly.
- Add syncutil package with deadlock-detecting mutex (build-tag switchable)
- Add main_test.go TestMain scaffolding across all server packages
- Harden concurrency in ws, admin, auth, and updater packages
- Update CI workflow, go.mod/sum, Cargo.lock, and root changelogen tooling
Wrap navigateTo() and renderContent() in try/catch blocks. Show visible
error message with "Back to Dashboard" recovery button on failure.
Add null guard on content element and stale-navigation guard on async paths.
PATCH /api/v1/users/me — update username/avatar
PUT /api/v1/users/me/password — change password with old pw verification
GET /api/v1/users/me/sessions — list active sessions (single SQL query)
DELETE /api/v1/users/me/sessions/:id — revoke session with ownership check
New files: profile_handler.go, profile_queries.go + tests for both.
All endpoints follow existing writeJSON/errorResponse patterns.
Create Server/api/constants.go (26 constants) and Server/auth/constants.go
(6 constants) for rate limits, timeouts, size limits, and token generation.
Replace all inline magic numbers with descriptive names across 9 source files.
No behavior changes — same values, just named for contributor readability.
CRITICAL (5):
- Hub panic recovery now calls h.Stop() after 3 panics (ws/hub.go)
- Ring buffer EventsSince returns non-nil empty slice for current seq (ws/ringbuffer.go)
- PTT event listener stores unsubscribe handle to prevent leak (ptt.ts)
- verifyTotp respects config.allowSelfSigned instead of hardcoding (api.ts)
- ptt_listen_for_key uses spawn_blocking to avoid thread pool starvation (ptt.rs)
HIGH - Server (13):
- TOTP rate-limit checked after body decode; counters reset on success
- TOTP enable returns 409 if already enabled (must disable first)
- Global search pre-computes accessible channel IDs for FTS WHERE clause
- DeleteAccount queries roles by name instead of hard-coded IDs
- BackupToSafe uses absClean in VACUUM INTO
- Voice camera slot uses atomic EnableCameraIfUnderLimit DB method
- readPump snapshots voiceChID before unregister for TOCTOU safety
- Voice join sets state after token send; rollback takes broadcast flag
- Updater download uses probe pattern instead of overflow write
- Webhook checks Authorization header before reading body
- Storage.Save adds fsync and fixes double-close
- Default WS origin denies cross-origin (was: accept all)
HIGH - Client (6):
- WS reconnect uses generation counter to discard stale events
- AudioPipeline uses generation counter against stale worklet callbacks
- Screenshare mute state preserved across reconnect (not full leave)
- handleVoiceToken uses iterative loop instead of unbounded recursion
- store.ts re-entrancy guard with pending update queue
- Notification AudioContext cleaned up on logout
Reviewed by 4 parallel agents across Server Core, Server Realtime,
Client & Tauri, and Security. 55 total findings; 24 CRITICAL+HIGH
fixed here, 31 MEDIUM+LOW tracked in vault backlog (T-265–T-295).
- C-3: inject setupLimiter into NewAdminAPI instead of package-level global
- H-5: generateRandomKey returns error instead of panicking
- H-6: replace init() bcrypt with sync.Once lazy initialization
- M-2: remove unsafe-inline from admin CSP
- M-3: sanitize upload filenames (strip control chars, truncate to 255)
- M-5: truncate User-Agent to 512 bytes before storing as device
- M-10: MaxBodySizeUnless uses prefix matching instead of exact path
- M-12: wrap seedExistingDatabase in a single transaction
- M-13: use errors.Is for EOF check in upload handler
- M-14: log writeJSON encoding errors instead of discarding
- M-16: standardize error codes to INTERNAL_ERROR across all handlers
- C-3: inject setupLimiter into NewAdminAPI instead of package-level global
- H-5: generateRandomKey returns error instead of panicking
- H-6: replace init() bcrypt with sync.Once lazy initialization
- M-2: remove unsafe-inline from admin CSP script-src and style-src
- M-3: sanitize upload filenames (strip control chars, truncate to 255)
- M-5: truncate User-Agent to 512 bytes before storing as device
- M-10: MaxBodySizeUnless uses prefix matching instead of exact path
- M-12: wrap seedExistingDatabase in a single transaction
- M-13: use errors.Is for EOF check in upload handler
- M-14: log writeJSON encoding errors instead of discarding
- M-16: standardize error codes to INTERNAL_ERROR across all handlers
Move handleVerifyTOTP, handleEnableTOTP, handleConfirmTOTP, and
handleDisableTOTP along with their request/response types into a
dedicated totp_handler.go file, bringing auth_handler.go from 829
to 583 lines.
Add audit log calls for TOTP lifecycle events:
- totp_verified on successful 2FA login verification
- totp_enabled on successful TOTP enrollment confirmation
- totp_disabled on successful TOTP removal
Cover low-coverage handler functions: handleGetPins, handleSetPinned,
handleMetrics, handleDiagnosticsConnectivity, isPrivateIP, AdminIPRestrict,
handleClientUpdate, and handleLiveKitHealth. Adds 30 new test cases across
4 new test files and 2 modified test files.
Comprehensive tests for handleUpload and handleServeFile covering:
- Route mounting verification
- Successful text and PNG uploads with response validation
- Authentication enforcement (missing/invalid tokens)
- Missing file field and invalid multipart form
- Blocked file types (PE, ELF, Mach-O, shell scripts)
- DB record creation and unlinked message_id
- File serving with correct Content-Type, Cache-Control, Content-Disposition
- File not found (missing DB record and missing storage file)
- CORS headers (matching, non-matching, wildcard, no origin)
- Full upload-then-serve round-trip for text and PNG
Coverage: MountUploadRoutes 100%, handleUpload 78.6%, handleServeFile 83.9%
- Video grid: sync stream type attribute on updates, add screenshare data attribute
- Dispatcher: handle voice_token messages, improve video track event handling
- LiveKit session: add video track publication support
- Hub: stale client timeout cleanup, improved voice state management
- Voice join/leave: context propagation, better error handling
- Livekit webhook: structured event handling with room/participant data
- Server DB: voice query improvements, new test coverage
- WS integration tests: expanded coverage for voice and LiveKit flows
- Gitignore: add internal dev tools directory, owncord-server.exe
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CSP nonce policy blocked all onclick handlers, preventing navigation.
Switched to 'unsafe-inline' (admin panel is IP-restricted). Also fixed
boolean settings display — toggles now accept '1' from the database.
- Fix race condition: handleDisconnected no longer nulls the room during
initial connect, allowing the retry loop to complete all 3 attempts
- Fix TLS proxy port: default to 443 instead of 8443 when server host
has no explicit port (servers behind nginx/reverse proxy)
- Fix cert store key: strip :443 suffix so LiveKit proxy fingerprint
lookup matches ws_proxy's stored key format
- Add node_ip config option for LiveKit WebRTC ICE candidates (required
for remote users behind NAT)
- Add resolved URL to all connection error/retry/reconnect logs for
easier debugging
- Add diagnostic logging to resolveLiveKitUrl showing which path was
taken (direct/proxy/passthrough)