Files
J3vbandClaude Fable 5 88c7a8249a docs(b2-7): trust model, absence proofs, plugin boundary (#1443)
* docs(b2-7): trust model — who can read what (BPR-050/051, C-09 contract)

One document states the operator trust model in plain language and traces
every claim to a code line or test: server-readable text and files and why,
E2EE voice/video/screen with the key-holder and TOFU rules, transport per TLS
mode with desktop pinning and the browser rule, the C-09 preview destination
contract B7 implements, at-rest storage, operator can/cannot, multi-device
sessions, and what beta does not claim. Linked from security.md,
deployment.md, quick-start.md and docs/README.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg9QQWVN3E5UUgBD2dydtu

* test(b2-7): absence proofs — no federation/directory routes, outbound host table

TestAbsenceContract_NoFederationDirectoryOrListingRoutes builds the production
router with uploads, voice and GIF on, walks the whole mounted tree with
chi.Walk and fails on any route matching federat|directory|discover|listing.
A floor on the route count and a check that the admin subtree was traversed
keep it from passing vacuously. trust-model.md gains "What OwnCord does not
have" (BPR-040/082/083) and the outbound-host table B6's network capture
checks against.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg9QQWVN3E5UUgBD2dydtu

* docs(b2-7): plugin boundary — off twice, compiled out of releases, no API promise

docs/architecture/plugins.md records the experimental WASM boundary (BPR-080/
081, BG-17): disabled by build tag and by config, absent from release.yml and
Dockerfile builds, the HP-2 configuration audit (fresh, upgraded, Docker,
standalone), the beta release-notes wording, what exists today with its limits
and tests, the post-beta plugin candidates that stay in core during beta, and
the core concerns that never move. Linked from architecture/README.md,
architecture/server.md and docs/README.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg9QQWVN3E5UUgBD2dydtu

* docs(b2-7): L-08 re-tagged to B10 with the reason; B2-7 evidence block

L-08's remaining gate ("deterministic source build passes") cannot pass in
principle — TinyGo embeds host paths and has no -trimpath — and a compile-only
drift check would need a second Go SDK, TinyGo and Binaryen on every PR for a
subsystem release builds compile out. Re-tagged to B10, which runs the compile
once against the release candidate or closes on the provenance record; the
"no API promise" half is closed by docs/architecture/plugins.md. The plan's
B2-7 evidence block records the four pre-squash SHAs, the absence test's RED
output, the release-build finding, the decision, and the BPR-051 reader
placeholder. CHANGELOG gains a Documentation block.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg9QQWVN3E5UUgBD2dydtu

* fix(b2-7): Codex review — TOFU windows disclosed, legacy TOTP, absence test at three boundaries

Two P1 and three P2 from the Codex review of 56f23a36, all verified against
the code. trust-model.md now states the desktop's first-connection TLS TOFU
window and the out-of-band fingerprint check; scopes identity-key pinning to
changes after the first pin, not first contact; discloses that databases from
before TOTP encryption may still hold plaintext secrets and how re-enrolment
fixes that. The absence contract gains two sibling tests — WebSocket wire
types from protocol/schema.json and every koanf key of config.Config (one
allowlisted on-disk path whose presence the test asserts) — and the document
states what the three tests bound. plugins.md corrected: unknown config keys
are warned about and ignored, not rejected. Evidence block records PR #1443
and the review outcome.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg9QQWVN3E5UUgBD2dydtu

* fix(b2-7): Codex re-review — desktop first-use window in every TLS mode; tls off is plaintext

The desktop pins the fingerprint it sees on first connection in every
tls.mode (CaptureVerifier, no web-PKI validation on the server connection),
so a public-CA certificate closes the first-use window only for a browser;
the short answer and the pinning list now say so. tls.mode off served
directly is plaintext HTTP with nothing enforcing a proxy; the transport
table row states it. Evidence block records the round-2 outcome.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg9QQWVN3E5UUgBD2dydtu

* fix(b2-7): Codex round 3 — E2EE does not survive a hostile operator on first contact; memory cap is server-wide

Identity keys are trusted on first use, so a modified server can deliver an
unpinned peer's first announce with keys the operator holds and the key
holder wraps the room key to it. trust-model.md now scopes E2EE to an
operator who reads, and to a modified server only for peers pinned and
compared out of band beforehand, in every place the stronger claim stood.
plugins.md: the wazero runtime is sized from plugins.max_memory_mb alone;
a manifest's memory value is validated but not applied. Evidence block
records the round.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg9QQWVN3E5UUgBD2dydtu

* fix(b2-7): Codex round 4 — transport intro excludes tls off; ipAllowed is not the full C-09 deny-set

The transport section opened with "everything is TLS"; it now excludes
tls.mode off. The C-09 contract cited the server's ipAllowed as the complete
address deny-set; it rejects loopback, private, link-local, unspecified,
multicast and CGN only, so the clause now lists the documentation and
benchmarking ranges the native broker must add and records that widening
ipAllowed is a separate server change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg9QQWVN3E5UUgBD2dydtu

* fix(b2-7): Codex round 5 — backups exclude uploads; release-time appimagetool fetch listed

The built-in backup is VACUUM INTO of the SQLite file only; the trust model
and deployment.md now say uploaded files are not in it and upload.storage_dir
needs its own backup. The outbound-host section scopes "no script fetches an
external host" to scripts the server runs and lists the release workflow's
build-time appimagetool download.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg9QQWVN3E5UUgBD2dydtu

* fix(b2-7): Codex round 6 — one pin per account, LiveKit media in the host table, quick-start wording

Identity pins are keyed by host and user id, so a peer's second device
overwrites the pin and the first device then mismatches; the document no
longer claims per-device pinning. The outbound-host table gains the
supervised LiveKit subprocess's WebRTC media and scopes the capture contract
to traffic the server initiates. The quick-start cross-link no longer says
voice and video are unreadable by the operator.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg9QQWVN3E5UUgBD2dydtu

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 11:49:24 +00:00

3.8 KiB

Quick Start Guide

Get OwnCord running with the fewest possible steps.

Choose Your Setup Path

Goal Best path
Fastest local/LAN setup Prebuilt binaries
Linux server with easiest operations Docker
Custom dev build Build from source

Platform Support (Current Releases)

Component Windows x64 Linux x64 Linux ARM64
Server binary Yes Yes Not published yet
Desktop client Yes Yes Yes

Prerequisites

  • Go 1.26+ (only if building server from source)
  • Node.js 24+ and Rust (only if building client from source)
  • Docker + Compose v2 (Docker path only)
  • LiveKit (optional, required for voice/video)
  1. Download from GitHub Releases.
  2. Start the server:
    • Windows: chatserver.exe
    • Linux: ./chatserver
  3. Open https://localhost:8443/admin.
  4. Complete the setup wizard: it creates the Owner account and configures the basics (server name, port, security, uploads, voice). Your choices are written to config.yaml automatically — no manual editing needed.
  5. Create invite codes and share them.

Option B: Docker (Linux server)

cd Server
cp .env.example .env
cp livekit.yaml.example livekit.yaml
# Edit both files before start
docker compose up -d

Then open https://localhost:8443/admin and complete the setup wizard.

Full Docker details: Deployment Guide.

Option C: Build from source

# Server (Windows)
cd Server
go build -o chatserver.exe -ldflags "-s -w -X main.version=1.2.0-alpha.4" .

# Server (Linux)
cd Server
CGO_ENABLED=0 go build -o chatserver -ldflags "-s -w -X main.version=1.2.0-alpha.4" .

# Client
cd Client
npm install
npm run tauri build

What Happens on First Server Start

  • config.yaml is created with defaults.
  • data/ is created for DB, certs, uploads, and backups.
  • A self-signed TLS certificate is generated.
  • SQLite schema and migrations are applied.

Client Connection Notes

  • The default server address is https://<server-ip>:8443.
  • The desktop client uses TOFU certificate pinning:
    • First connection prompts for trust.
    • Future connections require the same cert fingerprint.
  • Who can read what on a server you run or join — text is readable by the operator; voice and video are end-to-end encrypted, with the limits that document states — is in trust-model.md.
  • Linux/Wayland: the client automatically sets WEBKIT_DISABLE_DMABUF_RENDERER=1 on Wayland sessions to work around WebKitGTK rendering crashes. Export the variable yourself (any value) before launching to override this.

If Remote Users Cannot Connect

  1. Use Tailscale for the simplest remote setup.
  2. Or configure Port Forwarding.

Optional: enable the GIF picker

GIFs are off by default and each server supplies its own key — OwnCord does not ship one, so nothing is shared between servers.

  1. Request a key at partner.klipy.com.
  2. Set it on the server, then restart:
# Preferred — keeps the credential out of config.yaml
OWNCORD_GIF_API_KEY=your_key_here

Or in config.yaml:

gif:
  api_key: "your_key_here"

The key stays server-side; clients only ever call /api/v1/gif/* on their own server. Until one is set, the client's GIF button is disabled with "GIFs are not enabled on this server" — nothing else is affected.

Next Steps