* fix(admin): reject banned users in admin auth (F1) adminAuthMiddleware accepted a Bearer token on session validity plus the ADMINISTRATOR bit alone and never consulted ban state, so a ban never revoked admin-panel access. Adds the auth.IsEffectivelyBanned guard that api.AuthMiddleware already uses, at both admin credential-resolution points. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(ws): gate the voice-channel text subscription on READ_MESSAGES (F2) registerNow subscribed any client with voice state to that channel's text-message topic regardless of READ_MESSAGES. The handshake's already-computed readable-channel set is now passed into registerNow and the subscription only happens when the voice channel is in it, preserving authorized reconnect delivery. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(service): require READ_MESSAGES to delete messages (F4) The non-DM delete gate checked MANAGE_MESSAGES without READ_MESSAGES, so a role locked out of a private channel could still delete every message in it. Requires ReadMessages alongside ManageMessages (and alongside SendMessages on the author path) and derives the mod flag from that same gate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(service): require READ_MESSAGES alongside MANAGE_MESSAGES in SetMessagePinned (F8) Pin/unpin checked only MANAGE_MESSAGES, so a role denied READ on a private channel could still pin and unpin its messages. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(service): enforce the DM block at every DM interaction sink (F5) The DM block was only checked on send, leaving edit, reactions, pins and typing as bypasses. One shared requireDMNotBlocked is now called from all of them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(ws): re-check CONNECT_VOICE when minting a refreshed LiveKit token (F6) voice_token_refresh re-minted a LiveKit token without re-checking CONNECT_VOICE, so a revoked permission kept working for the life of the session. The permission is now re-checked where the token is minted, and a 60s sweep evicts participants whose permission was revoked. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(ws): rate-limit voice_e2ee_offer after validation, keyed on server state (F7) The limiter key was built from unvalidated client input, letting an attacker grow the limiter map without bound. The limiter now runs after validation and keys on (sender, voiceChannelID), never on client input. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(ws): deliver voice_state/voice_leave only to roles that may read the channel (F9) Voice state of private channels was broadcast to every connected client, leaking channel membership. All 11 emit sites now route through one READ-filtered fan-out, channel-tagged so replay filters too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(api): redact the LiveKit access token from proxy dial-failure logs (F10) A dial failure wrote the LiveKit access-token JWT into the server log via the URL in the error. redactKey now runs on the error before it reaches slog. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(auth): reserve the [deleted-N] username namespace (F11, F12) The tombstone username namespace used by account deletion was freely registrable, letting a user impersonate a deleted account. The namespace is now reserved at validation, and DeleteAccount retries with a random suffix on collision. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(api): strip Unicode format characters from upload filenames (F13) The attachment filename sanitizer stripped control characters but not unicode.Cf, allowing bidi-override extension spoofing. Cf is now stripped alongside controls and foreign path separators are cut. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(api): reserve the login attempt before the bcrypt compare (F3) The per-username lockout was a read-only IsLockedOut check followed by a failure recorded only after the ~250ms bcrypt compare, so N concurrent requests all passed the stale check before any of them recorded a failure. The per-username cap is the only cross-IP brute-force defence (the middleware limits per IP), so a distributed burst landed N guesses per 15-minute window instead of 10. Both counters are now reserved atomically with limiter.Allow before the compare, and the lockout decision moves to the read-only limiter.Check so the reservation is not double-counted. The limits are sized at threshold+1, which leaves the sequential accepted-input set byte-identical to the previous behaviour: failures 1-10 still land, the 10th still trips the lockout, and the account owner's correct password on attempt 10 still returns 200. Sizing at threshold instead would make 9 cheap wrong guesses convert the victim's own correct password into a 15-minute lockout - the regression that got two earlier attempts at this fix rejected, now pinned by a boundary test. Deliberately scoped to handleLogin. The report also suggested widening to the password-confirmation endpoints, but those are authenticated, share a single pw_confirm_fail key across the TOTP endpoints, and widening there is what got the first attempt rejected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(deps): bump five Rust dependencies in /Client/tauri-client/src-tauri Rolls up dependabot #1259, #1260, #1261, #1262 and #1263: tauri-build 2.5.6 -> 2.6.3 tauri-plugin-fs 2.4.5 -> 2.5.1 tauri-plugin-http 2.5.7 -> 2.5.9 tauri-plugin-store 2.4.2 -> 2.4.4 webpki-roots 1.0.6 -> 1.0.9 All five are lockfile-only; the manifest constraints already permitted the new versions. The five PRs each rewrote overlapping regions of the same Cargo.lock and so could not be merged independently, so the lockfile was regenerated with cargo update --precise for each crate instead. The combined result is smaller than the sum of the five diffs because they share transitive updates. Verified with cargo check --locked --all-targets (exit 0). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(deps): bump typescript-eslint from 8.58.0 to 8.65.0 in /Client/tauri-client Dependabot #1258. 8.65.0 improves @typescript-eslint/no-unnecessary-type-assertion, which surfaces four assertions that were already redundant and now fail the lint gate. They are removed here rather than in a follow-up so no commit in this branch leaves `npm run lint` red: UserBar.ts / members.store.ts "online" as UserStatus -> "online" (the receiver already accepts the literal) media.ts drops `as RequestInit` on a literal that is already assignable LoginForm.ts drops `as { message: unknown }` made redundant by the `"message" in err` narrowing All four are the rule's own autofix. Verified: npm run typecheck, npm run lint, npm run format:check all clean, and the unit suite is 3572/3572 green across 129 files. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
OwnCord
The gaming chat platform you actually own.
Early Alpha / Work in Progress OwnCord is in active development and is not production-ready. Expect rough edges, rapid changes, and occasional breaking behavior.
Do not use it for sensitive communications yet.
Development Model
OwnCord is built with an AI-first development workflow. Most implementation is generated through autonomous AI tooling, with quality validated primarily through automated checks (CI, tests, linting) and real-world feedback during alpha.
This approach enables fast iteration, but it also means behavior may change quickly between releases.
OwnCord is a self-hosted chat stack with a Go server and a Tauri desktop client. It includes real-time messaging, voice/video via LiveKit, file sharing, and a web admin panel.
Current Project Status
| Area | Status |
|---|---|
| Core chat flow | Working in alpha |
| Voice/video | Working in alpha |
| Admin panel | Working in alpha |
| Security hardening | First full review pass landed in v1.1.0-alpha.2; ongoing |
Platform Support (Current Releases)
| Component | Windows x64 | Linux x64 | Linux ARM64 |
|---|---|---|---|
| Server binary | Yes | Yes | Not yet |
| Desktop client | Yes (NSIS installer) | Yes (AppImage, .deb) | Yes (AppImage, .deb) |
| Docker server | N/A | Build from source (compose) | Not yet |
Start Here
- New user quick path: docs/quick-start.md
- Linux Docker deployment: docs/deployment.md
- Remote access without router config: docs/tailscale.md
- Manual router/network setup: docs/port-forwarding.md
Quick Start
Option A: Prebuilt binaries
- Download assets from OwnCord-releases (binaries, checksums, signatures, and a full source snapshot per release).
- Run the server binary:
- Windows:
chatserver.exe - Linux:
./chatserver
- Windows:
- Open
https://localhost:8443/adminand create your Owner account. - Generate invite codes in the admin panel and share them with friends.
Option B: Docker (Linux server)
cd Server
cp .env.example .env
cp livekit.yaml.example livekit.yaml
# Edit both files before starting
docker compose up -d
See the full setup guide in docs/deployment.md.
The client uses TOFU (Trust On First Use) for self-signed certificates: it prompts once, then pins the certificate for future connections.
What OwnCord Already Has
- Real-time channels and direct messages over WebSocket
- Voice/video channels via LiveKit
- Invite-only registration and role-based permissions
- Web admin panel with logs, backups, and update tooling
- File uploads and inline media rendering
- TOTP 2FA support and API rate limiting
- Desktop client auto-update with signature verification
- GIF picker — off by default; each server supplies its own
Klipy key via
gif.api_key(setup)
See deeper feature and architecture docs in docs/architecture/ and docs/protocol.md.
Architecture
Two main components:
- Go server (REST API, WebSocket hub, SQLite, admin panel)
- Tauri v2 desktop client (Rust backend + TypeScript frontend)
+---------------------+ +---------------------+
| OwnCord Client | | OwnCord Server |
| (Tauri v2) | | (Go) |
| | | |
| +---------------+ | WSS | +---------------+ |
| | Chat UI |--+------->| | WebSocket Hub| |
| +---------------+ | | +---------------+ |
| +---------------+ | HTTPS | +---------------+ |
| | REST Client |--+------->| | REST API | |
| +---------------+ | | +---------------+ |
| +---------------+ | LiveKit | +---------------+ |
| | Voice/Video |--+------->| | LiveKit SFU | |
| +---------------+ | | +---------------+ |
+---------------------+ | +---------------+ |
| | SQLite DB | |
| +---------------+ |
+---------------------+
Build and Test
Prerequisites
- Go 1.25+
- Node.js 20+
- Rust stable (client builds)
Build from source
# Server (Windows)
cd Server
go build -o chatserver.exe -ldflags "-s -w -X main.version=1.1.0-alpha.2" .
# Server (Linux)
cd Server
CGO_ENABLED=0 go build -o chatserver -ldflags "-s -w -X main.version=1.1.0-alpha.2" .
# Client
cd Client/tauri-client
npm install
npm run tauri build
Core verification commands
# Server
cd Server
go test ./...
# Client
cd Client/tauri-client
npm run typecheck
npm run lint
npm test
For the full command set, use docs/contributing.md.
Configuration
On first run, the server generates config.yaml and a local data/ directory:
data/
├── chatserver.db
├── certs/
├── uploads/
└── backups/
Key options include TLS mode, upload limits, LiveKit settings, and admin CIDR restrictions. See docs/server-configuration.md.
Security and Vulnerability Reporting
- For vulnerabilities, use GitHub Security Advisories (private disclosure flow).
- Do not open public issues for security bugs.
- Read full policy and hardening notes in docs/security.md.
Update Signing Notes (Maintainers)
Client and server update signing keys are intentionally separate.
Required Actions secrets for release signing:
TAURI_SIGNING_PRIVATE_KEYTAURI_SIGNING_PRIVATE_KEY_PASSWORDSERVER_UPDATE_SIGNING_PRIVATE_KEYSERVER_UPDATE_SIGNING_PRIVATE_KEY_PASSWORD
When rotating the server updater key, update Server/updater/server_update_public_key.txt and use staged rollover for live fleets.
Docs Index
- docs/quick-start.md
- docs/deployment.md
- docs/livekit-setup.md
- docs/port-forwarding.md
- docs/tailscale.md
- docs/architecture/ — system blueprints (diagrams + flows)
- docs/audit-2026-07-19.md — latest architecture & spec-conformance audit
- docs/api.md
- docs/protocol.md
- docs/schema.md
- docs/architecture/client.md — client architecture (replaces client-architecture.md)
- docs/architecture/ux/ — client UX specification (target-state flows, per-view states, event→reaction maps)
- docs/contributing.md
- docs/security.md
Contributing
- Create a branch from
main. - Keep changes focused and tested.
- Open a PR targeting
main.
See docs/contributing.md for the full process.
License
AGPL-3.0


