# The live call — what protects it, and what you can check

The other pages here describe the **async Space**: a sealed ledger, a content-blind platform, a decrypt-once
render. A live call is a **different guarantee with a different shape**, and conflating the two is the easiest
mistake to make about Witbitz. This page is the call's half.

> **One line:** a call's audio, video and chat are end-to-end encrypted between browsers and no server in the
> path can decode them — but the way you'd *verify* that is currently not exposed in Witbitz Call's UI (§4),
> and unlike upstream Kibitz, media here goes through a relay **by default** (§2).

> Witbitz Call **is** the [Kibitz](https://kibitz.chat) engine, rebranded — a pinned dependency, not a fork. The
> mechanisms below are Kibitz's; the configuration is ours, and it differs in two ways that matter. Engine-level
> detail: [kibitz.chat/security](https://kibitz.chat/security).

---

## 1. Media — end-to-end encrypted

Calls use WebRTC. Audio and video are encrypted with **DTLS-SRTP**, and the keys are negotiated directly between
the participating browsers — they never leave the devices. There is **no SFU or media server** that decodes,
mixes or records anything. Chat, shared links and directed messages travel the same way, over direct
DTLS-encrypted data channels between each pair; no participant relays another's content.

The single assumption is that those keys are exchanged honestly. That happens in signalling (§3), and §4 is how
you'd check it.

## 2. ⚑ The relay is the default here, not the exception

Upstream Kibitz uses a TURN relay only when a direct connection is impossible, and says most calls never need
one. **Witbitz Call sets `FORCE_RELAY_DEFAULT = true`** (`kibitz/src/core/callMedia.ts`) — direct-first was
tried and regressed three separate times on real networks, so relay-on is the deliberate default. A `?relay`
URL parameter overrides it.

What that means, stated plainly:

- **The relay cannot read the call.** It forwards packets that are already DTLS-SRTP encrypted; it sees
  ciphertext, IP addresses and traffic volume. The end-to-end property in §1 is unaffected.
- **Participants no longer see each other's IP addresses** — the relay's address stands in. On a direct WebRTC
  connection each peer learns the other's IP; routing through the relay removes that. This is a privacy *gain*
  over the direct path, and it is the upstream "hide my IP" behaviour turned on for everyone.
- **The relay is ours.** `/api/turn`, same-origin, on Witbitz infrastructure — not Kibitz's. So Witbitz sees
  the metadata a relay sees: who connected, from what address, for how long, and how much traffic. Not content.
- **It costs a little latency**, which is the trade accepted for calls that connect reliably.

## 3. Signalling — introductions only, and the one party that could attack

Two browsers cannot find each other unaided. A signalling server briefly exchanges setup data: a temporary peer
id, the room name from the link, and ICE candidates (which contain IP addresses). It sees connection metadata,
never call content, and stores none of it.

**Witbitz Call uses Kibitz's signalling broker** (`signal.kibitz.chat`) — Witbitz has no equivalent service. So
for the live-call path there is a third party in the introduction step, and it is worth naming rather than
glossing.

It matters because signalling is also where the browsers publish their certificate fingerprints. A compromised
or compelled signalling server is **the one party that could attempt to sit in the middle of a future call** by
substituting a key during setup. A relay cannot — it only ever sees ciphertext. That is exactly the attack §4
exists to catch.

## 4. ⚑ Verifying it — the safety code, and why you cannot use it here today

Every WebRTC "end-to-end" claim ultimately trusts the signalling channel to deliver the right keys. The
mechanism that removes that trust is a **safety code**: a short emoji code derived from the two browsers'
*actual* DTLS certificates — the ones really used on the wire, not the ones named in signalling. Same idea as
Signal's safety numbers. Matching codes mean nobody is in the middle; differing codes mean somebody is.

The engine implements this fully, including re-warning if a peer reconnects with different key material, and an
SSH-style alarm when a previously verified name reappears with a different key.

**It is not reachable in Witbitz Call.** The brand sets `VITE_BRAND_HIDE_PRIVACY_CHROME=1`, which suppresses the
shield button that opens *Verify* (`kibitz/src/widget/SecondaryControls.tsx`). The call is still end-to-end
encrypted — the flag hides UI, it does not change cryptography — but **the affordance that would let you check
that claim is currently switched off.**

That is the honest position: on this path you are trusting the signalling server, where upstream Kibitz would
let you verify it. If the call matters to you at that level, use kibitz.chat, where the shield is present.
Exposing it here is a UI decision, not a protocol one, and it is the single change that would make this page's
§1 claim checkable rather than asserted.

## 5. Offline — a room on one Wi-Fi, with no internet

For a plane, a cabin, a venue with dead cell: one Android device runs a **LAN hub**, everyone else opens the app
on the same Wi-Fi, and the room works with no internet at all. The download and setup are at
[witbitz.chat/call/host](https://witbitz.chat/call/host).

Like the internet broker, the hub is a **coordination point** — it sees who is in the room and carries the
handshakes. Content stays end-to-end encrypted: browser-to-browser when they can connect directly, or relayed
through the hub's own **LAN TURN as ciphertext** where they cannot (on phone Wi-Fi, iOS hides each device behind
an mDNS name the other cannot resolve). The hub never reads it, and it is a box you run on your own network.

## 6. What this protects — and what it does not

**Protected:** the content of the call, from the network and from Witbitz. It cannot be recorded server-side
because the media never reaches a server in readable form.

**Not protected, and not hidden:**

- **That a call happened**, its timing, duration and traffic volume — visible to the relay, which is now every
  call (§2).
- **Your IP address from Witbitz.** Hidden from other *participants* by the relay; visible to the relay itself.
- **The room is open by link unless gated.** By default anyone with the link can join. A knock-to-admit lobby,
  and verified/gated rooms (Google identity, mailed code, signed invites, a name list) are set at creation —
  see [the verified room](./room-link-auth.md), which is the same credential model the async Space uses.
- **The signalling channel** (§3), until the safety code is exposed (§4).
- **An agent's model backend.** When an AI agent joins, what it perceives leaves the encrypted room to reach a
  model — that is what an agent is for. It is disclosed to everyone before they join.

## 7. The agent in the call

An agent joins as a participant, and the **capability layer** governs it: every peer carries a grant of what it
may *perceive* (media, chat, roster, directed data) and *act*. **Humans default to full, agents to read-only.**
It is engine-enforced per peer — a withheld peer never receives the bytes — so a read-only agent gets chat but
no audio or screen share and can post nothing until granted. The same `Grant` vocabulary crosses to the async
Space unchanged ([the verified room §7](./room-link-auth.md)).

## 8. How this differs from a Space — do not conflate them

| | **Live call** | **Async Space** |
|---|---|---|
| Guarantee | E2EE **between browsers**; no server decodes | **content-blind at rest**; one decrypt-once render |
| Who could read it | nobody in the path — but see §3, §4 | the render, transiently, while a member is present |
| Verified by | a safety code — **not currently exposed** (§4) | four checks you run in a terminal ([verify](./verify.md)) |
| Persistence | ephemeral; the room vanishes when everyone leaves | a sealed ledger that persists |
| The moat claim | standard WebRTC, done carefully | [the double blind](./the-double-blind.md) |

The platform's verifiable-privacy argument is about the **Space**. The call is a well-built E2EE call — a
different and more conventional guarantee. Saying so plainly is the point of this page.

## 9. Where the code is

| Concern | Where |
|---|---|
| Media, relay policy (`FORCE_RELAY_DEFAULT`) | `kibitz/src/core/callMedia.ts` |
| Safety code / verify UI (hidden by the brand flag) | `kibitz/src/widget/SecondaryControls.tsx` |
| Capability grants | `kibitz/src/core/capabilities.ts` |
| TURN credentials (ours, same-origin) | `witbitz` `infra/turn.tf` → `/api/turn` |
| The engine, open source (Apache-2.0) | [github.com/kibitz-chat/kibitz](https://github.com/kibitz-chat/kibitz) |
