# The attested client — knowing the code that runs on the user's side

> **Status: design / roadmap. Not built.** Everything else on this site attests the *server* — a reproducible build,
> a locked egress allowlist, a signed `/cert.json`. This is the missing symmetric half: attesting the *client*. It is a
> direction, not a deployed property. When a room can require it, this line will say so.

Every proof on this site is about code we run on our side. The client — the page in the user's browser — has been
trusted on faith. And that faith is misplaced by construction: **you cannot verify the code a browser is running.** The
user owns the runtime; any integrity check the page performs on itself can be hooked, patched, or spoofed, and nothing
stops the user from ignoring the app entirely and calling the API with their own client. So a modified client can
interact with a room in ways the audited app never would.

The response is *not* to try to attest the browser — that's unwinnable. It's to offer, opt-in, a client whose code
**can** be attested, without giving up the web app.

## The idea: a thin native shell around the same reproducible bundle

The app you already ship is a static, egress-locked, reproducibly-built web bundle. The attested client is that exact
bundle, wrapped in a thin native shell whose only jobs are narrow and auditable:

1. **present a hardware attestation** — prove a genuine, unmodified build of the app is running on a genuine device;
2. **hold keys in hardware** — the Secure Enclave / StrongBox / TPM, so the room key never sits in JS;
3. **load the bundle locally** into a locked web view;
4. **freshness-bind** the attestation to each action.

You don't rewrite the app natively. There is **one source of truth** across web and attested-native, the privacy model
is preserved — in fact strengthened, since keys move into hardware — and the audit surface is a thin generic shell plus
the web code reviewers already read. It is the [Companion](https://docs.witbitz.chat/docs/the-companion.md) promoted
from a convenience into a trust primitive.

## The one rule that makes it real, not theater

**The web bundle must ship inside the attested binary and load locally — never off the network.**

- **Bundled, loaded from a local scheme:** the app's signature covers its embedded assets, so an attestation of "genuine,
  unmodified app" *includes the bundle*. Tamper with the bundle and the signature no longer matches — the attestation
  fails. The attested thing and the executed logic are the same thing. ✅
- **A web view pointed at a live URL (this includes Android Trusted Web Activities):** you attest the *shell*, which
  then runs whatever the network hands it — outside the attestation, and hookable at runtime. That is **worse than a
  plain browser**, because it launders trust it never earned. ❌

The Trusted Web Activity is the tempting trap here precisely because it is the easiest to build. Two locks keep the
bundled version honest: the native↔JS bridge must be minimal and audited (no general script-injection surface), and the
existing **egress-lock CSP** already forbids the bundle from pulling in remote script — so a bundled page can't quietly
turn itself back into a network loader.

And the attestation must bind to **every action**, not once at admission — otherwise a member attests with the genuine
app, then switches to a patched client for the writes that follow. The room's existing freshness challenge is the hook:
each signed entry carries a fresh, attestation-bound value.

## Platform support — and an honest split

The attested client is a web-view shell on each platform. The strength of the guarantee differs, and it's worth being
exact about two *distinct* properties: **integrity** ("the running bundle is unmodified") and **reproducibility**
("the shipped binary rebuilds from published source").

| Platform | Shell | Attestation | Keys | Notes |
|---|---|---|---|---|
| **Android** | Android WebView | Play Integrity, or **hardware Keystore attestation** (X.509 rooted in Google, carries app-signing digest + Verified Boot) | StrongBox / TEE | **Cleanest full chain.** Self-distributed + reproducible APK + Keystore attestation gives source → binary → attested. (Play Integrity is easier but ties you to Play distribution, which fights reproducibility — a real tradeoff.) |
| **iOS / iPadOS / macOS** | WKWebView | **App Attest** (hardware-backed, iOS 14+ / macOS 11+) | Secure Enclave | **Integrity strong; reproducibility partial.** Apple re-signs and encrypts the shipped binary, so you can't rebuild the App-Store artifact from source — you lean on a transparency-logged build pipeline for provenance. |
| **Windows** | WebView2 | **Assembled** — TPM 2.0 measured boot + Azure Attestation (device) · TPM key attestation (key) · Authenticode/MSIX + optional **WDAC** (app) | TPM 2.0 | No single "App Attest for Win32"; you compose device + key + binary-measurement attestation. **Strongest on a managed enterprise fleet** (MDM + WDAC), which is exactly the enterprise buyer. |

So **integrity of the running bundle is attestable on every platform**; end-to-end **reproducibility of the shipped
binary** is Android-clean, iOS-partial, and Windows-DIY. State that split plainly rather than implying one uniform
guarantee.

## The egress lock, generalized

Today the egress lock **is** the CSP header. With attested native clients it splits into two things a verifier keeps
separate:

- **The policy** — one canonical, platform-neutral egress allowlist, signed into `/cert.json`: the *room-data* path
  goes only to the declared origin, and the shell additionally reaches only a small, fixed set of platform-service
  endpoints (the attestation provider, push). A verifier reasons about **one** declaration.
- **The enforcement** — the per-platform binding of that policy. The web CSP is just its *web* binding:

| Platform | Enforces the allowlist via | How a skeptic checks it |
|---|---|---|
| **Web** | the CSP header (`connect-src …`) | **read the live header** — self-verifiable, trust nothing |
| **iOS / macOS** | CSP inside WKWebView + App Transport Security; a Network Extension / per-app VPN under MDM | reproduce + attest the binary and its `Info.plist` (ATS is TLS policy, not a host allowlist) |
| **Android** | CSP inside WebView + `network_security_config.xml`; always-on VPN under MDM | reproduce + attest the APK and its network config |
| **Windows** | CSP inside WebView2 + AppContainer network capabilities (MSIX) + Firewall / WDAC | attest the binary + its manifest (capabilities are coarse — internet vs private, not per-host) |

**The honest inversion.** Every native OS control here is *coarser* than `connect-src` — none is a clean per-host
allowlist you can read off a live response. So on native, the fine-grained guarantee leans more on the **reproducible,
attested binary** ("this app provably contains no network code beyond the declared endpoints") than on a readable OS
policy, and the verification *method* flips: on the web you **read a header**; on native you **reproduce and attest the
binary** and inspect its (coarser) declared config. The web is not the weak link in the egress story — it is the
platform with the *cleanest, self-verifiable* lock. Native buys code attestation and partly pays it back in
egress-verifiability, making up the difference with the reproducible build.

**Purpose-scoping is mandatory.** The shell needs egress the web bundle doesn't — the attestation call to
Apple / Google / Microsoft, and push. So the canonical allowlist scopes by purpose: the *room-data* path stays
same-origin and locked exactly as today; the shell adds only a small, fixed, **declared** set of platform endpoints.
That is a mild new trust surface (the platform attestor sees metadata), and it is stated, not hidden — the same honesty
discipline as the rest of the cert.

## How a room requires it

"Only attested clients may join" is not a client-asserted flag — it is an **admission condition set by the room/app
owner and verified on the server**, in the same slot that carries "only my registered users" (see
[The Space link is the auth](https://docs.witbitz.chat/docs/room-link-auth.md)). At join and on each write, the server
checks the platform attestation, bound to the member's signing key. Other members can be shown that a participant is on
an attested client — the assurance is visible peer-to-peer, not just to the server. Admission and client-integrity
become one policy: `{ authenticated identity } AND { attested client }`.

## What it buys — and what it doesn't

It **shrinks** the hardest residual: content injection between members. If only the audited, unmodified app can write,
and that app is audited to seal only well-formed payloads, arbitrary injection is off the table for compliant members.

It does **not** make the client omnipotent-proof. A jailbroken or rooted device (attestation fights this, but it's an
arms race), a vulnerability in the genuine app, and the fact that even an attested client still processes untrusted
content *from others* all remain — so receiver hardening (XSS-safe rendering, sandboxed widgets, validated tool inputs)
still matters; it just matters less. And because requiring an install trades away the open-a-link reach that makes the
web app spread, this is **opt-in and additive**: web rooms keep the untrusted-client architecture (accountable identity
+ server-side enforcement); attested-native rooms get this stronger tier on top.

## Why it fits

This is the natural apex of the attestation story the [attested tier](https://docs.witbitz.chat/docs/the-attested-tier.md)
started on the server side. Today the render is attested and reproducible while the client is trusted on faith — an
asymmetry. Closing it with a bundled, attested, hardware-keyed client makes the system attestable **end to end** for the
rooms that ask for it, without abandoning the static-web-page model that makes any of it verifiable in the first place.
