Commit Graph
441 Commits
Author SHA1 Message Date
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 5dbb89f237 feat: auto-grant microphone permission in WebView2 via --use-fake-ui-for-media-stream 2026-04-02 23:41:01 +02:00
J3vb e65a7d6a70 fix: harden server update signing 2026-04-02 23:35:11 +02:00
J3vb 8184283ab0 fix: enable Claude Code Review for fork PRs via pull_request_target
OIDC tokens are not available for fork PRs with pull_request trigger.
Switch to pull_request_target and checkout the PR head SHA explicitly.
Also grant pull-requests: write so the action can post review comments.
2026-04-02 17:36:16 +02:00
J3vb 4ffd6731c2 fix: resolve CI failures from invalid golangci-lint SHA and ESLint unknown rules
Update golangci-lint-action to v9.2.0 with correct commit SHA. Change
eslint-disable-next-line to oxlint-disable-next-line for oxlint-specific
rules (consistent-function-scoping, prefer-add-event-listener,
require-post-message-target-origin) that ESLint doesn't recognize.
2026-04-02 17:32:36 +02: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 56d330b930 updated readme 2026-04-02 16:54:33 +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 0b6a0a80f8 updated readme 2026-04-02 15:50:07 +02:00
J3vb 9aa81ea83a Merge pull request #97 from arcadia16/readme-clarify
IPv4 + Quick start update
2026-04-02 15:43:10 +02:00
J3vb 9f74710d91 fix: send invite code instead of numeric ID for revoke endpoint
The client was sending DELETE /api/v1/invites/{id} with a numeric database
ID, but the server expects the invite code string. This caused a 404 since
no invite has a code matching a numeric ID.
2026-04-02 15:41:25 +02:00
Arcadia a3cee4f0cc IPv4 + Quick start update
1. Clarified where and how servers IPv4 is obtained
2. Moved "Quick start" to the top (why would you place it so far?)
2026-04-02 16:34:54 +03: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 1754046c7d fix: guard replaceTrack with pipeline generation counter (BUG-106)
Both setupAudioPipeline and teardownAudioPipeline called replaceTrack
as fire-and-forget. If teardown's replaceTrack resolved after setup's,
the WebRTC sender would be bound to the wrong track. Both paths now
capture _pipelineGeneration before the async call and detect stale
completions, preventing out-of-order track replacement.
2026-04-02 13:47:22 +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 e9543ce6d6 fix: video grid auto-opens only for local camera/screenshare (BUG-105)
Refine auto-open to match Discord behavior: grid opens automatically
when the local user enables camera or screenshare, but NOT when remote
users do. Remote video requires clicking the user row to open.
2026-04-02 13:42:00 +02:00
J3vb 1622e04cee fix: auto-open video grid on camera/screenshare and harden CI supply chain (BUG-105, BUG-139)
BUG-105: checkVideoMode now auto-opens the video grid when any video
stream (local or remote camera/screenshare) becomes active. Previously
tiles were added to a hidden grid container.

BUG-139: All GitHub Actions pinned to commit SHAs instead of mutable
tags. Tool installs (govulncheck, tauri-typegen, cargo-audit) pinned
to specific versions instead of @latest.
2026-04-02 13:39:13 +02:00
J3vb 64ac640e4c fix: reconnect device restore, screenshare volume slider, scroll listener leak (BUG-099, BUG-102, BUG-104)
BUG-099: Auto-reconnect now reapplies saved audio input/output devices
via switchActiveDevice, matching the initial join path.

BUG-102: Screenshare tile volume slider now calls
setScreenshareAudioVolume with the normalized value instead of only
toggling mute. Intermediate volumes (e.g. 50%) work correctly.

BUG-104: attachScrollCollapse moved from update() to component creation
so only one listener is attached to the container, preventing
accumulation on every voice state change.
2026-04-02 13:31:32 +02:00
J3vb f52f68a0cc fix: stop leaked tracks on publish fail, handle OS stop-sharing, honor deafened on mic retry (BUG-100, BUG-101, BUG-103)
BUG-100: Camera/screenshare tracks are now stopped in catch blocks when
publishTrack fails, releasing hardware immediately.

BUG-101: Screen video track now has an 'ended' listener that triggers
the full disableScreenshare flow when the OS "Stop sharing" button is
clicked, keeping UI and WS state in sync.

BUG-103: retryMicPermission now checks localDeafened state. If deafened,
the mic is acquired but kept muted so audio is not published while the
UI shows deafened.
2026-04-02 13:26:18 +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 9d9f635c2d fix: gate credential persistence on rememberPassword checkbox (BUG-135)
wirePostAuth always called saveCredential regardless of the "Remember
password" checkbox state, leaving the session token in Windows
Credential Manager even when the user opted out. Added rememberPassword
parameter to wirePostAuth and skip saveCredential when false.
2026-04-02 13:01:10 +02:00
J3vb 5dc6fe61b1 chore: remove gitignored docs/brain files from tracking 2026-04-02 12:56:39 +02:00
J3vb f62d7e318b fix: stop leaked camera/screen tracks on reconnect (BUG-098)
teardownForReconnect only cleaned up audio pipeline and token timer,
leaving manual camera/screenshare MediaStreamTracks capturing
indefinitely after unexpected disconnect. Added stopManualCameraTrack
and stopManualScreenTracks calls before room is nulled, plus store
flag resets so the UI reflects the actual state.
2026-04-02 12:55:51 +02:00
J3vb 839b07e8c6 fix: show notification banner on TOFU first-use cert trust (BUG-133)
The cert-tofu event listener now handles "trusted_first_use" status
and shows a visible notification banner with the server hostname and
SHA-256 fingerprint. Adds onCertFirstTrust callback to the WS client
API. First-use certificate trust is no longer silent.
2026-04-02 12:47:07 +02:00
J3vb e2a9811765 fix: updater uses TOFU-pinned cert validation instead of disabling TLS (BUG-134)
Replace danger_accept_invalid_certs(true) with PinnedVerifier-based
rustls config that validates server cert against TOFU fingerprint from
the cert store. For CA-signed servers (no stored fingerprint), system
TLS is used. Shared PinnedVerifier, cert_store_key, and
load_stored_fingerprint are now pub(crate) for reuse.
2026-04-02 12:41:49 +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
J3vb d912a5a87d fix: AdminIPRestrict uses trusted_proxies for real client IP (BUG-116)
AdminIPRestrict now accepts trustedProxyCIDRs and resolves the real
client IP from X-Real-IP/X-Forwarded-For when connecting through a
trusted reverse proxy. Without trusted_proxies configured, behavior
is unchanged (RemoteAddr only). Prevents admin panel exposure when
OwnCord is deployed behind nginx/caddy/traefik.
2026-04-02 12:15:47 +02:00
J3vb 5edebb43b3 fix: add 30s session sweep to kick revoked WS connections (BUG-109)
Idle WebSocket connections only revalidated sessions every 10 sent
messages, allowing revoked tokens to stay connected indefinitely.
Add sweepRevokedSessions() on a 30s ticker that checks all connected
clients against the DB and kicks any with deleted/expired sessions
or banned users.
2026-04-02 12:12:56 +02:00
J3vb 77c440c4ae fix: atomic setup prevents TOCTOU race creating multiple owners (BUG-119)
Replace separate UserCount() + CreateUser() with atomic
CreateOwnerIfEmpty() that checks and inserts in a single SQLite
transaction. Concurrent race test validates exactly 1 owner under
20 parallel requests.
2026-04-02 12:09:27 +02:00
J3vb 098eebe674 fix: add CSRF Origin check to setup endpoint (BUG-097)
The first-run setup POST was vulnerable to cross-site request forgery
because it had no Origin validation. Added isSetupOriginAllowed check
that validates the Origin header against configured allowed_origins.
Requests with a mismatched Origin are rejected with 403. Requests
without an Origin header (same-origin or curl) are allowed through.
2026-04-02 11:54:56 +02:00
jevb 9249ff0a78 fix: close DB before backup restore to prevent corruption (BUG-096)
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.
2026-04-02 11:45:09 +02:00
jevb d2705dff92 fix: filter voice states by channel visibility in ready payload (BUG-095)
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.
2026-04-02 11:40:16 +02:00
jevb 125512d591 fix: fail closed on role lookup in WS ready flow (BUG-094)
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.
2026-04-02 11:29:10 +02:00
jevb b5111dddf1 fix: correct GitHub username in README issues link 2026-04-02 11:25:53 +02:00
jevb 527e7e7d2c fix: exclude DM channels from guild channel listings (BUG-093)
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.
2026-04-02 11:24:43 +02:00
jevb 68befb49c5 docs: add early alpha warning banner to README 2026-04-02 11:23:08 +02:00
jevb 57d87bb439 fix: require auth + channel ACL on file serving (BUG-092)
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.
2026-04-02 11:16:16 +02:00
jevb 384e94d9f9 fix: close 3 security audit findings (BUG-108, BUG-122, BUG-126)
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.
2026-04-02 10:37:34 +02:00
jevb cbc39d7ab5 fix: address code review findings from bug fix session
- 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
2026-04-01 18:33:33 +02:00
jevb cfaa253b1b fix: remediate low-signal Go test assertions (BUG-061)
Replace "doesn't panic" style assertions in coverage_boost_test.go
with behavioral checks on return values and state.
2026-04-01 18:25:13 +02:00
jevb 7924070531 fix: remediate low-signal test assertions in client tests (BUG-061, BUG-064)
- audio-pipeline.test.ts: replace no-op assertions with state checks
- device-manager.test.ts: replace toBeDefined/not.toThrow with actual
  value and behavior assertions
- livekit-session.test.ts: replace not.toHaveBeenCalled with state
  verification and return value checks
2026-04-01 18:22:09 +02:00
jevb 09da7db0d3 fix: improve native E2E reliability (BUG-059)
- Increase CDP bootstrap timeout to 60s with exponential backoff
- Replace waitForLoadState("networkidle") with element-based readiness
- Replace fixed waitForTimeout sleeps with waitForSelector/toBeVisible
- Convert data-dependent skips to conditional test.skip with messages
- Add shared helpers for countTextChannels/countVoiceChannels
- Increase login timeout to 60s for rate-limited servers
2026-04-01 18:20:44 +02:00