Commit Graph
846 Commits
Author SHA1 Message Date
dependabot[bot] 0383221d2c chore(deps): bump @tauri-apps/cli in /Client/tauri-client (#1242)
Bumps [@tauri-apps/cli](https://github.com/tauri-apps/tauri) from 2.10.1 to 2.11.4.
- [Release notes](https://github.com/tauri-apps/tauri/releases)
- [Commits](https://github.com/tauri-apps/tauri/compare/@tauri-apps/cli-v2.10.1...@tauri-apps/cli-v2.11.4)

---
updated-dependencies:
- dependency-name: "@tauri-apps/cli"
  dependency-version: 2.11.4
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-28 14:46:24 +02:00
dependabot[bot] 2704bff360 chore(deps): bump jsdom from 29.0.0 to 29.1.1 in /Client/tauri-client (#1240)
Bumps [jsdom](https://github.com/jsdom/jsdom) from 29.0.0 to 29.1.1.
- [Release notes](https://github.com/jsdom/jsdom/releases)
- [Commits](https://github.com/jsdom/jsdom/compare/v29.0.0...v29.1.1)

---
updated-dependencies:
- dependency-name: jsdom
  dependency-version: 29.1.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-28 14:46:21 +02:00
dependabot[bot] b7945f7a02 chore(deps): bump oxlint from 1.75.0 to 1.76.0 in /Client/tauri-client (#1238)
Bumps [oxlint](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint) from 1.75.0 to 1.76.0.
- [Release notes](https://github.com/oxc-project/oxc/releases)
- [Changelog](https://github.com/oxc-project/oxc/blob/main/npm/oxlint/CHANGELOG.md)
- [Commits](https://github.com/oxc-project/oxc/commits/oxlint_v1.76.0/npm/oxlint)

---
updated-dependencies:
- dependency-name: oxlint
  dependency-version: 1.76.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-28 14:46:17 +02:00
dependabot[bot] 86976a2ffd chore(deps): bump tauri-plugin-opener in /Client/tauri-client/src-tauri (#1237)
Bumps [tauri-plugin-opener](https://github.com/tauri-apps/plugins-workspace) from 2.5.3 to 2.5.4.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](https://github.com/tauri-apps/plugins-workspace/compare/http-v2.5.3...http-v2.5.4)

---
updated-dependencies:
- dependency-name: tauri-plugin-opener
  dependency-version: 2.5.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-28 14:46:13 +02:00
dependabot[bot] 14f150d01b chore(deps): bump github.com/prometheus/client_golang in /Server (#1248)
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.23.2 to 1.24.1.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/v1.24.1/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.23.2...v1.24.1)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-version: 1.24.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-28 14:45:36 +02:00
dependabot[bot] 45f396cef2 chore(deps): bump github.com/livekit/server-sdk-go/v2 in /Server (#1247)
Bumps [github.com/livekit/server-sdk-go/v2](https://github.com/livekit/server-sdk-go) from 2.16.0 to 2.18.1.
- [Commits](https://github.com/livekit/server-sdk-go/compare/v2.16.0...v2.18.1)

---
updated-dependencies:
- dependency-name: github.com/livekit/server-sdk-go/v2
  dependency-version: 2.18.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-28 14:45:33 +02:00
dependabot[bot] 28733edf6e chore(deps): bump go.yaml.in/yaml/v3 from 3.0.4 to 3.0.5 in /Server (#1245)
Bumps [go.yaml.in/yaml/v3](https://github.com/yaml/go-yaml) from 3.0.4 to 3.0.5.
- [Commits](https://github.com/yaml/go-yaml/compare/v3.0.4...v3.0.5)

---
updated-dependencies:
- dependency-name: go.yaml.in/yaml/v3
  dependency-version: 3.0.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-28 14:45:29 +02:00
J3vbandClaude Opus 5 f4be2f9b73 chore: ignore rust-review results and claude worktrees
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 14:42:53 +02:00
dependabot[bot] 7ba8cb83ef chore(deps): bump github.com/livekit/protocol in /Server (#1241)
Bumps [github.com/livekit/protocol](https://github.com/livekit/protocol) from 1.50.2 to 1.50.4.
- [Release notes](https://github.com/livekit/protocol/releases)
- [Changelog](https://github.com/livekit/protocol/blob/main/CHANGELOG.md)
- [Commits](https://github.com/livekit/protocol/compare/v1.50.2...v1.50.4)

---
updated-dependencies:
- dependency-name: github.com/livekit/protocol
  dependency-version: 1.50.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-28 14:41:27 +02:00
dependabot[bot] 52fa290685 chore(deps): bump golang.org/x/crypto from 0.52.0 to 0.54.0 in /Server (#1243)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.52.0 to 0.54.0.
- [Commits](https://github.com/golang/crypto/compare/v0.52.0...v0.54.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.54.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-28 14:40:21 +02:00
J3vb 2c424efb1e Merge pull request #1236 from J3vb/claude/test-coverage-audit-tvp07j
test: close measured test-coverage gaps across server, client and Rust
2026-07-28 14:33:22 +02:00
Claude 33a6b23cde fix: resolve golangci-lint failures and correct audit-doc inaccuracies
A pre-merge review caught things my local verification missed, because two CI
gates could not run in the sandbox and I mis-read a third.

golangci-lint (BLOCKER — would have turned CI red on both server matrix legs).
My local binary was built for Go 1.25 against a repo targeting 1.26, so I could
not run it. Installed 2.11.3 with the repo's own toolchain: 5 issues, all in
files this PR adds, base clean. Now 0 issues:
- bodyclose x3 in api/livekit_proxy_ws_test.go — websocket.Dial's *http.Response
  was discarded; adopt the repo's existing pattern from ws/ws_integration_test.go
- gocritic stringXbytes — string(got) != string(payload) -> !bytes.Equal
- staticcheck SA4000 in ws/topic_rate_limiter_test.go — `!Allow() || !Allow()`.
  This was a real defect, not just a lint: || short-circuits, so a failing first
  call skipped the second, left a token unspent, and the next assertion would
  have reported the wrong thing. Split into two statements.

Playwright: I reported this suite as passing. It does not. I read the exit code
of `tail` through a pipeline instead of playwright's own. Re-run properly: 229
of 255 web tests fail, all cascading from the shared login helper
(navigateToMainPage never sees [data-testid='app-layout']). It reproduces on a
clean b3caceb worktree, so it is pre-existing on main and unrelated to this
diff — but it was never true that I had verified it. Recorded as new finding
T-2026-07-25-21 and promoted to backlog #2; the client-e2e job stays
continue-on-error and now carries timeout-minutes so a red suite cannot burn
unbounded Actions minutes. rust-tests gets a timeout too.

Audit-doc corrections (all confirmed by re-measurement):
- screenShare.ts was listed as "untouched by this pass" at 61.1% when this PR
  takes it to 100%; T-12's wording made it the exception when it is the best
- IsEitherBlocked was NOT zero-coverage — 83.3% at base via message_test.go
- excluded LOC 2,229 -> 1,827
- "40 Playwright spec files" -> 44 (33 web + 11 native), 255 web tests
- HandleLiveKitHealthForTest callers: eight -> seven
- admin coverage: 71.4% is with only the T-01 fix; 77.9% with this PR's tests

Verified after the fixes: golangci-lint 0 issues, go vet, go test -race,
go test -tags deadlock, vitest 94.87%, cargo test --lib 74/74, tsc, prettier.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AEETs3Vh6sAHHb1jMBL75g
2026-07-28 12:25:10 +00:00
Claude 0918f859a0 test: close measured test-coverage gaps across server, client and Rust
Audits what actually has tests, then closes the gaps it found. Full write-up
with before/after numbers in docs/audit-test-coverage-2026-07-25.md.

Measurement first: `go test ./... -coverprofile` (what CI runs) instruments
each package only for itself, so code exercised through another package's
tests reads as uncovered — `service` reported 36.7% against a real 85%. All
analysis here uses -coverpkg=./..., and both views now have Makefile targets.

Features that had zero coverage at every layer:
- user blocking (db + service + the /api/v1/blocks routes)
- auth lockout persistence — the DB round-trip that survives a restart
- plugin install/enable/disable/uninstall and the plugin KV namespace
- event replay bounds (GetMaxEventSeq, PruneEventsOlderThan)
- LiveKit participant_joined webhook (replayed-token guard), the room-service
  client, and proxyWebSocket/copyWS
- ws_proxy.rs and livekit_proxy.rs — pure helpers extracted, matching the
  existing tofu.rs pattern, so cert-pin and header-injection checks are testable

Gaps that were hidden rather than absent:
- Server/admin reported 0.3% coverage with 307 tests passing. TestSpawnDetached_*
  re-execs the test binary; the child inherited GOCOVERDIR and the parent's
  stdout, clobbering the profile and printing "[no tests to run]". Now 71.4%,
  and CI's uploaded artifact is correct.
- vitest.config.ts excluded 2.2k LOC unexplained, including two files that
  already had tests. Trimmed to three entries, each justified inline.
- api.HandleLiveKitHealthForTest re-implemented the handler it claimed to
  expose, so eight call sites tested a copy. Added a hook to the real one.

Two bugs found and pinned rather than silently patched: logctx.WithGroup nests
req_id under the group, and drag-reorder.ts takes one listener ref per channel
but releases one per sidebar, so the count never reaches zero.

Coverage: client 92.93% -> 94.87% statements (3371 -> 3572 tests) even after
un-excluding hidden files; Rust 47 -> 74 tests; Go zero-coverage functions
~70 -> 21, with plugin 61->77%, admin 67->86%, db 76->84%, service 85->91%.

Verified: go vet, all four build-tag variants, go test -race, -tags deadlock,
vitest --coverage, cargo test --lib, cargo clippy --all-targets, playwright.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AEETs3Vh6sAHHb1jMBL75g
2026-07-25 14:47:21 +00:00
J3vb b3cacebd92 Merge pull request #1235 from J3vb/feat/logging-hardening
feat: logging & error-visibility hardening (server + client)
2026-07-24 11:37:57 +02:00
J3vbandClaude Opus 4.8 d35de4ca0a fix: integrate logging hardening with rebased main (F3 + tauri plugins)
Rebasing onto post-F3 main surfaced two spots the auto-merge left inconsistent:
- profile_handler UpdateIdentityKey path: thread ctx into the writeServiceError
  call (the signature gained a context param in the server logging change)
- identity-pin store lookup: drop a needless borrow flagged by clippy

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 11:29:21 +02:00
J3vbandClaude Opus 4.8 1423b17993 feat(client): logging & error-visibility hardening
- tauri-plugin-log: rotating Rust log file in the app-log dir so shipped users
  can retrieve proxy/TLS/TOFU diagnostics (a release build detaches the console)
- log the health-check failure cause; log persist failures in save_settings /
  store_cert_fingerprint; add a TOFU cert-pin accept/change audit trail; log
  http/livekit proxy-loop panics instead of swallowing them
- stop persisting the raw WS frame content and the auth token prefix to disk
- drain the pre-init in-memory log buffer so bootstrap logs reach disk
- surface the server X-Request-Id on API errors for cross-tier correlation

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 11:10:38 +02:00
J3vbandClaude Opus 4.8 4fc21cb372 feat(server): logging & error-visibility hardening
Make server failures debuggable without leaking secrets:
- configurable stdout log level (config.yaml logging.level + OWNCORD_LOGGING_LEVEL)
- preserve the DB cause in ErrInternal wraps; log auth-DB failures distinctly
  from bad tokens; log the previously-silent expired-session cleanup goroutine
- route HTTP handler panics through slog (was chi stderr-only, invisible to
  the admin log stream)
- stackutil: argument-free panic stacks so key/token bytes never reach the
  admin ring buffer / SSE; slog.LogValuer redaction on VoiceConfig/GitHubConfig/
  GIFConfig/Config and db.User/db.Session
- logctx: req_id/trace_id correlation on ...Context log calls

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 11:07:12 +02:00
J3vb 4b36a3339a Merge pull request #1234 from J3vb/worktree-tauri-plugins
feat(client): add single-instance/autostart/deep-link; move window-state to plugin
2026-07-24 10:50:46 +02:00
J3vbandClaude Opus 4.8 f3c6745c0b feat(client): add single-instance/autostart/deep-link; move window-state to plugin
Replace hand-rolled code with first-party Tauri v2 plugins where a plugin can
do the job, and add the genuine gaps:

- single-instance: focus the running window on a second launch instead of
  opening a duplicate (two WS connections / tray icons). Registered first;
  built with the "deep-link" feature so owncord:// links reach the running app.
- window-state: replace the hand-rolled save/restore plumbing with
  tauri-plugin-window-state. Keep only the one thing the plugin lacks — an
  off-screen re-center guard for windows restored onto a now-disconnected
  monitor (isRectOnScreen).
- autostart: "Launch on Login" toggle in Advanced settings, reading/writing
  real OS state via tauri-plugin-autostart (not a stored preference).
- deep-link: register the owncord:// scheme and route invite links into the
  register form. OwnCord invites are registration invites, so a link pre-fills
  and opens the form rather than completing a one-click join.

Intentionally NOT replaced: push-to-talk (ptt.rs) stays hand-rolled —
tauri-plugin-global-shortcut registers OS hotkeys that grab the key
system-wide (RegisterHotKey / XGrabKey), which cannot express non-consuming
press-and-hold PTT. Clipboard stays on the native Web API (no custom code).

Verified: tsc, eslint, prettier, 3369 unit tests, cargo check, cargo clippy
(client code clean; one pre-existing needless-borrow lint in commands.rs is
flagged only by newer local clippy, untouched here).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 10:44:06 +02:00
J3vb ed59925073 Merge pull request #1233 from J3vb/fix/e2ee-repin-toctou
fix(e2ee): close voice-E2EE re-pin TOCTOU (MITM) + forward-secrecy gaps
2026-07-24 09:21:33 +02:00
J3vbandClaude Opus 4.8 8b8632774e fix(e2ee): coalesce concurrent room-key rotations; refuse blind re-pin
Follow-up to the F3 re-pin/forward-secrecy fix, closing two residuals found by
adversarial re-review of the first fix:

- Concurrent-leave rotation drop (medium): rotateKeyPeriodically's _rotatingKey
  guard silently skipped a rotation already in flight, so a keyed peer that left
  mid-rotation kept a live room key until the next periodic (<=5 min) rotation.
  A coincident keyed-peer leave now DEFERS its rekey (_rotationPending) instead
  of dropping it; the completing rotation drains it via a shared
  drainPendingRotationOrArmTimer, excluding the departed member. Applied to both
  the become-holder and periodic rotation paths; reset in clearE2EEState.

- Blind re-pin (info, defense-in-depth): the mismatch modal's Trust action pinned
  publishedKey even when its fingerprint could not be computed (nothing shown to
  verify). onAccept now refuses to pin when fingerprint is null.

Client gates green: typecheck, lint (0 errors), prettier, vitest (3364).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 09:11:08 +02:00
J3vbandClaude Opus 4.8 ef1aca8a67 fix(e2ee): pin the verified identity key on re-pin, rekey on keyed-peer leave
Multi-agent F3 security review surfaced two voice-E2EE defects:

- Re-pin TOCTOU (voice-E2EE MITM): the identity-mismatch modal showed a
  fingerprint from one membersStore read, but rePinPeerIdentity re-read the
  server-writable store to decide what to pin. A malicious server (F3's threat
  model) could swap in an attacker key via a user_update during the human
  out-of-band verification window and have it pinned, silently defeating the
  mismatch prompt. rePinPeerIdentity now takes the exact verified key as a
  parameter; ChannelSidebar passes the bytes whose fingerprint it displayed.

- Membership forward secrecy: the key holder rotated the room key only when the
  holder ROLE transferred, so a departed non-key-holder kept a valid room key
  until the next periodic (<=5 min) rotation. The holder now also rotates when a
  peer that held the key leaves (reusing rotateKeyPeriodically), gated on the
  leaver having actually held a key.

Client gates green: typecheck, lint (0 errors), prettier, vitest (3361).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 08:55:07 +02:00
J3vb 98b403564e Merge pull request #1232 from J3vb/feat/e2ee-identity-tofu
feat(e2ee): F3 voice identity keys + TOFU verification (server, client, UI) + W2-4/W3-3 hardening
2026-07-23 22:08:33 +02:00
J3vbandClaude Opus 4.8 53b9c46179 feat(e2ee): surface F3 voice identity verification in the channel sidebar
Render the per-peer E2EE identity-verification state (F3 TOFU) on voice
user rows in the live channel sidebar, and give legitimate key rotation an
in-app recovery path:

- Per-peer shield badge in renderVoiceChannelItem: green shield-check
  (verified, safety number in tooltip), muted shield (unverified/legacy),
  red shield-alert (mismatch, click to review).
- createIdentityMismatchModal (in CertMismatchModal.ts, reusing the .cert-*
  CSS and buildRow) — the identity-key analogue of the cert-mismatch prompt.
  It shows the changed key's fingerprint for out-of-band verification, and
  "Trust New Key" re-pins via rePinPeerIdentity to recover from a genuine
  rotation.
- Fold verification status into the sidebar's voiceStore structural
  signature so a verified/unverified/mismatch flip re-renders the badge.
- Three Lucide shield icons; .vu-verify layout CSS.

The badge lives in ChannelSidebar.renderVoiceChannelItem (the live voice
renderer); createVoiceChannel in VoiceChannel.ts is dead/unused.

Client gates green: typecheck, lint (0 errors), prettier, full vitest (3358).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 22:06:50 +02:00
J3vbandClaude Opus 4.8 81a0b63e65 feat(e2ee): F3 identity/TOFU + W2-4/W3-3 hardening — checkpoint before F3 UI
WIP save point. Server + W2-4/W3-3 complete and gate-green; F3 voice E2EE
identity keys + TOFU implemented and MITM-verified-closed; the F3 voice-panel
UI (safety-number display, verified/mismatch badge, re-pin modal) is still TODO.

- W2-4 attachment link (coverage confirmed); W3-3a XFF CIDR pre-parse;
  W3-3b update-binary TOCTOU (single-handle verify + O_EXCL staging)
- F3 server: migration 017 identity_public_key, PATCH /users/me persist,
  ready/member_join/user_update carry key, signed voice_e2ee_announce
- F3 client: ECDSA identity keypair (keyring + pin store), publish wired into
  ready, verifyPeerAnnounce pin-before-legacy, rePinPeerIdentity recovery
- Gates: server full CI mirror green (-race/-deadlock/lint/4 build tags);
  client typecheck/lint/format + 3337 vitest green. Rust CI-verify only.

Next: build F3 voice-panel UI, then adversarial review, then finalize commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 21:07:09 +02:00
J3vb 865dffc771 Merge pull request #1231 from J3vb/fix/updater-integration
fix(updater): make client auto-update work end-to-end and Linux server self-update verifiable
2026-07-23 18:40:57 +02:00
J3vbandClaude Fable 5 f3a89e0e09 fix(updater): make client auto-update work end-to-end and Linux server self-update verifiable
- client: update endpoint now sends {{target}}-{{arch}}-{{bundle_type}} so the
  server-echoed platforms key matches the updater plugin's
  {os}-{arch}-{installer} lookup (previously bare {{target}} produced a key
  the plugin never matches, so no update was ever surfaced)
- client: TOFU cert pin is scoped to the OwnCord server host via
  HostScopedVerifier; the GitHub installer download validates against web PKI
  instead of failing the pinned-fingerprint check on every install
- client: check/install share one build_updater helper so the two paths cannot
  diverge; tauri-plugin-updater minor-pinned per its configure_client guidance
- server: client-update endpoint serves target-specific artifacts (NSIS,
  per-arch AppImage) and returns 204 for targets without a published updater
  artifact (deb, darwin) instead of always serving the Windows NSIS installer
- release: server-update-manifest.json now binds both OS assets (legacy
  top-level pair kept pointing at the Windows binary so deployed servers still
  verify); VerifyReleaseManifest resolves the entry matching the downloaded
  asset, fixing Linux server self-update
- release: ARM64 staging renames installer, tar.gz and .sig consistently so
  signatures keep pairing and arch-less names cannot collide with x86_64 assets
- ci: run cargo test --lib (Rust #[cfg(test)] code was never compiled in CI);
  merge the two ptt tests that raced on the global PTT_VKEY atomic

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 18:38:22 +02:00
J3vb 3b1b6deb46 Merge pull request #1230 from J3vb/chore/deps-batch-2026-07-23
chore(deps): batch-apply all open dependabot bumps (2026-07-23)
2026-07-23 17:25:10 +02:00
J3vbandClaude Fable 5 e392939c52 chore(deps): batch-apply all open dependabot bumps (2026-07-23)
Applies all 22 open dependabot PRs in one pass (CI on those PRs never
ran — Actions minutes exhausted). Verified locally via the ci-check
mirror: builds (all tag variants), vet, golangci-lint, sqlc/protocol
verify, vitest 3304/3304, npm audit clean, cargo check.

Server (Go): wazero 1.12.0, x/mod 0.38.0, chi 5.3.1, otel 1.44.0,
otel/trace 1.44.0, otel prometheus exporter 0.66.0, modernc sqlite
1.54.0, livekit/protocol 1.50.2, koanf/v2 2.3.5, x/sync 0.22.0.
Also x/text 0.39.0 (fixes GO-2026-5970, flagged by govulncheck).
livekit/protocol requires Go 1.26 → go.mod, CI pins, and docs bumped.

Client (npm, lockfile-only): playwright/test 1.61.1, oxlint 1.75.0,
eslint 9.39.5, knip 6.29.0, plugin-http 2.5.9, plugin-fs 2.5.1,
plugin-opener 2.5.4, tauri-apps/api 2.11.1 + npm audit fix
(brace-expansion, fast-uri transitive highs).

Client (cargo, lockfile-only): futures-util 0.3.33, env_logger
0.11.11, serde 1.0.229, tauri-typegen 0.5.2.

Closes #1204 #1205 #1206 #1207 #1209 #1210 #1211 #1212 #1213 #1214
Closes #1215 #1216 #1219 #1220 #1221 #1222 #1223 #1225 #1226 #1227 #1228 #1224

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 17:23:31 +02:00
J3vb 3b4db4c964 Merge pull request #1229 from J3vb/fix/security-scan-2026-07-22
fix(security): 2026-07-22 scan remediation, D13 permission consolidation, dead-code sweep, lint zero
2026-07-23 17:08:53 +02:00
J3vbandClaude Fable 5 6afa9e974c refactor(server): thread context.Context through the db layer and all callers
Fixes all 109 golangci-lint findings (106 contextcheck, 1 gocritic,
2 gosec) that accumulated after D2 wired dbgen (whose queries take ctx)
under ctx-less db.DB wrappers while CI lint was quota-dead. No nolint
comments added; every finding fixed by genuinely threading context.

- db: all 138 hand-written db.DB methods take ctx first; the dbCtx()
  Background shim is deleted; raw Query/QueryRow/Exec/Begin use their
  Context variants; the four redundant ctx-less passthroughs removed.
  db.Auditor/WriteAudit gain ctx.
- Seams: permissions.Checker (DB iface, HasChannelPerm,
  RequireChannelAccess) and the service.Store interface mirror the new
  signatures (ws.EventStore and plugin.PluginStore already did).
- Callers: api/admin handlers use r.Context(); ws per-message paths use
  the connection ctx via DispatchV2; hub loops and startup wiring use
  context.Background(); service methods thread ctx where they have one
  and Background where no ctx exists. Public service surface reached by
  ctx-holding chains (PermissionService.HasChannelPerm/GetRoleForUser/
  RequireChannelAccess, message/dm/block/invite/profile methods) is now
  ctx-first.
- Detached (context.WithoutCancel) where cancellation would break an
  invariant, found by a 3-lens adversarial review of the diff:
  * voice-leave background retries (a dead webhook/connection ctx killed
    retry 2 before it ran, leaving ghost capacity-holding voice rows)
  * rollbackVoiceJoin's compensating delete (its trigger IS the cancel)
  * post-2FA-change DeleteOtherSessions and logout DeleteSession (the
    security tail of a committed change must not die with the request)
  * all api/ws audit writes (a banned user could suppress their own
    login_blocked_banned row by aborting the request mid-bcrypt)
  * admin backup VACUUM INTO (an interrupt left a truncated .db that
    the backup list presented as restorable)
  * post-commit message/edit refetches (a committed message must still
    fan out when the sender disconnects)
  * hub settings-cache refresh (one dead connection could pin stale
    values for the 30s TTL)
- gocritic rangeValCopy fixed (index iteration); gosec G306 excluded in
  config with justification (generated source must stay world-readable)
  instead of flipping genprotocol output to 0o600.

Verified: gofmt/vet, all four build-tag variants, full suite, deadlock
pass, full -race pass, golangci-lint 0 issues uncapped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 17:03:52 +02:00
J3vbandClaude Fable 5 f4b20726ff chore(client): remove dead files, exports, and unused dependencies
knip findings (repo CI config), each verified including dynamic imports,
HTML refs, and the Rust side:

Files deleted: pluginBridge.ts (its documented PluginContainer.tsx
collaborator never existed in the repo; the server plugin host stays per
D11 — reinstate from git if client plugin UI work ever starts),
message-input/file-upload.ts, message-input/picker-toggle.ts (dir now
empty, removed), message-list/virtual-scroll.ts (MessageList does its
own virtualization via FenwickTree).

Dependencies removed: zod (zero imports; typegen uses
validation_library none — stale CLAUDE.md claim fixed),
@tauri-apps/plugin-store and plugin-updater npm halves (both features
are Rust-driven via StoreExt/UpdaterExt — Rust halves stay), and
tauri-plugin-global-shortcut on BOTH sides (PTT polls via device_query;
zero GlobalShortcutExt use): Cargo.toml dep, lib.rs registration, and
the 5 capability permission lines. Inert webview capability entries
store:default/updater:default also dropped. @stryker-mutator/api added
to devDependencies (stryker.config.mjs imports its types; core pins the
same version, zero install delta).

Exports removed: livekitSession clearOnError bound-const, ConnectPage/
MainPage ReturnType aliases, readAllPersistedLogs (never wired to any
UI) with its test blocks. getLogDir kept as the suite's observability
point, tagged @public for knip. protocolTypes.ts *Value types are
generated surface — knip.json now ignores that file instead.

Rust compile is CI-verified only (no MSVC toolchain here, same as the
F4/F8 TOFU work); Cargo.lock resolution pruned cleanly. Client gate
green: tsc, oxlint/eslint 0 errors, prettier, 3304/3304 vitest, knip
clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 15:29:01 +02:00
J3vbandClaude Fable 5 9d8bbec375 chore(db): drop 18 sqlc queries with zero callers
Each verified: the generated dbgen method's only references were the
.sql definition and dbgen output itself (no wrapper in db/*.go, no
test, no script). ArchiveChannel, DeleteAttachment,
FindExistingDMChannel, GetDefaultRole, GetMessagesByChannel,
GetMessagesByChannelBeforeCursor, GetMessagesForAPIBeforeCursor,
GetPinnedMessageRows, GetPlugin, GetPluginByName, InsertDMChannel,
InsertDMOpenState, InsertDMParticipants, LinkAttachmentToMessage,
SetChannelMixingThreshold, SetChannelVoiceMaxVideo,
SetChannelVoiceQuality, UpdateVoiceSpeaking.

dbgen regenerated with the pinned sqlc v1.30.0 (132 → 114 queries);
sqlc-verify clean; db/service/ws suites green including -race.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 15:29:01 +02:00
J3vbandClaude Fable 5 f2966c2527 chore(server): delete production-dead code; move test helpers to export_test
Applied from a deadcode (RTA from mains, all build tags) sweep with
per-symbol adversarial verification:

Deleted (nothing but their own self-tests used them):
- admin.Handler (deprecated since Phase 6; production mounts NewHandler)
  plus its two self-tests
- ws.Hub.broadcastVoiceStateUpdate + wrapper + two self-tests (pre-V2
  leftover; the live voice_state path is the hub voice routines)
- ws.VoiceLeaveEvent + methods ('retained as scaffolding', never
  constructed in production; MsgTypeVoiceLeaveBC stays — live via the
  leave routine)
- ws.parseIdentity (production calls parseParticipantIdentity directly;
  ParseIdentityForTest now exercises the real parser)
- telemetry.Float64 (String/Int64 are used; the float case is covered by
  the otel-tagged internal test, re-addable when a caller appears)

Moved into export_test.go so they leave the production binary (all
callers are same-package tests): the eight ws test-client constructors
and voice/E2EE setters from ws/client.go, admin.SetBackupBaseDir
(new admin/export_test.go), api.SecurityHeaders (test-only wrapper;
production uses SecurityHeadersWithTLS — docs/api.md updated to the
real name). Client.getVoiceJoinToken/setVoiceChID inlined into their
existing ForTest wrappers; TestSetVoiceChID_* self-tests deleted.

Kept after verification: updater.SetBaseURL (11 cross-package test call
sites) and telemetry.resetAppMetricsForInit (live under -tags otel —
untagged deadcode false positive).

Full gate green: gofmt/vet, 4 build-tag variants, full suite, deadlock,
race.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 15:28:41 +02:00
J3vbandClaude Fable 5 07b59ca485 docs(plans): amend security plan trackers; close A-2026-07-15
- security-hardening-remediation.md: status header now records that only
  W2-4 and W3-3 remain open (verified by the 2026-07-23 deletion audit),
  with a staleness note scoping the deleted store/-and-Postgres
  references as historical. Closes audit finding A-2026-07-15.
- security-scan-2026-07-22-remediation.md: F6 recorded as committed
  (ef58c04); resume checklist trimmed — F3 (voice E2EE identity TOFU)
  is the only remaining finding.
- audit-2026-07-19.md: A-2026-07-15 closure row flipped to RESOLVED.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 14:55:08 +02:00
J3vbandClaude Fable 5 2cef29bc71 chore: delete dead code and tracked junk (deletion audit 2026-07-23)
Verified-safe deletions from the 2026-07-23 deletion audit, applied now
that the permission-consolidation work (which deferred IsOwnerRole) has
landed:

- Server/service/voice.go: VoiceService was constructed in service.New
  and never called by any handler, ws routine, or test.
- permissions.IsOwnerRole: zero callers.
- Server/admin/static/admin-mockup.html: 1299 lines embedded into every
  release binary via //go:embed static, referenced by nothing.
- .cache/project-map/*.json: tool cache committed before .gitignore
  grew the .cache/ rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 14:54:59 +02:00
J3vbandClaude Fable 5 83d924c10a docs(audit): record D13 closure of A-2026-07-16
Design note (permission-middleware-consolidation.md, status implemented),
closure-table + §3 rows for A-2026-07-16, the A-2026-07-07 amendment
recording the missed fifth site, the D13 decision row, and the settled
two-scope authorization contract in architecture/server.md. Backlog row
12 stays untouched: the auth-route sweep is deferred to a future D14.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 14:45:42 +02:00
J3vbandClaude Fable 5 a4eca1a55a fix(perms): own the server-scoped rule in HasServerPerm and fail closed on override-fetch errors (D13)
Closes audit finding A-2026-07-16, two defects in the same rule:

- permissions.HasServerPerm (admin bypass OR all-of bit test) replaces
  the hand-rolled copies in api.RequirePermission (whose raw test was
  any-of for multi-bit masks) and ModerationService.requireBanPermission.
  RequirePermission's doc comment now states the scope contract: role
  bitfield only, channel overrides deliberately not consulted.
- PermissionService.getOrPopulate and ChannelService.ListVisibleChannels
  no longer substitute an empty override map when
  GetAllChannelPermissionsForRole errors. That silently dropped every
  channel-level deny — and the permission cache then served the degraded
  snapshot for permCacheTTL (30s) across ~25 callers. Both fail closed
  now; admins skip the fetch entirely (they bypass channel checks).
- PermissionService.HasChannelPerm delegates to Checker.HasChannelPermBatch
  and MessageService.GetAccessibleChannelIDs to VisibleChannelIDs — the
  missed fifth D9 site, making that closure true rather than aspirational.
- AuthMiddleware rejects a dangling role_id (GetRoleByID returns nil,
  nil) with 401 instead of putting a nil role in the request context.

Locked by failing-first tests: override-fetch-error denies (cached and
uncached paths), admin-outage skip, multi-bit all-of, channel allow
override must not grant a server-wide route, 403 locks on both
RequirePermission routes, dangling-role 401.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 14:45:34 +02:00
J3vbandClaude Fable 5 ef58c04ed1 fix(service): don't cache permission snapshots that raced an invalidation
An InvalidateUser/InvalidateChannel/InvalidateAll landing between
getOrPopulate's DB read and its cache store was silently overwritten by
the stale snapshot, serving revoked permissions for up to permCacheTTL
(30s). Guard the cache write with a generation counter bumped by every
invalidation; a populate that lost the race returns its snapshot for the
current request but caches nothing (security scan 2026-07-22, F6).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 14:45:13 +02:00
J3vbandClaude Opus 4.8 cac23c763f docs(security): add continuation plan for the 2026-07-22 security-scan remediation
Handoff doc: F1/F2/F5/F7 and F4/F8 committed, F6 done but riding with the permission-consolidation WIP, and the full F3 (voice E2EE identity keys + TOFU) design + PR split for the remaining work.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 12:57:15 +02:00
J3vbandClaude Opus 4.8 1485e13f9a fix(security): gate TLS trust-on-first-use behind explicit confirmation (F4/F8)
The http and ws proxies accepted ANY certificate on first use and silently
pinned it, forwarding login credentials and the bearer token before the user
ever saw the fingerprint — an on-path attacker at first contact captured them.
The three proxies also duplicated the TLS verifier and TOFU logic verbatim.

- Extract the shared verifier, cert-store helpers, and a pure `decide` function
  into src-tauri/src/tofu.rs (used by the http/ws/livekit proxies).
- Split the trust decision from persistence: a first-use cert is no longer
  pinned or forwarded to. The proxy rejects (ws: Err; http: 502) and emits a
  cert-tofu "first_use" event; the only writer of a pin is the explicit
  accept_cert_fingerprint command.
- Frontend: a global cert-tofu listener (active during the connect page's health
  checks, before any WS connect) surfaces an SSH-style first-use confirmation
  modal. On accept the fingerprint is pinned and the server re-checked; nothing
  is sent to an unconfirmed host.

Closes security-scan F4 (http proxy) and F8 (ws proxy). Verified: client
typecheck/lint/format clean, full unit suite 3311/3311 green (incl. new ws
first-use routing + modal tests). Rust compiles in CI (cargo clippy) per the
client CLAUDE.md; pure tofu logic covered by #[cfg(test)] unit tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 12:47:30 +02:00
J3vbandClaude Opus 4.8 92dae342e0 fix(client): parse OG tags with DOMParser to remove ReDoS vector
parseOgTags matched untrusted link-preview HTML (up to 50KB) against regexes with two sequential [^>]* quantifiers around a required literal, which backtrack polynomially and froze the UI thread on crafted input. Parse with DOMParser (a linear tokenizer) instead; it also correctly ignores meta-like strings inside comments/scripts. (Security scan F7)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 11:59:13 +02:00
J3vbandClaude Opus 4.8 e98c1d7cbc fix(ws): resolve live role in hasChannelPerm to honor mid-session demotions
hasChannelPerm resolved permissions from the connect-time role snapshot (c.user.RoleID), so a user reassigned to a lower role kept the old role's voice privileges (CONNECT_VOICE and the SPEAK/VIDEO grants in the LiveKit token) until reconnect. Resolve the current role via GetRoleForUser(c.userID), matching the V2 handlers. (Security scan F5)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 11:59:13 +02:00
J3vbandClaude Opus 4.8 420eec227c fix(plugin): serialize wazero guest calls with a per-Instance mutex
invokeCommand drove a shared wazero module (allocate/mem.Write/command_dispatch/mem.Read) with no per-instance lock, so concurrent invocations of the same plugin command raced the module's linear-memory buffer. Add a per-Instance mutex around the guest-call sequence. Confirmed under -race. (Security scan F2)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 11:59:13 +02:00
J3vbandClaude Opus 4.8 6bc5938ddf fix(auth): canonicalize username for per-user login lockout keys
The per-username brute-force lockout keyed on the raw request username while GetUserByUsername matches COLLATE NOCASE, so case variants (admin/Admin/ADMIN) each got an independent 9-attempt bucket, multiplying allowed guesses per account. Lowercase the username before building the login_user_fail/login_user_lock keys so all casings share one bucket. (Security scan F1)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 11:59:13 +02:00
J3vb 801b7eb0db Merge pull request #1203 from J3vb/fix/updater-singleflight
fix(updater): coalesce and negative-cache text-asset fetches (W3-1)
2026-07-20 17:32:26 +02:00
J3vbandClaude Fable 5 3f8e5b59a1 fix(updater): coalesce and negative-cache text-asset fetches (W3-1)
FetchTextAssetCached served the unauthenticated, unrate-limited client-update
endpoint. On TTL expiry every concurrent caller missed the cache and issued its
own outbound fetch: a measured 25 requests for 25 callers. Failures were not
cached at all, so an upstream outage produced one outbound request per caller
for as long as it lasted.

Guards the refresh with singleflight so a burst issues one fetch, caches
failures for errorCacheTTL (mirroring the release cache's existing cachedErr
idiom), and evicts expired keys so the map no longer grows by one entry per
release for the process lifetime.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:15:06 +02:00
J3vb e1391c19ef Merge pull request #1202 from J3vb/chore/coder-websocket
chore(deps): migrate nhooyr.io/websocket to the maintained coder/websocket fork
2026-07-20 16:59:30 +02:00
J3vbandClaude Fable 5 fbcbd39a9c chore(deps): migrate nhooyr.io/websocket to github.com/coder/websocket
nhooyr.io/websocket now resolves to github.com/nhooyr/websocket-old and its
README is a one-line deprecation pointing at coder/websocket. Its last three
releases (v1.8.15-17) all shipped on 2024-08-10 as the redirect; the fork has
shipped through 2026-06-15.

The version number decreases (v1.8.17 -> v1.8.15) because both paths tagged in
the same space, but the coder release is ~2 years newer. Import path only; the
9 API symbols used are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 16:54:19 +02:00
J3vb a94104d201 Merge pull request #1201 from J3vb/docs/gif-key-policy
docs: record per-operator GIF key decision and surface setup
2026-07-20 16:29:35 +02:00