Commit Graph
193 Commits
Author SHA1 Message Date
Claude 05face4b2a migrate upload_handler and profile_handler to service layer
- upload_handler.go: uses PermissionService.HasChannelPerm instead of
  the deleted hasChannelPermREST helper
- profile_handler.go: delegates to UserService for profile updates,
  password changes, session listing, and session revocation
- Remove hasChannelPermREST from channel_handler.go (no longer needed)
- UserService.UpdateProfile now returns ErrConflict on duplicate username

https://claude.ai/code/session_01CBFF3r84ywkJRWwuqw8zD8
2026-04-05 21:31:35 +00:00
Claude c9099f04e7 fix compile errors and wire permission cache invalidation
Critical fixes:
- Move svc creation in router.go above MountInviteRoutes/MountChannelRoutes
  (was used before definition — compile error)
- Restore hasChannelPermREST in channel_handler.go for upload_handler.go
  (was removed but still referenced — compile error)

Permission cache invalidation:
- Add PermissionInvalidator interface to admin package
- Wire through NewHandler → NewAdminAPI → handlePatchUser
- Call InvalidateUser(userID) after role changes in admin panel
- Update all admin test files to pass nil as new parameter

Also clarifies WithTx documentation for SQLite single-writer semantics.

https://claude.ai/code/session_01CBFF3r84ywkJRWwuqw8zD8
2026-04-05 21:25:35 +00:00
Claude 1bf3ca5de3 implement full three-tier priority queue system
Client now has three send channels:
- sendHigh (64 slots): DMs, mentions — drained first by writePump
- send (256 slots): chat messages, reactions — drained second
- sendLow (64 slots): typing, presence — drained last, dropped on overflow

writePump drains high-priority messages before checking normal/low.
PubSub gains PublishHigh/PublishLow alongside existing Publish.
EmitEvents routes events by priority:
- High: SequencedDMEvent, UserTargetedEvent
- Normal: ChannelEvent, VoiceChannelEvent
- Low: ExcludeSenderEvent (typing), PresenceEvent

Slow clients get typing/presence dropped first (sendLowMsg silently
drops), then disconnect on normal buffer overflow, ensuring DMs are
never lost to typing indicator backpressure.

https://claude.ai/code/session_01CBFF3r84ywkJRWwuqw8zD8
2026-04-05 21:12:08 +00:00
Claude c2bf9304e4 add low-priority pub/sub delivery for typing/presence events
PublishLowPriority uses trySendMsg to silently drop messages when a
client's buffer is full, instead of disconnecting them. This provides
priority-based backpressure: chat messages use normal Publish (disconnect
on overflow), while ephemeral events like typing indicators and presence
updates use PublishLowPriority (drop on overflow).

https://claude.ai/code/session_01CBFF3r84ywkJRWwuqw8zD8
2026-04-05 21:02:54 +00:00
Claude ccb80d8a83 complete service layer migration + store.Store integration
- Add UserService, DMService, InviteService, BlockService
- Migrate REST handlers (channel, DM, invite, block) to use services
- Remove block_handler.go (merged into dm_handler.go)
- Update all services to accept store.Store instead of *db.DB
- Router creates SQLiteStore and passes to service.New()

Handlers are now thin HTTP adapters: parse request → call service →
map error → write JSON. All business logic lives in the service layer.

https://claude.ai/code/session_01CBFF3r84ywkJRWwuqw8zD8
2026-04-05 21:01:29 +00:00
Claude 87055819b9 wire pub/sub channel subscriptions into channel_focus handler
When a client focuses a channel, subscribe to its pub/sub topic.
When switching channels, unsubscribe from the old topic first.
This completes the pub/sub integration — channel broadcasts now
route only to clients subscribed to the relevant topic.

https://claude.ai/code/session_01CBFF3r84ywkJRWwuqw8zD8
2026-04-05 20:52:02 +00:00
Claude 075ef28e29 add pub/sub broadcast model, replace iterate-and-filter (Phase A, Step 5)
Introduce topic-based PubSub for O(subscribers) message routing:
- Clients subscribe to "global" and "user:{id}" on connect
- Channel broadcasts route through "channel:{id}" topics
- deliverBroadcast() uses PubSub instead of iterating all clients
- UnsubscribeAll on disconnect/kick cleans up subscriptions
- Sequence numbering and replay buffer preserved unchanged

https://claude.ai/code/session_01CBFF3r84ywkJRWwuqw8zD8
2026-04-05 20:51:06 +00:00
Claude 20199fcfca add store.Store interface and SQLiteStore (Phase A, Step 3)
Define the Store interface composing domain-specific sub-interfaces
(MessageStore, ChannelStore, UserStore, etc.) that decouple services
from the concrete database. SQLiteStore wraps *db.DB, delegating all
operations to existing query methods.

https://claude.ai/code/session_01CBFF3r84ywkJRWwuqw8zD8
2026-04-05 20:50:28 +00:00
Claude e54ad46079 migrate WS chat/reaction/presence handlers to service layer
Handlers now delegate all business logic (validation, permission checks,
DB operations) to MessageService and ChannelService instead of calling
*db.DB directly. This eliminates logic duplication and enables the
service layer's permission cache.

https://claude.ai/code/session_01CBFF3r84ywkJRWwuqw8zD8
2026-04-05 20:47:47 +00:00
Claude 1c5fbbc246 add service layer foundation (Phase A, Step 1)
Introduce Server/service/ package with MessageService, ChannelService,
and PermissionService that encapsulate business logic previously
scattered across REST and WS handlers. The PermissionService adds
per-user in-memory caching with TTL-based expiry to eliminate
per-message DB round-trips at scale.

Services are wired into the WS hub via deps structs (strangler-fig
pattern) — existing handlers continue to work unchanged, with service
references available for incremental migration.

https://claude.ai/code/session_01CBFF3r84ywkJRWwuqw8zD8
2026-04-05 20:42:32 +00:00
J3vb a91ecd093b fix: resolve unparam lint errors in server build
- Remove unused `ver` param from handleHealth and handleInfo (version
  was intentionally removed from unauthenticated endpoints per C-2)
- Rename decodeBase64Loose to validateBase64Loose returning only error,
  matching actual usage (all callers only validate, never use the bytes)
2026-04-05 21:20:00 +02:00
J3vb e2f8858019 fix: resolve CI lint and ESLint failures
- Remove commented-out code flagged by gocritic
- Use bytes.Equal instead of string conversion comparison
- Remove unused buildRateLimitError function
- Remove unnecessary type assertions in e2eeCrypto.ts
2026-04-05 19:37:00 +02:00
J3vb 6e4a007b91 refactor: migrate WS handlers to V2 Command/Event architecture
Strangler-fig migration of 15 WebSocket handlers from V1 (Hub method,
*Client) to V2 (pure functions: Command, ClientInfo, deps -> Result).
V2 handlers are testable without a running Hub and produce declarative
Result values that the dispatch loop applies.

New abstractions:
- Command interface + typed constructors with input validation
- 7 Event routing interfaces (Channel, ExcludeSender, SequencedDM,
  UserTargeted, BroadcastAll, VoiceChannel, VoiceChannelGuarded)
- Per-domain deps structs (PingDeps, ChatDeps, PresenceDeps,
  ReactionDeps, VoiceDeps) with interface-based DI
- EmitEvents router matching events to delivery mechanisms
- DispatchV2 with panic recovery and runtime.Stack logging

Security hardening:
- Pre-sanitize byte length guard before bluemonday (DoS prevention)
- GetRoleForUser single-JOIN query avoids password hash on hot path
- channel_id positivity enforced in all command constructors
- Log injection prevention: msgType/reqID capped to 64 chars
- Nil KeyHolder dep returns ErrCodeInternal (not silent bypass)
- VoiceChannelGuardedEvent atomic check-and-send under h.mu.RLock

V1-only (complex state/mutex requirements): voice_join, voice_leave.

All tests pass with -race. No CI regressions expected.
2026-04-05 19:03:22 +02:00
J3vb 5239a75642 Merge pull request #1126 from J3vb/claude/security-audit-full-mmIrL
Force merge: CI issues will be fixed in upcoming Command/Event architecture refactor
2026-04-05 11:32:26 +02:00
J3vb 36e81ebec7 fix: address code review IMPORTANT issues — atomic TOCTOU, updateKeyHolder race, dead keyReceived var 2026-04-04 23:30:36 +02:00
J3vb 3d18ce4f99 fix: Go E2EE security hardening — key holder tracking, base64 loose validation, rate limits, test schema
- I-1: Add key holder election in Hub (lowest userID per channel); reject
  non-key-holder voice_e2ee_offer with NOT_KEY_HOLDER error
- I-2: Accept raw (unpadded) base64 in E2EE announce/offer handlers via
  decodeBase64Loose fallback
- I-6: Copy E2EE public key value while h.mu.RLock is held in getClientE2EEPubKey
- I-7: Lower loginRateLimitPerMinute from 60 to 5
- C-1: TOCTOU fix — target channel check held under same lock as client lookup
- C-2: Include is_key_holder bool in voice_token payload so client knows
  whether to initiate key distribution
- M-5/M-6: Add ErrCodeBadPayload/ErrCodeNotKeyHolder error constants
- Fix pre-existing api build errors: block_handler.go getUserFromContext,
  router.go RequirePermission arg count
- Add user_blocks table to all test DB schemas (ws, api DM)
- Add voice_e2ee_test.go and constants_test.go covering all fixes
2026-04-04 23:16:31 +02:00
J3vb f962d59cd6 fix: update tests and fix pending-join drain regression for CI
Update LiveKitSession tests to use _state discriminated union instead of
old flat field names (room, currentChannelId, latestToken, etc.) removed
in the state machine refactor. Also fix renderers.test.ts URL resolution
by setting a server host in beforeEach so isSafeUrl can parse relative
attachment URLs in jsdom. Stage all four Go test files so the CI Go job
runs them.

Additionally fix a regression in connectAndSetup's finally block: when a
pendingJoin is queued during a stale-join abort, preserve the connecting
state so handleVoiceToken's drain loop can pick it up rather than losing
it by resetting to idle.
2026-04-04 21:50:57 +02:00
Claude 774a7bcce9 fix: remaining E2EE hardening — rotation, retry, fingerprint, validation
Periodic key rotation:
- Key holder rotates room key every 5 minutes for forward secrecy,
  independent of participant changes. Timer managed by key holder only.

Offer retry mechanism:
- Non-key-holders re-announce their public key after 10s timeout and
  wait 5s more before giving up. Covers lost offers from target
  disconnect during async key wrapping.
- Key holder now re-sends room key offer on duplicate announces (peer
  may be re-requesting after a missed offer), instead of ignoring them.

Key fingerprint verification:
- New computeKeyFingerprint() in e2eeCrypto.ts — SHA-256 hash of raw
  public key formatted as "AB12 CD34 ..." for out-of-band verification.
  Can be displayed in UI for MITM detection.

Server hardening:
- Public key size limit tightened from 256 to 128 bytes (P-256
  uncompressed = 65 bytes = ~88 base64 chars).

Client hardening:
- WebCrypto availability check at module load — throws descriptive
  error if crypto.subtle is unavailable (non-HTTPS context).
- base64ToUint8() now wraps atob() in try-catch with clear error message.

https://claude.ai/code/session_01KKo3RwjdmcNzkgXNfUkgNT
2026-04-04 19:43:17 +00:00
Claude 277c2d3e76 fix: address critical E2EE review findings — races, validation, reconnect
Critical fixes:
- C1: Key holder election now uses lowest-user-ID from voiceStore instead
  of "am I first in peerPublicKeys" heuristic, preventing simultaneous
  join race where both participants generate conflicting room keys
- C2: TOCTOU race in handleVoiceE2EEOffer — target channel check now
  happens inside h.mu.RLock() section (atomically with client lookup)
- C3: Server now validates base64 encoding for public_key, encrypted_key,
  and iv before relaying, preventing client-side DoS via malformed payloads

High fixes:
- H1: ECDH keypair regenerated on reconnect with fresh announce, so
  stale keys don't persist and key rotation during disconnect is handled
- H2: E2EE epoch counter prevents stale offers from overwriting a
  rotated room key (handleE2EEOffer discards if epoch changed during unwrap)
- H3: After key rotation, re-check for peers that arrived during the async
  wrapping loop and send them the new key too
- H4: _ecdhKeyPair and _roomKey captured in local vars before async
  operations to prevent null dereference if clearE2EEState runs concurrently

Medium fixes:
- M1: User notified via onErrorCallback when E2EE key exchange times out
- M2: Timeout timer properly cleared to prevent leak and unhandled rejection
- M3: Duplicate announces deduplicated — same key ignored, changed key logged

https://claude.ai/code/session_01KKo3RwjdmcNzkgXNfUkgNT
2026-04-04 19:35:41 +00:00
Claude b89a7efa6a fix: harden E2EE key exchange — election, rotation, queuing, error handling
- Use deterministic key holder election (lowest user_id) instead of
  Map insertion order which is not guaranteed to match join order
- Use parseUserId() instead of raw parseInt() for LiveKit identity parsing
- Add concurrent key rotation guard (_rotatingKey flag) to prevent
  races when multiple participants leave in rapid succession
- Queue voice_e2ee_announce messages that arrive before ECDH keypair
  is ready; drain after keypair generation in connectAndSetup
- Propagate decryption failures to roomKeyResolver so connectAndSetup
  unblocks with an error instead of hanging
- Reject (not resolve) roomKeyResolver on voice leave for proper cleanup
- Convert dynamic await import("@lib/e2eeCrypto") to static imports
- Add VOICE_E2EE_ANNOUNCE/OFFER to protocolTypes.ts enum constants
- Use typed S.VOICE_E2EE_* constants in dispatcher instead of string casts
- Add payload size limits for encrypted_key (1024) and iv (128) on server

https://claude.ai/code/session_01KKo3RwjdmcNzkgXNfUkgNT
2026-04-04 19:13:51 +00:00
Claude 0c4d9f702c feat: implement true E2EE for voice via client-side ECDH key exchange
Replace server-generated symmetric keys with client-side ECDH P-256 key
exchange. The server now only relays opaque public keys and encrypted
room key blobs — it never sees the actual room encryption key.

Protocol:
- voice_e2ee_announce: clients broadcast ECDH public keys
- voice_e2ee_offer: key holder wraps room key for each peer via ECDH+HKDF+AES-GCM
- Key rotation on participant leave (forward secrecy)

Server changes:
- Remove VoiceE2EEKeys (server-side key generation)
- Add relay handlers for announce/offer messages
- Store per-client ECDH public keys on Client struct
- Send existing public keys to new joiners during voice state sync

Client changes:
- New e2eeCrypto.ts: ECDH P-256, HKDF-SHA256, AES-256-GCM key wrapping
- LiveKitSession generates keypair on join, manages key holder election
- Key holder generates room key and wraps for each peer
- Non-holders wait for offer before connecting to LiveKit
- Room key rotated when any participant leaves

https://claude.ai/code/session_01KKo3RwjdmcNzkgXNfUkgNT
2026-04-04 19:02:21 +00:00
Claude 1d8bfe2d6a fix: revert breaking security changes and update tests for version removal
Restores dangerous-settings and allowSelfSigned which are required for
self-hosted servers with self-signed certificates. Makes HealthResponse.version
optional to match server-side removal, and updates router tests to assert
version is correctly omitted from unauthenticated endpoints.

https://claude.ai/code/session_01KKo3RwjdmcNzkgXNfUkgNT
2026-04-04 18:02:26 +00:00
Claude 1673c37b9c 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
2026-04-04 16:48:57 +00:00
Claude 330fd8eed7 feat: add end-to-end encryption for voice/video via LiveKit SFrame
Server generates a per-channel 256-bit symmetric key (crypto/rand) when
the first participant joins voice. The key is distributed to all
participants via the voice_token WS message (already TLS-encrypted) and
cleared when the channel empties for forward secrecy per session.

Client configures LiveKit Room with ExternalE2EEKeyProvider and an
SFrame e2ee-worker. All audio/video frames are encrypted client-side
before reaching the SFU — the server never sees plaintext media.

Changes:
- Server: new VoiceE2EEKeys store, e2ee_key in voice_token payload
- Client: E2EE Room options, key provider wiring for connect/reconnect
- CSP: added worker-src 'self' blob: for the E2EE Web Worker

https://claude.ai/code/session_01KKo3RwjdmcNzkgXNfUkgNT
2026-04-04 16:34:52 +00:00
J3vb 321428c7f4 feat: security hardening - restrict PTT capture keys, gate devtools to DEV, fix race conditions and DM sequencing
- Restrict PTT key capture to non-text keys only (function, navigation, mouse buttons) via allowlist (BUG-136)
- Gate DevTools button and F12/Ctrl+Shift+I shortcut behind import.meta.env.DEV
- Disable devtools Tauri feature in production (Cargo.toml default feature removed)
- Remove overly broad http:default capability, replace with scoped http:allow-fetch
- Set withGlobalTauri to false to avoid global __TAURI__ surface exposure
- Fix reconnect race: add abort checks after room creation, URL resolve, and connect (BUG-070)
- Fix ws.ts reconnect guard: bail out safely when config is null after disconnect
- Fix DM broadcast double-send and add monotonic seq + replay buffer support via sendSequencedToUsers
- Add seqMu mutex to serialize seq assignment across broadcastDM and deliverBroadcast paths
- Fix handleFreshConnect to unregister client and close connection on buildReady failure
- Add tests for PTT allowlist, ws reconnect config-null guard, livekit abort-after-connect, and DM sequencing
2026-04-04 09:08:01 +02:00
J3vb c3a8aa477c fix: resolve 20 code review bugs across Rust, TypeScript, and Go
Critical/High Rust (Tauri client):
- BUG-140: replace .run() with .build() + RunEvent::Exit handler; native error dialog on startup failure
- BUG-141: eliminate PTT thread TOCTOU race with Mutex critical section; add AtomicBool shutdown and catch_unwind
- BUG-144: fix TOFU cert store corruption — read-before-write rollback restores previous fingerprint on save failure (all 3 write sites)
- BUG-145: add VK code range guard (1..=254) in is_key_down; fix cast to (state as i16) < 0
- BUG-147: replace bare spawns with JoinSet; abort_all + drain on exit; unconditional closed event
- BUG-150: add CRLF guard in handle_connection before header rewriting
- BUG-151: wrap header read loop in tokio::time::timeout(10s)
- BUG-158: extract CERTS_STORE/SETTINGS_STORE to constants.rs (eliminate 3 duplicates)
- HIGH-2: PTT thread self-cleanup uses unwrap_or_else defensive pattern
- HIGH-4: ws_send distinguishes Full vs Closed errors; warn log on backpressure

Critical/High TypeScript (Tauri client):
- BUG-142: join-generation counter prevents stale connectAndSetup completions
- BUG-143: replace 8 mutable LiveKit session fields with discriminated union SessionState
- BUG-146: 60s token refresh deadline; cleared on reply or voice leave
- BUG-148: ResizeObserver hoisted to outer scope; disconnect() in destroy() before ac.abort()
- BUG-152: dismissSignal.aborted guard already present (no change needed)
- BUG-153: measureRendered split into two-pass read-then-write; eliminates per-message reflow
- BUG-154: WS dedup cache batch-evicts to 80% on overflow (amortised O(1))
- BUG-157: pendingUpdates replaced with coalesced function-composition slot (O(1) queue depth)

Go server:
- BUG-149: safe two-value type assertion in getOutboundIP with localhost fallback
- BUG-155: broadcast buffer 256→1024; broadcastDrops atomic counter exposed in /api/v1/metrics
- BUG-156: LiveKitHealthCheck and implementations accept ctx context.Context; all call sites pass r.Context() (12 files)
- BUG-159: MaxMessageBytes constant in config/constants.go; replaces 1<<20 literals in serve.go and updater.go
- HIGH-1: cert store rollback reads old value before write; restores previous cert on save failure

All validation passes: go build, go vet, cargo check, npm typecheck
2026-04-03 23:18:06 +02:00
J3vb 4332541b3a fix: return f.Close() error in downloadFile to catch disk-full flush failures 2026-04-03 14:45:03 +02:00
J3vb d5cbd41bcb feat: add docker-compose with LiveKit for server Docker deployment
- docker-compose.yml: owncord + livekit/livekit-server on shared network
- Secrets (API key/secret) injected via .env → OWNCORD_VOICE_* env vars
- livekit.yaml.example: template config with port ranges and node_ip guidance
- .env.example: secret template with min-length reminder for API secret
- .gitignore: add .env / Server/.env to prevent accidental secret commits

Users: cp .env.example .env && cp livekit.yaml.example livekit.yaml,
fill in values, then docker compose up -d
2026-04-03 14:32:20 +02:00
J3vb 03969575e0 feat: Linux client port, ARM64 CI, and server Docker image
Client:
- Replace Win32 Credential Manager with cross-platform keyring crate
  (Windows Credential Manager / Linux Secret Service / macOS Keychain)
- Add Linux PTT support via device_query crate with VK-code-compatible
  mapping; thread-local DeviceState avoids repeated /dev/input/ opens
- Add AppImage + deb bundle targets to tauri.conf.json with Linux
  metadata and deb runtime dependencies

Cargo.toml:
- Add keyring = "3" (all platforms)
- Add device_query = "2" (Linux only, cfg guard)
- Remove Win32_Security_Credentials feature (no longer needed)

CI/CD:
- Add ubuntu-22.04 and ubuntu-22.04-arm to tauri-build matrix
- Fix Linux deps step condition: startsWith(matrix.os, 'ubuntu')
- Add server Docker build verification job (build-only, no push)
- Add release-client-linux (x86_64) and release-client-linux-arm64
  jobs producing AppImage + deb artifacts
- Add release-server-docker job pushing to ghcr.io on version tags
- Update publish job to include all Linux and ARM64 artifacts

Server:
- Add multi-stage Dockerfile (golang:1.25-bookworm → distroless/static)
- Non-root user (uid 65532), /app/data volume, port 8443 exposed
- Add .dockerignore excluding binaries, data, and local config
2026-04-03 14:26:26 +02:00
J3vb 0b1d178d31 fix: wire parseChecksumFileAny for Linux checksum lookup and add missing mock export
- updater.go: DownloadAndVerify now uses parseChecksumFileAny with
  checksumEntryNamesForGOOS so the linux/ path prefix produced by the
  release workflow's sha256sum is found correctly (fixes
  TestDownloadAndVerify_Success on ubuntu-latest)
- video-grid.test.ts: add setScreenshareAudioVolume to the
  @lib/livekitSession mock so vitest does not throw on the export
  that VideoGrid.ts imports
2026-04-03 12:27:33 +02:00
J3vb 998e06d947 chore: merge dev — resolve conflicts between Linux support and signing hardening
- release.yml: integrate signing/manifest/changelog steps with new
  multi-platform artifact layout (windows/ + linux/ dirs)
- updater.go: combine Linux tar.gz support with existing signature
  verification; merge platform-aware asset matching into switch
- updater_test.go: keep PR Linux tests + dev signing/manifest tests
2026-04-03 08:59:57 +02:00
J3vb 9e399384c3 fix: suppress gosec false positives in Linux server support
- G204 in proc_spawner_nix.go and proc_spawner_win.go: exePath is the
  server's own validated binary path, not arbitrary user input
- G302 in updater.go: 0o755 is required for the extracted Linux binary
  to be executable
2026-04-03 08:45:45 +02:00
J3vb 9f32736278 fix: resolve all 12 golangci-lint issues
- nilerr: mark intentional nil returns in DecryptTOTPSecret (backwards compat for unencrypted legacy secrets)
- gosec G703: suppress path traversal false positives in backup handlers (paths already sanitized by HasPrefix guard)
- contextcheck: thread context through handleWebhookParticipantJoined/Left; nolint goroutine in handleFreshConnect that intentionally detaches from request context
- errcheck: handle fmt.Fprintf return in handleWAFInterruption
- gocritic elseif: flatten else-if chain in upload_handler access check
- gocritic ifElseChain: rewrite asset name matching as switch in updater
- unparam: remove unused totpKey param from handleLogin (TOTP verification handled by separate handleVerifyTOTP endpoint)
2026-04-03 08:33:55 +02:00
J3vb 485040be32 security: validate avatar URLs on server and client
- Add validateAvatarURL helper enforcing https:// scheme, non-empty host, and 512-char max length
- Add rate limiting (10/min) to PATCH /api/v1/users/me profile update endpoint
- Guard avatar rendering in DmSidebar, DmProfileSidebar, and UserProfilePopup with isSafeUrl check to prevent unsafe URL injection in the UI
2026-04-03 08:10:26 +02:00
J3vb e65a7d6a70 fix: harden server update signing 2026-04-02 23:35:11 +02:00
Vladislav Borisov 52a59b064f PR#88 Added server linux support and optimized ci/release workflow for multiplatform server build 2026-04-03 00:04:17 +07:00
J3vb 2762e9a4ef chore: remove soundboard feature entirely
Soundboard was never implemented — remove USE_SOUNDBOARD permission bit,
rate limiter, protocol entry, admin mockup reference, TODOS entry, and
all related test assertions.
2026-04-02 17:00:27 +02:00
J3vb 7cdc2ef1ca feat: broadcast username changes to all connected clients via WebSocket
Add user_update event so other clients see profile changes in real-time
without needing to reconnect. Also updates saved credentials in Windows
Credential Manager when the current user changes their username.

Fixes: livekit-session test mock missing unpublishTrack property.
2026-04-02 16:33:11 +02:00
J3vb 371862861f fix: mount profile routes so PATCH /api/v1/users/me works
MountProfileRoutes was never called in the router, causing 404 for
profile update, password change, and session management endpoints.
2026-04-02 16:04:25 +02:00
J3vb f40e6787a9 fix: resolve remaining LOW security findings (L2-L14)
- Document single-instance requirement for in-memory rate stores (L2)
- Add IsOwnerRole() helper for explicit owner guards (L4)
- WS auth deadline uses request context, not context.Background (L5)
- Double-check voice state before clearing in webhook handler (L8)
- Upload stores measured write size instead of client header.Size (L11)
- Startup warning when config upload size exceeds HTTP body limit (L12)
- Update check endpoint now requires owner role (L13)
- Backup paths resolved to absolute at init time (L14)
2026-04-02 15:22:03 +02:00
J3vb e35e1b346c fix: encrypt TOTP secrets at rest and filter replay buffer by permissions
M1 — TOTP secrets are now AES-256-GCM encrypted before being stored in
the database. Key is auto-generated on first run (data/totp.key) or set
via OWNCORD_TOTP_KEY env var. Existing plaintext secrets are detected
and returned as-is for backwards compatibility.

M3 — Replay buffer events are now tagged with their channel ID. On
reconnect, the server computes the user's current accessible channels
and only replays events from those channels. Global broadcasts (presence,
voice state, member updates) are always replayed. Falls back to full
ready payload if permission computation fails.
2026-04-02 15:05:56 +02:00
J3vb 9e48e8d8e8 fix: security hardening — 11 findings across auth, WS, upload, admin, data
Security audit across all 11 sections (AUTH-001 through DATA-001) found
0 critical, 1 high, 7 medium, 15 low issues. This commit addresses:

- Add json:"-" to User.PasswordHash, User.TOTPSecret, Session.TokenHash
  to prevent accidental serialization of sensitive fields (M7)
- Add X-Content-Type-Options: nosniff to file serve responses (M5)
- Apply owner-only guard to backup list endpoint for consistency (M6)
- Persist rate-limit lockouts to SQLite so they survive restarts (M2)
- Normalize DM non-participant responses to 404 to prevent oracle (L3)
- Add explicit per-entry expiry check in partial auth Lookup/Consume (L1)
- Truncate unknown WS message type to 64 chars before echo (L6)
- Rate-limit ping handler to 2/sec per user (L7)
- Replace raw error strings in update handlers with generic messages (L15)
- Update 4 tests to match new 404 behavior for DM non-participant
2026-04-02 14:51:16 +02:00
J3vb 827b370ac1 fix: audio cleanup srcObject, diagnostics rate limit, orphan cleanup race (BUG-107, BUG-121, BUG-132)
BUG-107: cleanupAllAudioElements now calls pause() and sets
srcObject = null before removing elements from DOM, ensuring streams
are fully released during reconnection cleanup.

BUG-121: Diagnostics endpoint now has 5 req/min rate limit as
documented, preventing enumeration of internal topology.

BUG-132: DeleteOrphanedAttachments uses DELETE ... RETURNING stored_as
(atomic) instead of separate SELECT then DELETE, eliminating the race
where a file could be deleted after its attachment was linked.
2026-04-02 13:45:41 +02:00
J3vb d5ec7ca877 fix: validate proxy IP headers and force attachment for unsafe MIME types (BUG-112, BUG-118)
BUG-112: clientIPWithProxies now validates extracted X-Real-IP and
X-Forwarded-For values with net.ParseIP. Non-IP strings are rejected,
falling back to RemoteAddr. Prevents attackers from choosing arbitrary
rate-limit bucket keys via header injection.

BUG-118: Files with MIME types that could execute active content
(HTML, SVG, XML, PDF) are now served with Content-Disposition: attachment
instead of inline, preventing content hosting under the OwnCord origin.
2026-04-02 13:23:09 +02:00
J3vb fcffdb8d6b fix: add per-user upload rate limit to prevent disk exhaustion (BUG-131)
Upload endpoint now enforces 10 uploads/min per user via the existing
RateLimiter. Previously only body size was capped (100 MiB) with no
per-user throttle, allowing authenticated users to exhaust disk with
repeated uploads.
2026-04-02 13:18:57 +02:00
J3vb ac86fc1ef5 fix: immediately disconnect banned user's WebSocket connection (BUG-113)
BroadcastMemberBan now calls DisconnectUser after broadcasting, which
sends an error message and kicks the client. Previously banned users
retained WS access until the periodic 30s session sweep or 10-message
recheck triggered.
2026-04-02 13:16:06 +02:00
J3vb 50258457e4 fix: per-username login lockout and per-user password confirmation lockout (BUG-110, BUG-111)
BUG-110: Login handler now tracks failures per-username alongside per-IP.
Distributed brute force from rotating IPs is blocked after 9 failures
for the same username within 15 minutes.

BUG-111: Password-change, TOTP enable/confirm/disable endpoints now have
per-user escalating lockout (3 failures / 15min window / 15min lock),
matching the existing delete-account pattern. Prevents password oracle
attacks via stolen session tokens.
2026-04-02 13:12:38 +02:00
J3vb d056229b28 fix: self-signed cert no longer generated as CA (BUG-138)
Changed IsCA to false, removed KeyUsageCertSign, and reduced validity
from 10 years to 2 years. A compromised key can no longer sign
additional certificates trusted by TOFU-pinning clients.
2026-04-02 13:03:02 +02:00
J3vb df9b5ab90f fix: close reconnect event gap and eliminate silent message drops (BUG-123, BUG-124)
BUG-123: Register client BEFORE writing replay/ready data so broadcasts
during the write window queue in the send buffer instead of being lost.
On handshake failure, unregister before closing.

BUG-124: sendMsg/trySendMsg now close the send channel on buffer
overflow, forcing a disconnect → reconnect with replay recovery
instead of silently dropping messages and diverging state.
2026-04-02 12:32:11 +02:00
J3vb 5a417def52 fix: harden LiveKit tokens — 5min TTL, source restrictions, webhook validation (BUG-127, BUG-128)
BUG-127: Reduce token TTL from 24h to 5min. Webhook participant_joined
now validates voice_states membership and join token match — rogue
participants are removed via LiveKit API.

BUG-128: GenerateToken uses CanPublishSources to restrict track types
(microphone/camera/screen_share) based on actual OwnCord permissions,
preventing SFU-level bypass of USE_VIDEO/SHARE_SCREEN checks.
2026-04-02 12:25:03 +02:00