mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
7a4e5dc357942ae858df8445fed67be54b69a467
6
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7a4e5dc357 |
refactor: rename the Go module to github.com/J3vb/OwnCord/Server (RL-13)
`Server/go.mod` declared `github.com/owncord/server` while the public repository is `github.com/J3vb/OwnCord`. Nothing resolves that path — there is no `owncord` GitHub org and no vanity-import host serving go-import metadata for it — so every import line in the tree named a location that does not exist. It compiles because a main module's own path is never fetched, which is exactly why it went unnoticed. The obvious fix — an AST-aware import rewriter (`gomvpkg`, `go mod edit`) — is wrong here, and provably so. Six of the 722 occurrences are not imports at all: `api/main_test.go:20` (a goleak `IgnoreTopFunction` pattern), `telemetry/metrics.go:17-19` (three OTel instrumentation-scope names), `invariants/syncutil_locks.go:73` (a diagnostic message), and `invariants/syncutil_locks_test.go:56` (an import line inside a raw-string Go fixture). An import rewriter touches none of them, and the compiler cannot see any of them either. Done as one scripted substitution over `git ls-files`, anchored on the full `github.com/owncord/server` string. The anchor matters: `owncord-server` is a different identifier — the OTel `service.name` (`config/config.go`, `telemetry/telemetry_otel.go`) and the GHCR image name (`.github/workflows/release.yml`, `docker-compose.yml`) — and a looser pattern would have moved it. It is untouched: 10 occurrences across 9 files, before and after. 350 files, 728 insertions, 728 deletions. 722 occurrences in 344 Go files, plus `go.mod:1`, the `sed` at `Makefile:67`, `Server/CLAUDE.md:3`, `docs/architecture/server.md:5`, and the ledger pair (`findings-ledger.json:3758` plus a `render-ledger.mjs` re-render of `FINDINGS.md`). Zero in any workflow, zero in the Dockerfile, zero in `Server/.golangci.yml` (no `local-prefixes`, `gci`, `importas` or `depguard` rule keys on the module path, so import grouping is not configured anywhere). The plan's blast-radius estimate missed one thing, and it is the one that would have gone red: **gofmt**. `J` (0x4A) sorts before every lowercase letter, so in the 36 files where a module-local import shares a contiguous group with a third-party one, the module's imports must move above `github.com/go-chi/...`. `gofmt -l` was clean before the substitution and listed exactly 36 files after it; `gofmt -w` on those 36 restores it to clean. `gofmt` is an enforced gate — the `formatters` block in `Server/.golangci.yml`, which is S-05 — so a substitution-only commit fails Lint. Verified: both directions, and the line accounting is exact. Every added line in this diff contains the new module path (728) and every removed line contains the old one (728); the count of changed lines containing neither is **zero**, so the gofmt re-sort moved module-path lines only and touched no third-party import. The residual check (`git ls-files -z | xargs -0 grep -n 'github\.com/owncord/server'`) returns exactly two hits, both deliberately out of scope: the RL-13 row in `docs/audit-2026-08-23-repository-layout.md` and the measurement row in this phase's own plan. The compiler-invisible half was proven by reverting *only* `api/main_test.go:20` to the old path on the otherwise-renamed tree: `go build ./...` and `go vet ./api/` both still pass — they see nothing wrong — while `go test ./api/` FAILS, because the runtime function name now carries the new path and goleak stops ignoring `ws.(*Hub).Run.func1`. Restoring the line makes it pass. `go.sum` is byte-identical (no `go mod tidy` was run and none was needed). All four build-tag variants compile; `go vet ./...`, `go vet -tags otel,wazero ./...` and `go vet -tags deadlock ./...` pass; `go test -race ./...` is 16/16 packages green; `go test -tags deadlock ./...` passes; the tag-gated `./plugin/...` (wazero) and `./telemetry/...` (otel) runs pass. `golangci-lint` v2.11.3 — the pinned CI version, rebuilt locally against Go 1.26 because the packaged binary cannot load a 1.26 config — reports **0 issues**. `go run ./cmd/genprotocol` leaves `git diff --exit-code ws/message_types.go ../Client/src/lib/protocolTypes.ts` clean, so the rename does not reach the generated protocol constants. `npx prettier --check .` and `node .superpowers/render-ledger.mjs --check` pass. Not included: `docs/audit-2026-08-23-repository-layout.md` and `docs/plans/b1-repository-foundation-2026-08-25.md` keep the old path — they are the audit row and the measurement that motivated this change, and rewriting them would erase the record of what was measured. They are why the residual check needs a two-path allowance rather than being empty; that allowance is stated above rather than hidden in a pathspec. `telemetry/metrics.go:19` declares `scopeVoice` for a `Server/voice` package that does not exist; the substitution carried the dead path forward verbatim as `github.com/J3vb/OwnCord/Server/voice` rather than fixing it, because correcting a real observability bug inside a mechanical rename would hide it in a 350-file diff. It needs its own item. No `go.work`, no second module, and no vanity-import host was set up — the new path resolves against the real repository, but nothing imports this module as a library, so `go get` reachability was not exercised either way. Refs RL-13, L-12 |
||
|
|
f1a673e87e |
fix: 35 findings from the 2026-08-22 bug hunt (#1402)
* fix(voice): 1 defect(s) (OC-0277) * fix(voice): 1 defect(s) (OC-0278) * fix(client): 1 defect(s) (OC-0280) refreshDmSidebar() rebuilds the entire DM sidebar subtree on every dmStore.channels change - which includes presence flips and new messages, not just DM list changes. The "Find a conversation" filter text and input focus live only in that destroyed subtree, so they were silently wiped mid-typing. Capture and restore both across the destroy+recreate cycle. * fix(ws): 1 defect(s) (OC-0285) * fix(client): 1 defect(s) (OC-0286) * fix(client): 1 defect(s) (OC-0288) Consume the legacy unscoped mute key after migrating it onto the first host, so a brand-new host with no scoped key of its own no longer reads through to the same legacy list and inherits another server's mutes. * fix(voice): 1 defect(s) (OC-0290) * fix(db): 1 defect(s) (OC-0293) DecrementMentionCounts reversed mention_count bumps that were never applied: message_mentions stores every resolved mention id including the author's blockers, while applyMentionCounts excludes blockers before incrementing. Deleting a blocked author's message therefore wiped an unrelated, genuine mention badge on the same read_states row. Mirror the block exclusion in the decrement UPDATE. * fix(db): 1 defect(s) (OC-0294) DeleteAccount soft-deletes the departing user's messages but never reversed the read_states.mention_count bumps those messages made, leaving phantom mention badges. Reverse them inline in the existing transaction, mirroring DecrementMentionCounts' guards. * fix(client): 1 defect(s) (OC-0295) MemberList rebuilt every row on any non-presence-only membersStore change and on every roles_update, but registered each row's click/contextmenu listeners on the component-lifetime disposable.signal, which only aborts at destroy(). Discarded rows therefore stayed reachable (and their listeners live) for the component's whole lifetime. Route per-row listeners through a per-render AbortController that is aborted and replaced at the top of every render, and aborted again in destroy(). * fix(identity): 1 defect(s) (OC-0297) UpdateProfile's post-commit re-read of the user row could fail for reasons unrelated to context cancellation (SQLITE_BUSY, I/O error, pool exhaustion) and was reported as ErrInternal even though UpdateUserProfile had already committed. Callers that treat any UpdateProfile error as proof the write never landed — handleUploadAvatar deletes the file it just stored — would delete a file the committed avatar column now points at, permanently breaking the avatar with no user_update broadcast. Since UpdateUserProfile only writes username/avatar/display_name/about, merge those four onto the pre-write snapshot to reconstruct the committed row without needing the re-read to succeed, and log the read failure. * fix(ws): 2 defect(s) (OC-0298, OC-0299) - OC-0298: applyConnectStatus stamped c.user.Status even when the UpdateUserStatus write failed, so auth_ok and the presence broadcast claimed a status users.status disagreed with, and buildReady's ListMembers read never self-corrected for the session. - OC-0299: refreshUserSnapshot silently fell back to roleName "member" when the new role lookup failed, pinning the session to a fabricated role on the wire. It now fails closed like the sibling lookups in upgradeAndAuth and handleFreshConnect. * fix(client): 1 defect(s) (OC-0300) * fix(client): 1 defect(s) (OC-0301) * fix(ws): 1 defect(s) (OC-0302) * fix(api): 1 defect(s) (OC-0305) handleDiagnosticsConnectivity used clientIP(r), ignoring cfg.Server.TrustedProxies, so behind a configured trusted reverse proxy the endpoint reported the proxy hop instead of the real client address. Use clientIPWithProxies with the parsed trusted-proxy nets, matching RateLimitMiddleware on the same route. * fix(client): 2 defect(s) (OC-0306, OC-0308) * fix(client): 1 defect(s) (OC-0307) QuickSwitcher registered a per-row click listener against the overlay-lifetime AbortSignal, but renderResults() rebuilds every row on each keystroke, arrow key, and store refresh. Discarded rows kept their listeners alive until the overlay closed. Replaced with one delegated click listener on the stable results container, keyed off the data-channelid each row already carries. * fix(client): 1 defect(s) (OC-0310) * fix(server): 3 defect(s) (OC-0279, OC-0291, OC-0292) Reap a soft-deleted message's attachment files, count lapsed temporary bans as active users in the require_2fa enrollment gate, and only apply the 2FA-enrollment precondition when require_2fa itself is being enabled. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * test(api): sync apiTestSchema with the user_blocks migration DeleteAccount's mention-count reversal joins user_blocks; the api package's hand-rolled schema fixture predates migration 012. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * fix(client): 3 defect(s) (OC-0281, OC-0282, OC-0296) Decouple the E2EE identity-mismatch modal and right-click popovers from the sidebar's per-render abort signal, and let global drag listeners survive a mid-drag re-render. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * fix(voice): 2 defect(s) (OC-0283, OC-0287) Retire a departed peer's E2EE key unconditionally on leave, and surface a failed microphone unmute instead of reporting an unmuted state the room never saw. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * fix(client): 3 defect(s) (OC-0289, OC-0303, OC-0309) Guard the DM call button against redialing the channel already joined, resolve the incoming-call banner's caller through the nickname-aware display name, and keep the DM profile sidebar subscribed to live member/status updates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * style(client): prettier-format the dm-store test Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * fix(server): 1 defect(s) (OC-0284) Make message soft-delete a compare-and-set so a repeated chat_delete cannot reverse mention counts twice. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * fix(server): 2 defect(s) (OC-0276, OC-0304) Re-sync a resumed connection's voice E2EE peer keys in registerNow (announce frames are unsequenced and cannot be replayed), and apply the live-connection presence rule to every DM payload DMService builds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * chore(ledger): record the 2026-08-21 hunt findings as fixed Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * chore(ledger): independent revert-proof pass for OC-0276..OC-0310 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo * refactor(service): extract DeleteMessage authorization into a helper Keeps DeleteMessage under the cyclop complexity ceiling after the OC-0284 guard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdkJRbjCtrG76jEnrhKbYo --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
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 |
||
|
|
447a4543e7 |
chore: remaining server changes (code quality, go mod tidy)
Go mod tidy, minor server-side adjustments from security verification and code quality cleanup pass. |
||
|
|
4c4526e539 |
fix: security hardening — 45 issues from full-project Copilot audit
Critical (6):
- C1: SQL injection in VACUUM INTO backup path — strict character allowlist
- C2: Unlimited binary download in updater — 500MB LimitReader
- C3: JSON injection in SSE log stream — json.Marshal instead of concat
- C4: CSS injection via custom themes — reject () and {} in values
- C5: Silent DM message loss — error response on participant lookup failure
- C6: LiveKit URL credential leak — strip creds from diagnostics endpoint
High (11):
- H1: DB errors no longer trigger login rate-limit lockout
- H2: Permission fetch failure returns 500, not empty channel list
- H3: TOCTOU race on duplicate WS — atomic check-and-register in hub
- H5: LiveKit webhook verifies voice channel match (already implemented)
- H7: Server host address validated before storage (hostname regex)
- H8: WS message deduplication on reconnect replay (1000-entry Set)
- H9: Admin setup endpoint rate limited (5/min/IP)
- H10: Backup responses return filename only, not full path
- H11: Update binary recovery failure now alerts admin
Medium (17):
- M1: MIME type from magic bytes, not client header
- M3: Nil guard on DM broadcast recipient
- M5: LiveKit process run-done channel race fixed
- M6: Backup restore calls fsync before close
- M7: Partial download file cleaned up on error
- M8: Admin CSP uses nonce instead of unsafe-inline
- M9: Client rate limiter enforced for presence_update
- M10: Voice joinedAt not reset on double-join
- M11: Unread count skips increment during reconnect replay
- M13: Category type uses exact match, not substring
- M14: Storage LimitReader off-by-one fixed
- M15: GitHub token only sent to GitHub hosts
- M16: Content-parser ReDoS regex replaced with split approach
- M17: Audio device switch error handling added
Low (11):
- L1: CORS uses configured origins instead of wildcard
- L2: HSTS header added when TLS enabled
- L3: Consistent JSON error responses across all endpoints
- L4: File modtime from stat, not time.Now()
- L5: Malformed invite JSON returns 400
- L6: TouchSession failure logged at warn
- L8: MessageInput timers cleared on destroy
- L9: Log persistence flush errors caught
- L10: Credential save failure surfaced to user
- L11: Case-insensitive asset name matching in updater
Found by GitHub Copilot full-project review (claude-sonnet-4.6 + claude-haiku-4.5).
|
||
|
|
f30d267fda |
feat: add observability, debugging, and diagnostics across all layers
Phase 1 — Server-side logging: - Enhance HTTP request logging with client_ip, bytes, req_id - Enrich WS disconnect logs with duration, msgs received/sent/dropped, voice channel, and last error - Add structured logging to LiveKit webhook events - Enrich voice join/leave logs with username, remote addr, quality, channel occupancy - Add channel_id to voice control debug logs Phase 1 — Client log persistence: - New logPersistence.ts: rotating JSONL files in appLogDir with 5-day retention, 2s debounced flush, append mode - Wire into app startup with flush on beforeunload - Scope all new FS capabilities to $APPLOG/** Phase 1 — Rust proxy logging: - Replace eprintln! with structured log crate (info/warn/error/debug) in livekit_proxy.rs and ws_proxy.rs - Add env_logger with try_init for safe initialization - Log TLS handshakes, TOFU checks, connection lifecycle, byte counts Phase 1 — Cache management UI: - Add Clear Image Cache, Clear Log Files, and Clear All Cache & Restart buttons to Settings > Advanced with confirmation dialog Phase 2 — LiveKit ICE and lifecycle logging: - Log ICE candidate types (host/srflx/relay) and selected candidate pair on every voice connect and auto-reconnect - Add room lifecycle event handlers: Reconnecting, Reconnected, SignalReconnecting, MediaDevicesError, ConnectionQualityChanged - Expose ICE connection state in getSessionDebugInfo() Phase 2 — WebSocket reconnection logging: - Structured reconnection logs with host, attempt, lastSeq - Log reconnect success with attempt count - Detailed connection state transitions (open/close with context) Phase 2 — Server diagnostics endpoint: - GET /api/v1/diagnostics/connectivity (auth required) - Returns server info, LiveKit health/URL/node_ip, client remote_addr, and private network detection |